From b47f43d5f2be4051daf8d1d0e2644245bd48b1b1 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 4 Aug 2026 17:31:52 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=20T(v)=20=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vipeye-internal/src/analyzer/binder.ts | 17 +- .../src/analyzer/typeEvaluator.ts | 60935 ++++++++-------- .../src/tests/viperGenericTypeVarCall.test.ts | 162 + 3 files changed, 30671 insertions(+), 30443 deletions(-) create mode 100644 packages/vipeye-internal/src/tests/viperGenericTypeVarCall.test.ts diff --git a/packages/vipeye-internal/src/analyzer/binder.ts b/packages/vipeye-internal/src/analyzer/binder.ts index 79fff4d2..adfd89da 100644 --- a/packages/vipeye-internal/src/analyzer/binder.ts +++ b/packages/vipeye-internal/src/analyzer/binder.ts @@ -631,9 +631,24 @@ export class Binder extends ParseTreeWalker { // Don't walk the body of the function until we're done analyzing // the current scope. + // Viper 扩展:泛型类的方法需要能引用类的类型参数(如 `self.value = T(v)`)。 + // Python 默认把方法的 parent scope 设为最近的非 Class scope(跳过 Class 与 + // typeParamScope),导致方法内 `T` 解析到模块级符号而非本类 type param。 + // 当 enclosingClass 声明了 typeParams(PEP 695 `class Box[T]`)时,将方法 + // parent 设为 Class scope,使查找链 Function → Class → typeParamScope 可达 + // 类型参数。类成员(如 `value: T`)仍通过 self 访问,不受影响。 + let methodParentScope: Scope | undefined; + if ( + containingClassNode && + containingClassNode.d.typeParams && + IsViperDialect(this._fileInfo.executionEnvironment.dialect) + ) { + methodParentScope = AnalyzerNodeInfo.getScope(containingClassNode); + } + this._createNewScope( ScopeType.Function, - typeParamScope ?? this._getNonClassParentScope(), + methodParentScope ?? typeParamScope ?? this._getNonClassParentScope(), /* proxyScope */ undefined, /* chainedModuleLevelScopeLookup */ undefined, () => { diff --git a/packages/vipeye-internal/src/analyzer/typeEvaluator.ts b/packages/vipeye-internal/src/analyzer/typeEvaluator.ts index a10b9fce..ea025e53 100644 --- a/packages/vipeye-internal/src/analyzer/typeEvaluator.ts +++ b/packages/vipeye-internal/src/analyzer/typeEvaluator.ts @@ -1,8415 +1,8415 @@ -/* - * typeEvaluator.ts - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT license. - * Author: Eric Traut - * - * Module that evaluates types of parse tree nodes within - * a program. - * - * Note: This is a gargantuan module - much larger than I would - * normally create. It is written this way primarily for performance, - * with the internal methods having access to the full closure of - * the createTypeEvaluator function. This is the same approach - * taken by the TypeScript compiler. - */ - -import { CancellationToken } from 'vscode-languageserver'; - -import { invalidateTypeCacheIfCanceled, throwIfCancellationRequested } from '../common/cancellationUtils'; -import { appendArray } from '../common/collectionUtils'; -import { DiagnosticLevel } from '../common/configOptions'; -import { ConsoleInterface } from '../common/console'; -import { isThenable } from '../common/core'; -import { assert, assertNever, fail } from '../common/debug'; -import { DiagnosticAddendum } from '../common/diagnostic'; -import { DiagnosticRule } from '../common/diagnosticRules'; -import { IsViperDialect } from '../common/dialect'; -import { convertOffsetsToRange, convertOffsetToPosition } from '../common/positionUtils'; -import { - PythonVersion, - pythonVersion3_13, - pythonVersion3_14, - pythonVersion3_6, - pythonVersion3_7, - pythonVersion3_9, -} from '../common/pythonVersion'; -import { TextRange } from '../common/textRange'; -import { Uri } from '../common/uri/uri'; -import { LocAddendum, LocMessage, ParameterizedString } from '../localization/localize'; -import { - ArgCategory, - ArgumentNode, - AssignmentNode, - AugmentedAssignmentNode, - AwaitNode, - BinaryOperationNode, - CallNode, - CaseNode, - ClassNode, - ComprehensionForIfNode, - ComprehensionNode, - ConstantNode, - DecoratorNode, - DictionaryNode, - ErrorExpressionCategory, - ExceptNode, - ExecutionScopeNode, - ExpressionNode, - FormatStringNode, - ForNode, - FunctionNode, - ImportAsNode, - ImportFromAsNode, - ImportFromNode, - IndexNode, - isExpressionNode, - LambdaNode, - ListNode, - MatchNode, - MemberAccessNode, - NameNode, - NumberNode, - ParamCategory, - ParameterNode, - ParseNode, - ParseNodeType, - SetNode, - SliceNode, - StringListNode, - StringNode, - TupleNode, - TypeAliasNode, - TypeAnnotationNode, - TypeParameterListNode, - TypeParameterNode, - TypeParameterScopeNode, - TypeParamKind, - UnpackNode, - WithItemNode, - YieldFromNode, - YieldNode, -} from '../parser/parseNodes'; -import { ParseOptions, Parser, ParseTextMode } from '../parser/parser'; -import { KeywordType, OperatorType, StringTokenFlags } from '../parser/tokenizerTypes'; -import { AnalyzerFileInfo, ImportLookup, isAnnotationEvaluationPostponed } from './analyzerFileInfo'; -import * as AnalyzerNodeInfo from './analyzerNodeInfo'; -import { CodeFlowAnalyzer, FlowNodeTypeOptions, FlowNodeTypeResult, getCodeFlowEngine } from './codeFlowEngine'; -import { - CodeFlowReferenceExpressionNode, - createKeyForReference, - FlowFlags, - FlowNode, - FlowWildcardImport, - isCodeFlowSupportedForReference, - wildcardImportReferenceKey, -} from './codeFlowTypes'; -import { ConstraintSolution } from './constraintSolution'; -import { - addConstraintsForExpectedType, - applySourceSolutionToConstraints, - assignTypeVar, - solveConstraints, - solveConstraintSet, -} from './constraintSolver'; -import { ConstraintSet, ConstraintTracker } from './constraintTracker'; -import { createFunctionFromConstructor, getBoundInitMethod, validateConstructorArgs } from './constructors'; -import { applyDataClassClassBehaviorOverrides, synthesizeDataClassMethods } from './dataClasses'; -import { - ClassDeclaration, - Declaration, - DeclarationType, - FunctionDeclaration, - isVariableDeclaration, - ModuleLoaderActions, - SpecialBuiltInClassDeclaration, - VariableDeclaration, -} from './declaration'; -import { - getDeclarationsWithUsesLocalNameRemoved, - getNameNodeForDeclaration, - resolveAliasDeclaration as resolveAliasDeclarationUtil, - ResolvedAliasInfo, - synthesizeAliasDeclaration, -} from './declarationUtils'; -import { - addOverloadsToFunctionType, - applyClassDecorator, - applyFunctionDecorator, - FunctionDecoratorInfo, - getDeprecatedMessageFromCall, - getFunctionInfoFromDecorators, -} from './decorators'; -import { - createEnumType, - getEnumAutoValueType, - getTypeOfEnumMember, - isDeclInEnumClass, - isEnumClassWithMembers, - isEnumMetaclass, -} from './enums'; -import { applyFunctionTransform } from './functionTransform'; -import { createNamedTupleType } from './namedTuples'; -import { - getTypeOfAugmentedAssignment, - getTypeOfBinaryOperation, - getTypeOfTernaryOperation, - getTypeOfUnaryOperation, -} from './operations'; -import { - getParamListDetails, - isParamSpecArgs, - isParamSpecKwargs, - ParamAssignmentTracker, - ParamKind, - ParamListDetails, - VirtualParamDetails, -} from './parameterUtils'; -import * as ParseTreeUtils from './parseTreeUtils'; -import { assignTypeToPatternTargets, checkForUnusedPattern, narrowTypeBasedOnPattern } from './patternMatching'; -import { assignProperty } from './properties'; -import { assignClassToProtocol, assignModuleToProtocol } from './protocols'; -import { Scope, ScopeType, SymbolWithScope } from './scope'; -import * as ScopeUtils from './scopeUtils'; -import { createSentinelType } from './sentinel'; -import { evaluateStaticBoolExpression } from './staticExpressions'; -import { indeterminateSymbolId, Symbol, SymbolFlags, SynthesizedTypeInfo } from './symbol'; -import { isConstantName, isPrivateName, isPrivateOrProtectedName } from './symbolNameUtils'; -import { getLastTypedDeclarationForSymbol, isEffectivelyClassVar } from './symbolUtils'; -import { assignTupleTypeArgs, expandTuple, getSlicedTupleType, getTypeOfTuple, makeTupleObject } from './tuples'; -import { SpeculativeModeOptions, SpeculativeTypeTracker } from './typeCacheUtils'; -import { - assignToTypedDict, - assignTypedDictToTypedDict, - createTypedDictType, - createTypedDictTypeInlined, - getTypedDictDictEquivalent, - getTypedDictMappingEquivalent, - getTypedDictMembersForClass, - getTypeOfIndexedTypedDict, - synthesizeTypedDictClassMethods, -} from './typedDicts'; -import { - AbstractSymbol, - Arg, - ArgResult, - ArgWithExpression, - AssignTypeFlags, - CallResult, - CallSignature, - CallSignatureInfo, - CallSiteEvaluationInfo, - ClassMemberLookup, - ClassTypeResult, - DeclaredSymbolTypeInfo, - EffectiveTypeResult, - ensureExpectedTypeCandidates, - EvalFlags, - EvaluatorUsage, - ExpectedTypeOptions, - ExpectedTypeResult, - FunctionTypeResult, - MagicMethodDeprecationInfo, - MapSubtypesOptions, - maxInferredContainerDepth, - maxSubtypesForInferredType, - MemberAccessDeprecationInfo, - PrefetchedTypes, - PrintTypeOptions, - Reachability, - ResolveAliasOptions, - SolveConstraintsOptions, - SymbolDeclInfo, - TypeEvaluator, - TypeResult, - TypeResultWithNode, - ValidateArgTypeParams, - ValidateTypeArgsOptions, -} from './typeEvaluatorTypes'; -import { enumerateLiteralsForType } from './typeGuards'; -import * as TypePrinter from './typePrinter'; -import { - AnyType, - ClassType, - ClassTypeFlags, - combineTypes, - DataClassBehaviors, - EnumLiteral, - findSubtype, - FunctionParam, - FunctionParamFlags, - FunctionType, - FunctionTypeFlags, - InheritanceChain, - isAny, - isAnyOrUnknown, - isClass, - isClassInstance, - isFunction, - isFunctionOrOverloaded, - isInstantiableClass, - isMethodType, - isModule, - isNever, - isCDefineType, - isCArrayType, - isCPtrType, - isCVoidType, - getCPtrLevel, - isCConstType, - isCStaticType, +/* + * typeEvaluator.ts + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * Author: Eric Traut + * + * Module that evaluates types of parse tree nodes within + * a program. + * + * Note: This is a gargantuan module - much larger than I would + * normally create. It is written this way primarily for performance, + * with the internal methods having access to the full closure of + * the createTypeEvaluator function. This is the same approach + * taken by the TypeScript compiler. + */ + +import { CancellationToken } from 'vscode-languageserver'; + +import { invalidateTypeCacheIfCanceled, throwIfCancellationRequested } from '../common/cancellationUtils'; +import { appendArray } from '../common/collectionUtils'; +import { DiagnosticLevel } from '../common/configOptions'; +import { ConsoleInterface } from '../common/console'; +import { isThenable } from '../common/core'; +import { assert, assertNever, fail } from '../common/debug'; +import { DiagnosticAddendum } from '../common/diagnostic'; +import { DiagnosticRule } from '../common/diagnosticRules'; +import { IsViperDialect } from '../common/dialect'; +import { convertOffsetsToRange, convertOffsetToPosition } from '../common/positionUtils'; +import { + PythonVersion, + pythonVersion3_13, + pythonVersion3_14, + pythonVersion3_6, + pythonVersion3_7, + pythonVersion3_9, +} from '../common/pythonVersion'; +import { TextRange } from '../common/textRange'; +import { Uri } from '../common/uri/uri'; +import { LocAddendum, LocMessage, ParameterizedString } from '../localization/localize'; +import { + ArgCategory, + ArgumentNode, + AssignmentNode, + AugmentedAssignmentNode, + AwaitNode, + BinaryOperationNode, + CallNode, + CaseNode, + ClassNode, + ComprehensionForIfNode, + ComprehensionNode, + ConstantNode, + DecoratorNode, + DictionaryNode, + ErrorExpressionCategory, + ExceptNode, + ExecutionScopeNode, + ExpressionNode, + FormatStringNode, + ForNode, + FunctionNode, + ImportAsNode, + ImportFromAsNode, + ImportFromNode, + IndexNode, + isExpressionNode, + LambdaNode, + ListNode, + MatchNode, + MemberAccessNode, + NameNode, + NumberNode, + ParamCategory, + ParameterNode, + ParseNode, + ParseNodeType, + SetNode, + SliceNode, + StringListNode, + StringNode, + TupleNode, + TypeAliasNode, + TypeAnnotationNode, + TypeParameterListNode, + TypeParameterNode, + TypeParameterScopeNode, + TypeParamKind, + UnpackNode, + WithItemNode, + YieldFromNode, + YieldNode, +} from '../parser/parseNodes'; +import { ParseOptions, Parser, ParseTextMode } from '../parser/parser'; +import { KeywordType, OperatorType, StringTokenFlags } from '../parser/tokenizerTypes'; +import { AnalyzerFileInfo, ImportLookup, isAnnotationEvaluationPostponed } from './analyzerFileInfo'; +import * as AnalyzerNodeInfo from './analyzerNodeInfo'; +import { CodeFlowAnalyzer, FlowNodeTypeOptions, FlowNodeTypeResult, getCodeFlowEngine } from './codeFlowEngine'; +import { + CodeFlowReferenceExpressionNode, + createKeyForReference, + FlowFlags, + FlowNode, + FlowWildcardImport, + isCodeFlowSupportedForReference, + wildcardImportReferenceKey, +} from './codeFlowTypes'; +import { ConstraintSolution } from './constraintSolution'; +import { + addConstraintsForExpectedType, + applySourceSolutionToConstraints, + assignTypeVar, + solveConstraints, + solveConstraintSet, +} from './constraintSolver'; +import { ConstraintSet, ConstraintTracker } from './constraintTracker'; +import { createFunctionFromConstructor, getBoundInitMethod, validateConstructorArgs } from './constructors'; +import { applyDataClassClassBehaviorOverrides, synthesizeDataClassMethods } from './dataClasses'; +import { + ClassDeclaration, + Declaration, + DeclarationType, + FunctionDeclaration, + isVariableDeclaration, + ModuleLoaderActions, + SpecialBuiltInClassDeclaration, + VariableDeclaration, +} from './declaration'; +import { + getDeclarationsWithUsesLocalNameRemoved, + getNameNodeForDeclaration, + resolveAliasDeclaration as resolveAliasDeclarationUtil, + ResolvedAliasInfo, + synthesizeAliasDeclaration, +} from './declarationUtils'; +import { + addOverloadsToFunctionType, + applyClassDecorator, + applyFunctionDecorator, + FunctionDecoratorInfo, + getDeprecatedMessageFromCall, + getFunctionInfoFromDecorators, +} from './decorators'; +import { + createEnumType, + getEnumAutoValueType, + getTypeOfEnumMember, + isDeclInEnumClass, + isEnumClassWithMembers, + isEnumMetaclass, +} from './enums'; +import { applyFunctionTransform } from './functionTransform'; +import { createNamedTupleType } from './namedTuples'; +import { + getTypeOfAugmentedAssignment, + getTypeOfBinaryOperation, + getTypeOfTernaryOperation, + getTypeOfUnaryOperation, +} from './operations'; +import { + getParamListDetails, + isParamSpecArgs, + isParamSpecKwargs, + ParamAssignmentTracker, + ParamKind, + ParamListDetails, + VirtualParamDetails, +} from './parameterUtils'; +import * as ParseTreeUtils from './parseTreeUtils'; +import { assignTypeToPatternTargets, checkForUnusedPattern, narrowTypeBasedOnPattern } from './patternMatching'; +import { assignProperty } from './properties'; +import { assignClassToProtocol, assignModuleToProtocol } from './protocols'; +import { Scope, ScopeType, SymbolWithScope } from './scope'; +import * as ScopeUtils from './scopeUtils'; +import { createSentinelType } from './sentinel'; +import { evaluateStaticBoolExpression } from './staticExpressions'; +import { indeterminateSymbolId, Symbol, SymbolFlags, SynthesizedTypeInfo } from './symbol'; +import { isConstantName, isPrivateName, isPrivateOrProtectedName } from './symbolNameUtils'; +import { getLastTypedDeclarationForSymbol, isEffectivelyClassVar } from './symbolUtils'; +import { assignTupleTypeArgs, expandTuple, getSlicedTupleType, getTypeOfTuple, makeTupleObject } from './tuples'; +import { SpeculativeModeOptions, SpeculativeTypeTracker } from './typeCacheUtils'; +import { + assignToTypedDict, + assignTypedDictToTypedDict, + createTypedDictType, + createTypedDictTypeInlined, + getTypedDictDictEquivalent, + getTypedDictMappingEquivalent, + getTypedDictMembersForClass, + getTypeOfIndexedTypedDict, + synthesizeTypedDictClassMethods, +} from './typedDicts'; +import { + AbstractSymbol, + Arg, + ArgResult, + ArgWithExpression, + AssignTypeFlags, + CallResult, + CallSignature, + CallSignatureInfo, + CallSiteEvaluationInfo, + ClassMemberLookup, + ClassTypeResult, + DeclaredSymbolTypeInfo, + EffectiveTypeResult, + ensureExpectedTypeCandidates, + EvalFlags, + EvaluatorUsage, + ExpectedTypeOptions, + ExpectedTypeResult, + FunctionTypeResult, + MagicMethodDeprecationInfo, + MapSubtypesOptions, + maxInferredContainerDepth, + maxSubtypesForInferredType, + MemberAccessDeprecationInfo, + PrefetchedTypes, + PrintTypeOptions, + Reachability, + ResolveAliasOptions, + SolveConstraintsOptions, + SymbolDeclInfo, + TypeEvaluator, + TypeResult, + TypeResultWithNode, + ValidateArgTypeParams, + ValidateTypeArgsOptions, +} from './typeEvaluatorTypes'; +import { enumerateLiteralsForType } from './typeGuards'; +import * as TypePrinter from './typePrinter'; +import { + AnyType, + ClassType, + ClassTypeFlags, + combineTypes, + DataClassBehaviors, + EnumLiteral, + findSubtype, + FunctionParam, + FunctionParamFlags, + FunctionType, + FunctionTypeFlags, + InheritanceChain, + isAny, + isAnyOrUnknown, + isClass, + isClassInstance, + isFunction, + isFunctionOrOverloaded, + isInstantiableClass, + isMethodType, + isModule, + isNever, + isCDefineType, + isCArrayType, + isCPtrType, + isCVoidType, + getCPtrLevel, + isCConstType, + isCStaticType, isCModifierType, - isPureCModifierType, - isCEnumType, - isCEnumSubclassType, - isCTypeSubclassType, - canImplicitlyConvertCType, - isViperIntegerType, - isViperCIntegerTypeName, - isViperCFloatTypeName, - isOverloaded, - isParamSpec, - isPositionOnlySeparator, - isTypeSame, - isTypeVar, - isTypeVarTuple, - isUnbound, - isUnion, - isUnknown, - isUnpacked, - isUnpackedClass, - isUnpackedTypeVarTuple, - LiteralValue, - maxTypeRecursionCount, - ModuleType, - NeverType, - OverloadedType, - ParamSpecType, - removeFromUnion, - removeUnbound, - SentinelLiteral, - TupleTypeArg, - Type, - TypeAliasInfo, - TypeBase, - TypeCategory, - TypeCondition, - TypedDictEntries, - TypeVarKind, - TypeVarScopeId, - TypeVarScopeType, - TypeVarTupleType, - TypeVarType, - UnboundType, - UnionType, - UnknownType, - Variance, -} from './types'; -import { - addConditionToType, - addTypeVarsToListIfUnique, - applySolvedTypeVars, - ApplyTypeVarOptions, - areTypesSame, - buildSolutionFromSpecializedClass, - ClassMember, - combineSameSizedTuples, - combineTupleTypeArgs, - combineVariances, - computeMroLinearization, - containsAnyOrUnknown, - containsLiteralType, - convertToInstance, - convertToInstantiable, - convertTypeToParamSpecValue, - derivesFromAnyOrUnknown, - derivesFromClassRecursive, - derivesFromStdlibClass, - doForEachSubtype, - ensureSignaturesAreUnique, - explodeGenericClass, - getContainerDepth, - getDeclaredGeneratorReturnType, - getGeneratorTypeArgs, - getGeneratorYieldType, - getSpecializedTupleType, - getTypeCondition, - getTypeVarArgsRecursive, - getTypeVarScopeId, - getTypeVarScopeIds, - getUnknownForTypeVar, - getUnknownTypeForCallable, - InferenceContext, - invertVariance, - isDescriptorInstance, - isEffectivelyInstantiable, - isEllipsisType, - isIncompleteUnknown, - isInstantiableMetaclass, - isLiteralLikeType, - isLiteralType, - isMaybeDescriptorInstance, - isMemberReadOnly, - isMetaclassInstance, - isNoneInstance, - isNoneTypeClass, - isOptionalType, - isPartlyUnknown, - isProperty, - isSentinelLiteral, - isTupleClass, - isTupleIndexUnambiguous, - isTypeAliasPlaceholder, - isTypeAliasRecursive, - isTypeVarSame, - isUnboundedTupleClass, - isVarianceOfTypeArgCompatible, - lookUpClassMember, - lookUpObjectMember, - makeFunctionTypeVarsBound, - makeInferenceContext, - makePacked, - makeTypeVarsBound, - makeTypeVarsFree, - mapSignatures, - mapSubtypes, - MemberAccessFlags, - partiallySpecializeType, - preserveUnknown, - removeNoneFromUnion, - requiresSpecialization, - requiresTypeArgs, - selfSpecializeClass, - simplifyFunctionToParamSpec, - someSubtypes, - sortTypes, - specializeForBaseClass, - specializeTupleClass, - specializeWithDefaultTypeArgs, - specializeWithUnknownTypeArgs, - stripTypeForm, - stripTypeFormRecursive, - synthesizeTypeVarForSelfCls, - transformExpectedType, - transformPossibleRecursiveTypeAlias, - UniqueSignatureTracker, - validateTypeVarDefault, -} from './typeUtils'; - -interface GetTypeArgsOptions { - isAnnotatedClass?: boolean; - hasCustomClassGetItem?: boolean; - isFinalAnnotation?: boolean; - isClassVarAnnotation?: boolean; - supportsTypedDictTypeArg?: boolean; -} - -interface MatchArgsToParamsResult { - overload: FunctionType; - overloadIndex: number; - - argumentErrors: boolean; - isTypeIncomplete: boolean; - argParams: ValidateArgTypeParams[]; - activeParam?: FunctionParam | undefined; - paramSpecTarget?: ParamSpecType | undefined; - paramSpecArgList?: Arg[] | undefined; - - // Was there an unpacked argument of unknown length? - unpackedArgOfUnknownLength?: boolean; - - // Did that unpacked argument map to a variadic parameter? - unpackedArgMapsToVariadic?: boolean; - - // A score that indicates how well the overload matches with - // supplied arguments. Used to pick the "best" for purposes - // of error reporting when no matches are found. The higher - // the score, the worse the match. - argumentMatchScore: number; -} - -export interface MemberAccessTypeResult { - type: Type; - isDescriptorApplied?: boolean; - isAsymmetricAccessor?: boolean; - memberAccessDeprecationInfo?: MemberAccessDeprecationInfo; - typeErrors?: boolean; -} - -interface ScopedTypeVarResult { - type: TypeVarType; - scopeNode: TypeParameterScopeNode | AssignmentNode | undefined; - foundInterveningClass: boolean; -} - -interface AliasMapEntry { - alias: string; - module: 'builtins' | 'collections' | 'internals'; - implicitBaseClass?: string; - isSpecialForm?: boolean; - isIllegalInIsinstance?: boolean; - typeParamVariance?: Variance; -} - -interface AssignClassToSelfInfo { - class: ClassType; - assumedVariance: Variance; -} - -interface MatchedOverloadInfo { - overload: FunctionType; - matchResults: MatchArgsToParamsResult; - constraints: ConstraintTracker; - argResults: ArgResult[]; - returnType: Type; -} - -interface ValidateArgTypeOptions { - skipUnknownArgCheck?: boolean; - isArgFirstPass?: boolean; - conditionFilter?: TypeCondition[]; - skipReportError?: boolean; -} - -interface EffectiveReturnTypeOptions { - callSiteInfo?: CallSiteEvaluationInfo; -} - -interface SignatureTrackerStackEntry { - tracker: UniqueSignatureTracker; - rootNode: ParseNode; -} - -// This table contains the names of several built-in types that -// are not subscriptable at runtime on older versions of Python. -// It lists the first version of Python where subscripting is -// allowed. -const nonSubscriptableBuiltinTypes: Map = new Map([ - ['asyncio.futures.Future', pythonVersion3_9], - ['asyncio.tasks.Task', pythonVersion3_9], - ['builtins.dict', pythonVersion3_9], - ['builtins.frozenset', pythonVersion3_9], - ['builtins.list', pythonVersion3_9], - ['builtins._PathLike', pythonVersion3_9], - ['builtins.set', pythonVersion3_9], - ['builtins.tuple', pythonVersion3_9], - ['collections.ChainMap', pythonVersion3_9], - ['collections.Counter', pythonVersion3_9], - ['collections.defaultdict', pythonVersion3_9], - ['collections.DefaultDict', pythonVersion3_9], - ['collections.deque', pythonVersion3_9], - ['collections.OrderedDict', pythonVersion3_9], - ['queue.Queue', pythonVersion3_9], -]); - -// Some types that do not inherit from others are still considered -// "compatible" based on the Python spec. These are sometimes referred -// to as "type promotions". -const typePromotions: Map = new Map([ - ['builtins.float', ['builtins.int']], - ['builtins.complex', ['builtins.float', 'builtins.int']], - ['builtins.bytes', ['builtins.bytearray', 'builtins.memoryview']], -]); - -interface SymbolResolutionStackEntry { - // The symbol ID and declaration being resolved. - symbolId: number; - declaration: Declaration; - - // Initially true, it's set to false if a recursion - // is detected. - isResultValid: boolean; - - // Some limited forms of recursion are allowed. In these - // cases, a partially-constructed type can be registered. - partialType?: Type | undefined; -} - -interface ReturnTypeInferenceContext { - functionNode: FunctionNode; - codeFlowAnalyzer: CodeFlowAnalyzer; -} - -interface ParamSpecArgResult { - argumentErrors: boolean; - constraintTrackers: (ConstraintTracker | undefined)[]; -} - -// How many levels deep should we attempt to infer return -// types based on call-site argument types? The deeper we go, -// the more types we may be able to infer, but the worse the -// performance. -const maxReturnTypeInferenceStackSize = 2; - -// What is the max number of input arguments we should allow -// for call-site return type inference? We've found that large, -// complex functions with many arguments can take too long to -// analyze. -const maxReturnTypeInferenceArgCount = 6; - -// What is the max complexity of the code flow graph that -// we will analyze to determine the return type of a function -// when its parameters are unannotated? We want to keep this -// pretty low because this can be very costly. -const maxReturnTypeInferenceCodeFlowComplexity = 32; - -// What is the max complexity of the code flow graph for -// call-site type inference? This is very expensive, so we -// want to keep this very low. -const maxReturnCallSiteTypeInferenceCodeFlowComplexity = 8; - -// What is the max number of return types cached per function -// when using call-site inference? -const maxCallSiteReturnTypeCacheSize = 8; - -// How many entries in a list, set, or dict should we examine -// when inferring the type? We need to cut it off at some point -// to avoid excessive computation. -const maxEntriesToUseForInference = 64; - -// How many times should attempt to infer a return type of a -// function before giving up and assuming that it won't converge -// due to recursion? -const maxReturnTypeInferenceAttempts = 8; - -// How many assignments to an unannotated variable should be used -// when inferring its type? We need to cut it off at some point -// to avoid excessive computation. -const maxDeclarationsToUseForInference = 64; - -// Maximum number of times to attempt effective type evaluation -// of a variable that has no type declaration. -const maxEffectiveTypeEvaluationAttempts = 16; - -// Maximum number of combinatoric argument type expansions allowed -// when resolving an overload. -const maxTotalOverloadArgTypeExpansionCount = 256; - -// Maximum size of an enum that will be expanded during overload -// argument type expansion. -const maxSingleOverloadArgTypeExpansionCount = 64; - -// Maximum number of recursive function return type inference attempts -// that can be concurrently pending before we give up. -const maxInferFunctionReturnRecursionCount = 12; - -// Maximum recursion amount when comparing two recursive type aliases. -// Increasing this can greatly increase the time required to evaluate -// two recursive type aliases that have the same definition. Decreasing -// it can increase the chance of false negatives for such recursive -// type aliases. -const maxRecursiveTypeAliasRecursionCount = 10; - -// Normally a symbol can have only one type declaration, but there are -// cases where multiple are possible (e.g. a property with a setter -// and a deleter). In extreme cases, we need to limit the number of -// type declarations we consider to avoid excessive computation. -const maxTypedDeclsPerSymbol = 16; - -// This switch enables a special debug mode that attempts to catch -// bugs due to inconsistent evaluation flags used when reading types -// from the type cache. -const verifyTypeCacheEvaluatorFlags = false; - -// This debugging option prints each expression and its evaluated type. -const printExpressionTypes = false; - -// The following number is chosen somewhat arbitrarily. We need to cut -// off code flow analysis at some point for code flow graphs that are too -// complex. Otherwise we risk overflowing the stack or incurring extremely -// long analysis times. This number has been tuned empirically. -export const maxCodeComplexity = 768; - -export interface EvaluatorOptions { - printTypeFlags: TypePrinter.PrintTypeFlags; - logCalls: boolean; - minimumLoggingThreshold: number; - evaluateUnknownImportsAsAny: boolean; - verifyTypeCacheEvaluatorFlags: boolean; -} - -// Describes a "deferred class completion" that is run when a class type is -// fully created and the "PartiallyEvaluated" flag has just been cleared. -// This allows us to properly compute information like the MRO which -// depends on a full understanding of base classes. -interface DeferredClassCompletion { - dependsUpon: ClassType; - classesToComplete: ClassNode[]; -} - -interface TypeCacheEntry { - typeResult: TypeResult; - incompleteGenCount: number; - flags: EvalFlags | undefined; -} - -interface CodeFlowAnalyzerCacheEntry { - typeAtStart: TypeResult | undefined; - codeFlowAnalyzer: CodeFlowAnalyzer; -} - -interface FunctionRecursionInfo { - callerNode: ExpressionNode | undefined; -} - -type LogWrapper = any>(func: T) => (...args: Parameters) => ReturnType; - -interface SuppressedNodeStackEntry { - node: ParseNode; - suppressedDiags: string[] | undefined; -} - -interface ExpectedTypeCacheEntry { - type: Type; - candidates: Type[]; -} - -export function createTypeEvaluator( - importLookup: ImportLookup, - evaluatorOptions: EvaluatorOptions, - wrapWithLogger: LogWrapper -): TypeEvaluator { - const symbolResolutionStack: SymbolResolutionStackEntry[] = []; - const speculativeTypeTracker = new SpeculativeTypeTracker(); - const suppressedNodeStack: SuppressedNodeStackEntry[] = []; - const assignClassToSelfStack: AssignClassToSelfInfo[] = []; - - let functionRecursionMap = new Map(); - let codeFlowAnalyzerCache = new Map(); - let typeCache = new Map(); - let effectiveTypeCache = new Map>(); - let expectedTypeCache = new Map(); - let asymmetricAccessorAssignmentCache = new Set(); - let deferredClassCompletions: DeferredClassCompletion[] = []; - let cancellationToken: CancellationToken | undefined; - let printExpressionSpaceCount = 0; - let incompleteGenCount = 0; - const returnTypeInferenceContextStack: ReturnTypeInferenceContext[] = []; - let returnTypeInferenceTypeCache: Map | undefined; - const signatureTrackerStack: SignatureTrackerStackEntry[] = []; - let prefetched: Partial | undefined; - - function runWithCancellationToken(token: CancellationToken, callback: () => T): T; - function runWithCancellationToken(token: CancellationToken, callback: () => Promise): Promise; - function runWithCancellationToken(token: CancellationToken, callback: () => T | Promise): T | Promise { - // Save the current token and restore it after the callback to support nested calls - const oldToken = cancellationToken; - let result: T | Promise | undefined = undefined; - try { - cancellationToken = token; - result = callback(); - - if (!isThenable(result)) { - return result; - } - - return result.finally(() => { - cancellationToken = oldToken; - }); - } finally { - if (!isThenable(result)) { - cancellationToken = oldToken; - } - } - } - - function checkForCancellation() { - if (cancellationToken) { - throwIfCancellationRequested(cancellationToken); - } - } - - function getTypeCacheEntryCount(): number { - return typeCache.size; - } - - // This function should be called immediately prior to discarding - // the type evaluator. It forcibly replaces existing cache maps - // with empty equivalents. This shouldn't be necessary, but there - // is apparently a bug in the v8 GC where it is unable to detect - // circular references in complex data structures, so it fails - // to clean up the objects if we don't help it out. - function disposeEvaluator() { - functionRecursionMap = new Map(); - codeFlowAnalyzerCache = new Map(); - typeCache = new Map(); - effectiveTypeCache = new Map>(); - expectedTypeCache = new Map(); - asymmetricAccessorAssignmentCache = new Set(); - } - - function readTypeCacheEntry(node: ParseNode) { - // Should we use a temporary cache associated with a contextual - // analysis of a function, contextualized based on call-site argument types? - if (returnTypeInferenceTypeCache && isNodeInReturnTypeInferenceContext(node)) { - return returnTypeInferenceTypeCache.get(node.id); - } else { - return typeCache.get(node.id); - } - } - - function isTypeCached(node: ParseNode) { - const cacheEntry = readTypeCacheEntry(node); - if (!cacheEntry) { - return false; - } - - return !cacheEntry.typeResult.isIncomplete || cacheEntry.incompleteGenCount === incompleteGenCount; - } - - function readTypeCache(node: ParseNode, flags: EvalFlags | undefined): Type | undefined { - const cacheEntry = readTypeCacheEntry(node); - if (!cacheEntry || cacheEntry.typeResult.isIncomplete) { - return undefined; - } - - if (evaluatorOptions.verifyTypeCacheEvaluatorFlags || verifyTypeCacheEvaluatorFlags) { - if (flags !== undefined) { - const expectedFlags = cacheEntry.flags; - - if (expectedFlags !== undefined && flags !== expectedFlags) { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - const position = convertOffsetToPosition(node.start, fileInfo.lines); - - const message = - `Type cache flag mismatch for node type ${node.nodeType} ` + - `(parent ${node.parent?.nodeType ?? 'none'}): ` + - `cached flags = ${expectedFlags}, access flags = ${flags}, ` + - `file = {${fileInfo.fileUri} [${position.line + 1}:${position.character + 1}]}`; - if (evaluatorOptions.verifyTypeCacheEvaluatorFlags) { - fail(message); - } else { - console.log(message); - } - } - } - } - - return cacheEntry.typeResult.type; - } - - function writeTypeCache( - node: ParseNode, - typeResult: TypeResult, - flags: EvalFlags | undefined, - inferenceContext?: InferenceContext, - allowSpeculativeCaching = false - ) { - // Should we use a temporary cache associated with a contextual - // analysis of a function, contextualized based on call-site argument types? - const typeCacheToUse = - returnTypeInferenceTypeCache && isNodeInReturnTypeInferenceContext(node) - ? returnTypeInferenceTypeCache - : typeCache; - - if (!typeResult.isIncomplete) { - incompleteGenCount++; - } else { - const oldValue = typeCacheToUse.get(node.id); - if (oldValue !== undefined && !isTypeSame(typeResult.type, oldValue.typeResult.type)) { - incompleteGenCount++; - } - } - - typeCacheToUse.set(node.id, { typeResult, flags, incompleteGenCount }); - - // If the entry is located within a part of the parse tree that is currently being - // "speculatively" evaluated, track it so we delete the cached entry when we leave - // this speculative context. - if (isSpeculativeModeInUse(node)) { - speculativeTypeTracker.trackEntry(typeCacheToUse, node.id); - if (allowSpeculativeCaching) { - speculativeTypeTracker.addSpeculativeType( - node, - typeResult, - incompleteGenCount, - inferenceContext?.expectedType - ); - } - } - } - - function setTypeResultForNode(node: ParseNode, typeResult: TypeResult, flags = EvalFlags.None) { - writeTypeCache(node, typeResult, flags); - } - - function setAsymmetricDescriptorAssignment(node: ParseNode) { - if (isSpeculativeModeInUse(/* node */ undefined)) { - return; - } - - asymmetricAccessorAssignmentCache.add(node.id); - } - - function isAsymmetricAccessorAssignment(node: ParseNode) { - return asymmetricAccessorAssignmentCache.has(node.id); - } - - // Determines whether the specified node is contained within - // the function node corresponding to the function that we - // are currently analyzing in the context of parameter types - // defined by a call site. - function isNodeInReturnTypeInferenceContext(node: ParseNode) { - const stackSize = returnTypeInferenceContextStack.length; - if (stackSize === 0) { - return false; - } - - const contextNode = returnTypeInferenceContextStack[stackSize - 1]; - - let curNode: ParseNode | undefined = node; - while (curNode) { - if (curNode === contextNode.functionNode) { - return true; - } - curNode = curNode.parent; - } - - return false; - } - - function getCodeFlowAnalyzerForReturnTypeInferenceContext() { - const stackSize = returnTypeInferenceContextStack.length; - assert(stackSize > 0); - const contextNode = returnTypeInferenceContextStack[stackSize - 1]; - return contextNode.codeFlowAnalyzer; - } - - function getIndexOfSymbolResolution(symbol: Symbol, declaration: Declaration) { - return symbolResolutionStack.findIndex( - (entry) => entry.symbolId === symbol.id && entry.declaration === declaration - ); - } - - function pushSymbolResolution(symbol: Symbol, declaration: Declaration) { - const index = getIndexOfSymbolResolution(symbol, declaration); - if (index >= 0) { - // Mark all of the entries between these two as invalid. - for (let i = index + 1; i < symbolResolutionStack.length; i++) { - symbolResolutionStack[i].isResultValid = false; - } - return false; - } - - symbolResolutionStack.push({ - symbolId: symbol.id, - declaration, - isResultValid: true, - }); - return true; - } - - function popSymbolResolution(symbol: Symbol) { - const poppedEntry = symbolResolutionStack.pop(); - assert( - poppedEntry !== undefined && poppedEntry.symbolId === symbol.id, - `Symbol resolution stack mismatch: expected symbol ${symbol.id}, got ${ - poppedEntry?.symbolId ?? 'empty stack' - }` - ); - return poppedEntry.isResultValid; - } - - function setSymbolResolutionPartialType(symbol: Symbol, declaration: Declaration, type: Type) { - const index = getIndexOfSymbolResolution(symbol, declaration); - if (index >= 0) { - symbolResolutionStack[index].partialType = type; - } - } - - function getSymbolResolutionPartialType(symbol: Symbol, declaration: Declaration): Type | undefined { - const index = getIndexOfSymbolResolution(symbol, declaration); - if (index >= 0) { - return symbolResolutionStack[index].partialType; - } - - return undefined; - } - - // Determines the type of the specified node by evaluating it in - // context, logging any errors in the process. This may require the - // type of surrounding statements to be evaluated. - function getType(node: ExpressionNode): Type | undefined { - initializePrefetchedTypes(node); - - let type = evaluateTypeForSubnode(node, () => { - evaluateTypesForExpressionInContext(node); - })?.type; - - // If this is a type parameter with a calculated variance, see if we - // can swap it out for a version that has a computed variance. - if (type && isTypeVar(type) && type.shared.declaredVariance === Variance.Auto) { - const typeVarType = type; - const typeParamListNode = ParseTreeUtils.getParentNodeOfType( - node, - ParseNodeType.TypeParameterList - ); - - if (typeParamListNode?.parent?.nodeType === ParseNodeType.Class) { - const classTypeResult = getTypeOfClass(typeParamListNode.parent); - - if (classTypeResult) { - inferVarianceForClass(classTypeResult.classType); - - const typeParam = classTypeResult.classType.shared.typeParams.find((param) => - isTypeSame(param, typeVarType, { ignoreTypeFlags: true }) - ); - - if (typeParam?.priv.computedVariance !== undefined) { - type = TypeVarType.cloneWithComputedVariance(type, typeParam.priv.computedVariance); - } - } - } else if (typeParamListNode?.parent?.nodeType === ParseNodeType.TypeAlias) { - const typeAliasType = getTypeOfTypeAlias(typeParamListNode.parent); - const typeParamIndex = typeParamListNode.d.params.findIndex((param) => param.d.name === node); - - if (typeParamIndex >= 0) { - inferVarianceForTypeAlias(typeAliasType); - - const typeAliasInfo = typeAliasType.props?.typeAliasInfo; - if (typeAliasInfo?.shared.computedVariance) { - const computedVariance = typeAliasInfo.shared.computedVariance[typeParamIndex]; - - type = TypeVarType.cloneWithComputedVariance(type, computedVariance); - } - } - } - } - - if (type) { - type = transformPossibleRecursiveTypeAlias(type); - } - - return type; - } - - function getTypeResult(node: ExpressionNode): TypeResult | undefined { - return evaluateTypeForSubnode(node, () => { - evaluateTypesForExpressionInContext(node); - }); - } - - function getTypeResultForDecorator(node: DecoratorNode): TypeResult | undefined { - return evaluateTypeForSubnode(node, () => { - evaluateTypesForExpressionInContext(node.d.expr); - }); - } - - // Reads the type of the node from the cache. - function getCachedType(node: ExpressionNode | DecoratorNode): Type | undefined { - return readTypeCache(node, EvalFlags.None); - } - - // Determines the expected type of a specified node based on surrounding - // context. For example, if it's a subexpression of an argument expression, - // the associated parameter type might inform the expected type. - function getExpectedType(node: ExpressionNode): ExpectedTypeResult | undefined { - // This is a primary entry point called by language server providers, - // and it might be called before any other type evaluation has occurred. - // Use this opportunity to do some initialization. - initializePrefetchedTypes(node); - - // Scan up the parse tree to find the top-most expression node - // so we can evaluate the entire expression. - let topExpression = node; - let curNode: ParseNode | undefined = node; - while (curNode) { - if (isExpressionNode(curNode)) { - topExpression = curNode; - } - - curNode = curNode.parent; - } - - // Evaluate the expression. This will have the side effect of - // storing an expected type in the expected type cache. - evaluateTypesForExpressionInContext(topExpression); - - // Look for the resulting expected type by scanning up the parse tree. - curNode = node; - while (curNode) { - const expectedType = expectedTypeCache.get(curNode.id); - if (expectedType) { - return { - type: expectedType.type, - node: curNode, - candidates: ensureExpectedTypeCandidates(expectedType.type, expectedType.candidates), - }; - } - - if (curNode === topExpression) { - break; - } - - curNode = curNode.parent; - } - - return undefined; - } - - function addExpectedTypeCacheEntry(node: ParseNode, expectedType: Type) { - const cached = expectedTypeCache.get(node.id); - if (!cached) { - expectedTypeCache.set(node.id, { - type: expectedType, - candidates: [expectedType], - }); - return; - } - - cached.type = expectedType; - if (!cached.candidates.some((candidate) => isTypeSame(candidate, expectedType))) { - cached.candidates.push(expectedType); - } - } - - function initializePrefetchedTypes(node: ParseNode) { - if (!prefetched) { - // Some of these types have cyclical dependencies on each other, - // so don't re-enter this block once we start executing it. - prefetched = {}; - - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - - prefetched.objectClass = getBuiltInType(node, 'object'); - prefetched.typeClass = getBuiltInType(node, 'type'); - prefetched.functionClass = getTypesType(node, 'FunctionType') ?? getBuiltInType(node, 'function'); - prefetched.methodClass = getTypesType(node, 'MethodType'); - - prefetched.unionTypeClass = getTypesType(node, 'UnionType'); - if (prefetched.unionTypeClass && isClass(prefetched.unionTypeClass)) { - prefetched.unionTypeClass.shared.flags |= ClassTypeFlags.SpecialFormClass; - } - - // Initialize and cache "Collection" to break a cyclical dependency - // that occurs when resolving tuple below. - getTypingType(node, 'Collection'); - - prefetched.noneTypeClass = getTypeshedType(node, 'NoneType') ?? UnknownType.create(); - prefetched.tupleClass = getBuiltInType(node, 'tuple'); - prefetched.boolClass = getBuiltInType(node, 'bool'); - prefetched.intClass = getBuiltInType(node, 'int'); - prefetched.strClass = getBuiltInType(node, 'str'); - prefetched.dictClass = getBuiltInType(node, 'dict'); - prefetched.moduleTypeClass = getTypingType(node, 'ModuleType'); - prefetched.typedDictPrivateClass = - getTypeCheckerInternalsType(node, 'TypedDictFallback') ?? getTypingType(node, '_TypedDict'); - prefetched.typedDictClass = getTypingType(node, 'TypedDict'); - prefetched.awaitableClass = getTypingType(node, 'Awaitable'); - prefetched.mappingClass = getTypingType(node, 'Mapping'); - - // Don't attempt to resolve the string.templatelib if viper is configured for - // Python 3.13 or older. Doing so will either fail to resolve (if running on Python 3.13 - // or older) or resolve to the templatelib.py source file (if running on Python 3.14). - if (PythonVersion.isGreaterOrEqualTo(fileInfo.executionEnvironment.pythonVersion, pythonVersion3_14)) { - prefetched.templateClass = getTypeOfModule(node, 'Template', ['string', 'templatelib']); - } else { - prefetched.templateClass = UnknownType.create(); - } - - prefetched.supportsKeysAndGetItemClass = getTypeshedType(node, 'SupportsKeysAndGetItem'); - if (!prefetched.supportsKeysAndGetItemClass) { - // Fall back on 'Mapping' if 'SupportsKeysAndGetItem' is not available. - prefetched.supportsKeysAndGetItemClass = prefetched.mappingClass; - } - - // Wire up the `Any` class to the special-form version of our internal AnyType. - if ( - prefetched.objectClass && - isInstantiableClass(prefetched.objectClass) && - prefetched.typeClass && - isInstantiableClass(prefetched.typeClass) - ) { - const anyClass = ClassType.createInstantiable( - 'Any', - 'typing.Any', - 'typing', - Uri.empty(), - ClassTypeFlags.BuiltIn | ClassTypeFlags.SpecialFormClass | ClassTypeFlags.IllegalIsinstanceClass, - /* typeSourceId */ -1, - /* declaredMetaclass */ undefined, - /* effectiveMetaclass */ prefetched.typeClass - ); - anyClass.shared.baseClasses.push(prefetched.objectClass); - computeMroLinearization(anyClass); - const anySpecialForm = AnyType.createSpecialForm(); - - if (isAny(anySpecialForm)) { - TypeBase.setSpecialForm(anySpecialForm, anyClass); - TypeBase.setTypeForm(anySpecialForm, convertToInstance(anySpecialForm)); - } - } - } - } - - function getTypeOfExpression( - node: ExpressionNode, - flags = EvalFlags.None, - inferenceContext?: InferenceContext - ): TypeResult { - // Is this type already cached? - const cacheEntry = readTypeCacheEntry(node); - if (cacheEntry) { - if (!cacheEntry.typeResult.isIncomplete || cacheEntry.incompleteGenCount === incompleteGenCount) { - if (printExpressionTypes) { - console.log( - `${getPrintExpressionTypesSpaces()}${ParseTreeUtils.printExpression(node)} (${getLineNum( - node - )}): Cached ${printType(cacheEntry.typeResult.type)} ${ - cacheEntry.typeResult.typeErrors ? ' Errors' : '' - }` - ); - } - - return cacheEntry.typeResult; - } - } - - // Is it cached in the speculative type cache? - const specCacheEntry = speculativeTypeTracker.getSpeculativeType(node, inferenceContext?.expectedType); - if (specCacheEntry) { - if ( - !specCacheEntry.typeResult.isIncomplete || - specCacheEntry.incompleteGenerationCount === incompleteGenCount - ) { - if (printExpressionTypes) { - console.log( - `${getPrintExpressionTypesSpaces()}${ParseTreeUtils.printExpression(node)} (${getLineNum( - node - )}): Speculative ${printType(specCacheEntry.typeResult.type)}` - ); - } - - return specCacheEntry.typeResult; - } - } - - if (printExpressionTypes) { - console.log( - `${getPrintExpressionTypesSpaces()}${ParseTreeUtils.printExpression(node)} (${getLineNum(node)}): Pre` - ); - printExpressionSpaceCount++; - } - - // This is a frequently-called routine, so it's a good place to call - // the cancellation check. If the operation is canceled, an exception - // will be thrown at this point. - checkForCancellation(); - - if (inferenceContext) { - inferenceContext.expectedType = transformPossibleRecursiveTypeAlias(inferenceContext.expectedType); - } - - // If we haven't already fetched some core type definitions from the - // typeshed stubs, do so here. It would be better to fetch this when it's - // needed in assignType, but we don't have access to the parse tree - // at that point. - initializePrefetchedTypes(node); - - let typeResult = getTypeOfExpressionCore(node, flags, inferenceContext); - - // Should we disable type promotions for bytes? - if ( - isInstantiableClass(typeResult.type) && - typeResult.type.priv.includePromotions && - !typeResult.type.priv.includeSubclasses && - ClassType.isBuiltIn(typeResult.type, 'bytes') - ) { - if (AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.disableBytesTypePromotions) { - typeResult = { - ...typeResult, - type: ClassType.cloneRemoveTypePromotions(typeResult.type), - }; - } - } - - if (inferenceContext) { - // Handle TypeForm assignments. - typeResult.type = convertToTypeFormType(inferenceContext.expectedType, typeResult.type); - } - - // Don't allow speculative caching for assignment expressions because - // the target name node won't have a corresponding type cached speculatively. - const allowSpeculativeCaching = node.nodeType !== ParseNodeType.AssignmentExpression; - - writeTypeCache(node, typeResult, flags, inferenceContext, allowSpeculativeCaching); - - if (node.nodeType === ParseNodeType.Name || node.nodeType === ParseNodeType.MemberAccess) { - // If this is a generic function and there is a signature tracker, - // make sure the signature is unique. - typeResult.type = ensureSignatureIsUnique(typeResult.type, node); - } - - // If there was an expected type, make sure that the result type is compatible. - if ( - inferenceContext && - !isAnyOrUnknown(inferenceContext.expectedType) && - !isNever(inferenceContext.expectedType) - ) { - addExpectedTypeCacheEntry(node, inferenceContext.expectedType); - - if (!typeResult.isIncomplete && !typeResult.expectedTypeDiagAddendum) { - const diag = new DiagnosticAddendum(); - - // Make sure the resulting type is assignable to the expected type. - if ( - !assignType( - inferenceContext.expectedType, - typeResult.type, - diag, - /* constraints */ undefined, - AssignTypeFlags.Default - ) - ) { - // Set the typeErrors to true, but first make a copy of the - // type result because the (non-error) version may already - // be cached. - typeResult = { ...typeResult, typeErrors: true }; - typeResult.expectedTypeDiagAddendum = diag; - diag.addTextRange(node); - } - } - } - - if (printExpressionTypes) { - printExpressionSpaceCount--; - console.log( - `${getPrintExpressionTypesSpaces()}${ParseTreeUtils.printExpression(node)} (${getLineNum( - node - )}): Post ${printType(typeResult.type)}${typeResult.isIncomplete ? ' Incomplete' : ''}` - ); - } - - return typeResult; - } - - // This is a helper function that implements the core of getTypeOfExpression. - function getTypeOfExpressionCore( - node: ExpressionNode, - flags = EvalFlags.None, - inferenceContext?: InferenceContext - ): TypeResult { - let typeResult: TypeResult | undefined; - let expectingInstantiable = (flags & EvalFlags.InstantiableType) !== 0; - - switch (node.nodeType) { - case ParseNodeType.Name: { - typeResult = getTypeOfName(node, flags); - break; - } - - case ParseNodeType.MemberAccess: { - typeResult = getTypeOfMemberAccess(node, flags); - break; - } - - case ParseNodeType.Index: { - typeResult = getTypeOfIndex(node, flags); - break; - } - - case ParseNodeType.Call: { - typeResult = useSignatureTracker(node, () => getTypeOfCall(node, flags, inferenceContext)); - break; - } - - case ParseNodeType.Tuple: { - typeResult = getTypeOfTuple(evaluatorInterface, node, flags, inferenceContext); - break; - } - - case ParseNodeType.Constant: { - typeResult = getTypeOfConstant(node, flags); - break; - } - - case ParseNodeType.StringList: { - if ((flags & EvalFlags.StrLiteralAsType) !== 0) { - // Don't report expecting type errors again. We will have already - // reported them when analyzing the contents of the string. - expectingInstantiable = false; - } - - typeResult = getTypeOfStringList(node, flags, inferenceContext); - break; - } - - case ParseNodeType.Number: { - typeResult = getTypeOfNumber(node, typeResult); - break; - } - - case ParseNodeType.Ellipsis: { - typeResult = getTypeOfEllipsis(flags, typeResult, node); - break; - } - - case ParseNodeType.UnaryOperation: { - typeResult = getTypeOfUnaryOperation(evaluatorInterface, node, flags, inferenceContext); - break; - } - - case ParseNodeType.BinaryOperation: { - let effectiveFlags = flags; - - // If we're expecting an instantiable type and this isn't a union operator, - // don't require that the two operands are also instantiable types. - if (expectingInstantiable && node.d.operator !== OperatorType.BitwiseOr) { - effectiveFlags &= ~EvalFlags.InstantiableType; - } - - typeResult = getTypeOfBinaryOperation(evaluatorInterface, node, effectiveFlags, inferenceContext); - break; - } - - case ParseNodeType.AugmentedAssignment: { - typeResult = getTypeOfAugmentedAssignment(evaluatorInterface, node, inferenceContext); - break; - } - - case ParseNodeType.List: - case ParseNodeType.Set: { - typeResult = getTypeOfListOrSet(node, flags, inferenceContext); - break; - } - - case ParseNodeType.Slice: { - typeResult = getTypeOfSlice(node); - break; - } - - case ParseNodeType.Await: { - typeResult = getTypeOfAwaitOperator(node, flags, inferenceContext); - break; - } - - case ParseNodeType.Ternary: { - typeResult = getTypeOfTernaryOperation(evaluatorInterface, node, flags, inferenceContext); - break; - } - - case ParseNodeType.Comprehension: { - typeResult = getTypeOfComprehension(node, flags, inferenceContext); - break; - } - - case ParseNodeType.Dictionary: { - typeResult = getTypeOfDictionary(node, flags, inferenceContext); - break; - } - - case ParseNodeType.Lambda: { - typeResult = getTypeOfLambda(node, inferenceContext); - break; - } - - case ParseNodeType.Assignment: { - typeResult = getTypeOfExpression(node.d.rightExpr, flags, inferenceContext); - assignTypeToExpression( - node.d.leftExpr, - typeResult, - node.d.rightExpr, - /* ignoreEmptyContainers */ true, - /* allowAssignmentToFinalVar */ true - ); - break; - } - - case ParseNodeType.AssignmentExpression: { - if ((flags & EvalFlags.TypeExpression) !== 0) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.walrusNotAllowed(), node); - } - - typeResult = getTypeOfExpression(node.d.rightExpr, flags, inferenceContext); - assignTypeToExpression(node.d.name, typeResult, node.d.rightExpr, /* ignoreEmptyContainers */ true); - break; - } - - case ParseNodeType.Yield: { - typeResult = getTypeOfYield(node); - break; - } - - case ParseNodeType.YieldFrom: { - typeResult = getTypeOfYieldFrom(node); - break; - } - - case ParseNodeType.Unpack: { - typeResult = getTypeOfUnpackOperator(node, flags, inferenceContext); - break; - } - - case ParseNodeType.TypeAnnotation: { - typeResult = getTypeOfExpression( - node.d.annotation, - EvalFlags.InstantiableType | - EvalFlags.TypeExpression | - EvalFlags.StrLiteralAsType | - EvalFlags.NoParamSpec | - EvalFlags.NoTypeVarTuple | - EvalFlags.VarTypeAnnotation - ); - break; - } - - case ParseNodeType.String: - case ParseNodeType.FormatString: { - typeResult = getTypeOfString(node); - break; - } - - case ParseNodeType.Error: { - // Evaluate the child expression as best we can so the - // type information is cached for the completion handler. - suppressDiagnostics(node, () => { - if (node.d.child) { - getTypeOfExpression(node.d.child); - } - }); - typeResult = { type: UnknownType.create() }; - break; - } - - default: - assertNever(node, `Illegal node type: ${(node as any).nodeType}`); - } - - if (!typeResult) { - // We shouldn't get here. If we do, report an error. - fail(`Unhandled expression type '${ParseTreeUtils.printExpression(node)}'`); - } - - // Do we need to validate that the type is instantiable? - if (expectingInstantiable) { - validateTypeIsInstantiable(typeResult, flags, node); - } - - // If this is a PEP 695 type alias, remove the special form so the type - // printer prints it as its aliased type rather than TypeAliasType. - if ((flags & EvalFlags.TypeExpression) !== 0 && typeResult.type.props?.typeForm === undefined) { - const specialForm = typeResult.type.props?.specialForm; - if (specialForm && ClassType.isBuiltIn(specialForm, 'TypeAliasType')) { - typeResult.type = TypeBase.cloneAsSpecialForm(typeResult.type, undefined); - } - } - - return typeResult; - } - - // Reports the case where a function or class has been decorated with - // @type_check_only and is used in a value expression. - function reportUseOfTypeCheckOnly(type: Type, node: ExpressionNode) { - let isTypeCheckingOnly = false; - let name = ''; - - if (isInstantiableClass(type) && !type.priv.includeSubclasses) { - isTypeCheckingOnly = ClassType.isTypeCheckOnly(type); - name = type.shared.name; - } else if (isFunction(type)) { - isTypeCheckingOnly = FunctionType.isTypeCheckOnly(type); - name = type.shared.name; - } - - if (isTypeCheckingOnly) { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - - if (!fileInfo.isStubFile) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeCheckOnly().format({ name }), - node - ); - } - } - } - - function validateTypeIsInstantiable(typeResult: TypeResult, flags: EvalFlags, node: ExpressionNode) { - // If the type is incomplete, don't log any diagnostics yet. - if (typeResult.isIncomplete) { - return; - } - - if ((flags & EvalFlags.NoTypeVarTuple) !== 0) { - if (isTypeVarTuple(typeResult.type) && !typeResult.type.priv.isInUnion) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeVarTupleContext(), node); - typeResult.type = UnknownType.create(); - } - } - - if (isEffectivelyInstantiable(typeResult.type, { honorTypeVarBounds: true })) { - return; - } - - // Exempt ellipses. - if (isClassInstance(typeResult.type) && ClassType.isBuiltIn(typeResult.type, ['EllipsisType', 'ellipsis'])) { - return; - } - - // Viper 扩展:t.CDefine 常量豁免可实例化检查。 - // CDefine 是编译时常量,在类型下标中作为参数使用时 - // (如 CArray[int_slot, UHCI_INT_SLOTS]),不应要求其可实例化。 - if (isCDefineType(typeResult.type)) { - return; - } - - // Viper 扩展:整数字面量豁免可实例化检查。 - // Viper 中 CArray[T, N] 的 N 是数组长度(整数字面量), - // 如 CArray[t.CUInt32T, 6],不应要求整数可实例化。 - if ( - isClassInstance(typeResult.type) && - isLiteralType(typeResult.type) && - IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect) - ) { - return; - } - - // Viper 扩展:Viper 是 AOT 静态语言,typedef/TypeSet 等类型别名 - // 在跨模块 import 后可能被求值为类实例(如 int)而非类本身。 - // 在 Viper 模式下,豁免所有类型的可实例化检查。 - if (IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect)) { - return; - } - - // Emit these errors only if we know we're evaluating a type expression. - if ((flags & EvalFlags.TypeExpression) !== 0) { - const diag = new DiagnosticAddendum(); - if (isUnion(typeResult.type)) { - doForEachSubtype(typeResult.type, (subtype) => { - if (!isEffectivelyInstantiable(subtype, { honorTypeVarBounds: true })) { - diag.addMessage(LocAddendum.typeNotClass().format({ type: printType(subtype) })); - } - }); - } - - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeExpectedClass().format({ type: printType(typeResult.type) }) + diag.getString(), - node - ); - - typeResult.type = UnknownType.create(); - } - - typeResult.typeErrors = true; - } - - function getTypeOfAwaitOperator(node: AwaitNode, flags: EvalFlags, inferenceContext?: InferenceContext) { - if ((flags & EvalFlags.TypeExpression) !== 0) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.awaitNotAllowed(), node); - return { type: UnknownType.create() }; - } - - const expectedType = inferenceContext - ? createAwaitableReturnType( - node, - inferenceContext.expectedType, - /* isGenerator */ false, - /* useCoroutine */ false - ) - : undefined; - - const exprTypeResult = getTypeOfExpression(node.d.expr, flags, makeInferenceContext(expectedType)); - const awaitableResult = getTypeOfAwaitable(exprTypeResult, node.d.expr); - const typeResult: TypeResult = { - type: awaitableResult.type, - isIncomplete: exprTypeResult.isIncomplete || awaitableResult.isIncomplete, - typeErrors: exprTypeResult.typeErrors, - }; - - if (exprTypeResult.isIncomplete) { - typeResult.isIncomplete = true; - } - return typeResult; - } - - function getTypeOfEllipsis(flags: EvalFlags, typeResult: TypeResult | undefined, node: ExpressionNode) { - if ((flags & EvalFlags.ConvertEllipsisToAny) !== 0) { - typeResult = { type: AnyType.create(/* isEllipsis */ true) }; - } else { - if ((flags & EvalFlags.TypeExpression) !== 0 && (flags & EvalFlags.AllowEllipsis) === 0) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.ellipsisContext(), node); - typeResult = { type: UnknownType.create() }; - } else { - const ellipsisType = - getBuiltInObject(node, 'EllipsisType') ?? getBuiltInObject(node, 'ellipsis') ?? AnyType.create(); - typeResult = { type: ellipsisType }; - } - } - return typeResult; - } - - function getTypeOfNumber(node: NumberNode, typeResult: TypeResult | undefined) { - if (node.d.isImaginary) { - typeResult = { type: getBuiltInObject(node, 'complex') }; - } else if (node.d.isInteger) { - typeResult = { type: cloneBuiltinObjectWithLiteral(node, 'int', node.d.value) }; - } else { - typeResult = { type: getBuiltInObject(node, 'float') }; - } - return typeResult; - } - - function getTypeOfUnpackOperator(node: UnpackNode, flags: EvalFlags, inferenceContext?: InferenceContext) { - let typeResult: TypeResult | undefined; - let iterExpectedType: Type | undefined; - - if (inferenceContext) { - const iterableType = getBuiltInType(node, 'Iterable'); - if (iterableType && isInstantiableClass(iterableType)) { - iterExpectedType = ClassType.cloneAsInstance( - ClassType.specialize(iterableType, [inferenceContext.expectedType]) - ); - } - } - - const iterTypeResult = getTypeOfExpression(node.d.expr, flags, makeInferenceContext(iterExpectedType)); - const iterType = iterTypeResult.type; - if ((flags & EvalFlags.NoTypeVarTuple) === 0 && isTypeVarTuple(iterType) && !iterType.priv.isUnpacked) { - typeResult = { type: TypeVarType.cloneForUnpacked(iterType) }; - } else if ( - (flags & EvalFlags.AllowUnpackedTuple) !== 0 && - isInstantiableClass(iterType) && - ClassType.isBuiltIn(iterType, 'tuple') - ) { - typeResult = { type: ClassType.cloneForUnpacked(iterType) }; - } else if ((flags & EvalFlags.TypeExpression) !== 0) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.unpackInAnnotation(), - node, - node.d.starToken - ); - typeResult = { type: UnknownType.create() }; - } else { - const iteratorTypeResult = getTypeOfIterator(iterTypeResult, /* isAsync */ false, node) ?? { - type: UnknownType.create(!!iterTypeResult.isIncomplete), - isIncomplete: iterTypeResult.isIncomplete, - }; - typeResult = { - type: iteratorTypeResult.type, - typeErrors: iterTypeResult.typeErrors, - unpackedType: iterType, - isIncomplete: iteratorTypeResult.isIncomplete, - }; - } - - return typeResult; - } - - function getTypeOfStringList( - node: StringListNode, - flags: EvalFlags, - inferenceContext?: InferenceContext - ): TypeResult { - let typeResult: TypeResult | undefined; - - if ((flags & EvalFlags.StrLiteralAsType) !== 0 && (flags & EvalFlags.TypeFormArg) === 0) { - return getTypeOfStringListAsType(node, flags); - } - - const isBytesNode = (node: StringNode | FormatStringNode) => - (node.d.token.flags & StringTokenFlags.Bytes) !== 0; - - // Check for mixing of bytes and str, which is not allowed. - const firstStrIndex = node.d.strings.findIndex((str) => !isBytesNode(str)); - const firstBytesIndex = node.d.strings.findIndex((str) => isBytesNode(str)); - if (firstStrIndex >= 0 && firstBytesIndex >= 0) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.mixingBytesAndStr(), - node.d.strings[Math.max(firstBytesIndex, firstStrIndex)] - ); - - return { type: UnknownType.create() }; - } - - const isBytes = firstBytesIndex >= 0; - let isLiteralString = true; - let isIncomplete = false; - let isTemplate = false; - - node.d.strings.forEach((expr) => { - // Handle implicit concatenation. - const typeResult = getTypeOfString(expr); - - if (typeResult.isIncomplete) { - isIncomplete = true; - } - - let isExprLiteralString = false; - - if (isClassInstance(typeResult.type)) { - if (ClassType.isBuiltIn(typeResult.type, 'str') && typeResult.type.priv.literalValue !== undefined) { - isExprLiteralString = true; - } else if (ClassType.isBuiltIn(typeResult?.type, 'LiteralString')) { - isExprLiteralString = true; - } - - if (typeResult.type.shared.name === 'Template') { - isTemplate = true; - } - } - - if (!isExprLiteralString) { - isLiteralString = false; - } - }); - - if (isTemplate) { - const templateType = - prefetched?.templateClass && isInstantiableClass(prefetched?.templateClass) - ? ClassType.cloneAsInstance(prefetched.templateClass) - : UnknownType.create(); - - typeResult = { type: templateType, isIncomplete }; - } else if (node.d.strings.some((str) => str.nodeType === ParseNodeType.FormatString)) { - if (isLiteralString) { - const literalStringType = getTypingType(node, 'LiteralString'); - if (literalStringType && isInstantiableClass(literalStringType)) { - typeResult = { type: ClassType.cloneAsInstance(literalStringType) }; - } - } - - if (!typeResult) { - typeResult = { - type: getBuiltInObject(node, isBytes ? 'bytes' : 'str'), - isIncomplete, - }; - } - } else { - typeResult = { - type: cloneBuiltinObjectWithLiteral( - node, - isBytes ? 'bytes' : 'str', - node.d.strings.map((s) => s.d.value).join('') - ), - isIncomplete, - }; - } - - if (node.d.strings.length !== 1 || node.d.strings[0].nodeType !== ParseNodeType.String) { - return typeResult; - } - - // Only attempt to interpret the string as a TypeForm forward reference when - // there's a signal that a TypeForm value is wanted in this context. Doing it - // unconditionally can trigger expensive (and recursion-prone) type lookups - // for plain string literals in non-type contexts. - const wantsTypeForm = - (flags & EvalFlags.TypeFormArg) !== 0 || - (inferenceContext !== undefined && expectedTypeWantsTypeForm(inferenceContext.expectedType)); - if (!wantsTypeForm) { - return typeResult; - } - - // For performance reasons, do not attempt to treat the string literal - // as a TypeForm if it's going to fail anyway or is unlikely to be a - // TypeForm (really long, triple-quoted, etc.). - const stringNode = node.d.strings[0]; - const tokenFlags = stringNode.d.token.flags; - const disallowedTokenFlags = - StringTokenFlags.Bytes | - StringTokenFlags.Raw | - StringTokenFlags.Format | - StringTokenFlags.Template | - StringTokenFlags.Triplicate; - const maxTypeFormStringLength = 256; - - if ( - (tokenFlags & disallowedTokenFlags) !== 0 || - stringNode.d.token.escapedValue.length >= maxTypeFormStringLength - ) { - return typeResult; - } - - const typeFormResult = getTypeOfStringListAsType(node, flags); - if (typeFormResult.type.props?.typeForm) { - typeResult.type = TypeBase.cloneWithTypeForm(typeResult.type, typeFormResult.type.props.typeForm); - } - - return typeResult; - } - - function getTypeOfStringListAsType(node: StringListNode, flags: EvalFlags): TypeResult { - const reportTypeErrors = (flags & EvalFlags.StrLiteralAsType) !== 0; - let updatedFlags = flags | EvalFlags.ForwardRefs | EvalFlags.InstantiableType; - let typeResult: TypeResult | undefined; - - // In most cases, annotations within a string are not parsed by the interpreter. - // There are a few exceptions (e.g. the "bound" value for a TypeVar constructor). - if ((flags & EvalFlags.ParsesStringLiteral) === 0) { - updatedFlags |= EvalFlags.NotParsed; - } - - updatedFlags &= ~EvalFlags.TypeFormArg; - - if (node.d.annotation && (flags & EvalFlags.TypeExpression) !== 0) { - return getTypeOfExpression(node.d.annotation, updatedFlags); - } - - if (node.d.strings.length === 1) { - const tokenFlags = node.d.strings[0].d.token.flags; - - if (tokenFlags & StringTokenFlags.Bytes) { - if (reportTypeErrors) { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.annotationBytesString(), node); - } - return { type: UnknownType.create() }; - } - - if (tokenFlags & StringTokenFlags.Raw) { - if (reportTypeErrors) { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.annotationRawString(), node); - } - return { type: UnknownType.create() }; - } - - if (tokenFlags & StringTokenFlags.Format) { - if (reportTypeErrors) { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.annotationFormatString(), node); - } - return { type: UnknownType.create() }; - } - - if (tokenFlags & StringTokenFlags.Template) { - if (reportTypeErrors) { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.annotationTemplateString(), node); - } - return { type: UnknownType.create() }; - } - - // We didn't know at parse time that this string node was going - // to be evaluated as a forward-referenced type. We need - // to re-invoke the parser at this stage. - const expr = parseStringAsTypeAnnotation(node, reportTypeErrors); - if (expr) { - typeResult = useSpeculativeMode(reportTypeErrors ? undefined : node, () => { - return getTypeOfExpression(expr, updatedFlags); - }); - } - } - - if (!typeResult) { - if (reportTypeErrors) { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.expectedTypeNotString(), node); - } - typeResult = { type: UnknownType.create() }; - } - - return typeResult; - } - - function getTypeOfString(node: StringNode | FormatStringNode): TypeResult { - const isBytes = (node.d.token.flags & StringTokenFlags.Bytes) !== 0; - let typeResult: TypeResult | undefined; - let isIncomplete = false; - - if (node.nodeType === ParseNodeType.String) { - typeResult = { - type: cloneBuiltinObjectWithLiteral(node, isBytes ? 'bytes' : 'str', node.d.value), - isIncomplete, - }; - } else { - const isTemplateString = (node.d.token.flags & StringTokenFlags.Template) !== 0; - let isLiteralString = true; - - // If all of the format expressions are of type LiteralString, then - // the resulting formatted string is also LiteralString. - node.d.fieldExprs.forEach((expr) => { - const exprTypeResult = getTypeOfExpression(expr); - const exprType = exprTypeResult.type; - - if (exprTypeResult.isIncomplete) { - isIncomplete = true; - } - - doForEachSubtype(exprType, (exprSubtype) => { - if (!isClassInstance(exprSubtype)) { - isLiteralString = false; - return; - } - - if (ClassType.isBuiltIn(exprSubtype, 'LiteralString')) { - return; - } - - if (ClassType.isBuiltIn(exprSubtype, 'str') && exprSubtype.priv.literalValue !== undefined) { - return; - } - - isLiteralString = false; - }); - }); - - if (isTemplateString) { - const templateType = - prefetched?.templateClass && isInstantiableClass(prefetched?.templateClass) - ? ClassType.cloneAsInstance(prefetched.templateClass) - : UnknownType.create(); - - typeResult = { type: templateType, isIncomplete }; - } else if (!isBytes && isLiteralString) { - const literalStringType = getTypingType(node, 'LiteralString'); - if (literalStringType && isInstantiableClass(literalStringType)) { - typeResult = { type: ClassType.cloneAsInstance(literalStringType), isIncomplete }; - } - } - - if (!typeResult) { - typeResult = { - type: getBuiltInObject(node, isBytes ? 'bytes' : 'str'), - isIncomplete, - }; - - if (isClass(typeResult.type) && typeResult.type.priv.includePromotions) { - typeResult.type = ClassType.cloneRemoveTypePromotions(typeResult.type); - } - } - } - - return typeResult; - } - - function stripLiteralValue(type: Type): Type { - // Handle the not-uncommon case where the type is a union that consists - // only of literal values. - if (isUnion(type) && type.priv.subtypes.length > 0) { - if ( - type.priv.literalInstances.literalStrMap?.size === type.priv.subtypes.length || - type.priv.literalInstances.literalIntMap?.size === type.priv.subtypes.length || - type.priv.literalInstances.literalEnumMap?.size === type.priv.subtypes.length - ) { - return stripLiteralValue(type.priv.subtypes[0]); - } - } - - return mapSubtypes(type, (subtype) => { - if (isClass(subtype)) { - if (subtype.priv.literalValue !== undefined) { - subtype = ClassType.cloneWithLiteral(subtype, /* value */ undefined); - } - - if (ClassType.isBuiltIn(subtype, 'LiteralString')) { - // Handle "LiteralString" specially. - if (prefetched?.strClass && isInstantiableClass(prefetched.strClass)) { - let strInstance = ClassType.cloneAsInstance(prefetched.strClass); - strInstance = TypeBase.cloneForCondition(strInstance, getTypeCondition(subtype)); - return strInstance; - } - } - } - - return subtype; - }); - } - - function getTypeOfParamAnnotation(paramTypeNode: ExpressionNode, paramCategory: ParamCategory) { - return getTypeOfAnnotation(paramTypeNode, { - typeVarGetsCurScope: true, - allowUnpackedTuple: paramCategory === ParamCategory.ArgsList, - allowUnpackedTypedDict: paramCategory === ParamCategory.KwargsDict, - }); - } - - function getTypeOfAnnotation(node: ExpressionNode, options?: ExpectedTypeOptions): Type { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - - // Special-case the typing.pyi file, which contains some special - // types that the type analyzer needs to interpret differently. - if (fileInfo.isTypingStubFile || fileInfo.isTypingExtensionsStubFile) { - const specialType = handleTypingStubTypeAnnotation(node); - if (specialType) { - return specialType; - } - } - - const adjustedOptions: ExpectedTypeOptions = options ? { ...options } : {}; - - adjustedOptions.typeExpression = true; - adjustedOptions.convertEllipsisToAny = true; - - // If the annotation is part of a comment, allow forward references - // even if it's not enclosed in quotes. - if (node?.parent?.nodeType === ParseNodeType.Assignment && node.parent.d.annotationComment === node) { - adjustedOptions.forwardRefs = true; - adjustedOptions.notParsed = true; - } else if (node?.parent?.nodeType === ParseNodeType.FunctionAnnotation) { - if (node.parent.d.returnAnnotation === node || node.parent.d.paramAnnotations.some((n) => n === node)) { - adjustedOptions.forwardRefs = true; - adjustedOptions.notParsed = true; - } - } else if (node?.parent?.nodeType === ParseNodeType.Parameter) { - if (node.parent.d.annotationComment === node) { - adjustedOptions.forwardRefs = true; - adjustedOptions.notParsed = true; - } - } - - const annotationType = getTypeOfExpressionExpectingType(node, adjustedOptions).type; - - if (isModule(annotationType)) { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.moduleAsType(), node); - } - - return convertToInstance(annotationType); - } - - function canBeFalsy(type: Type, recursionCount = 0): boolean { - type = makeTopLevelTypeVarsConcrete(type); - - if (recursionCount > maxTypeRecursionCount) { - return true; - } - recursionCount++; - - switch (type.category) { - case TypeCategory.Unbound: - case TypeCategory.Unknown: - case TypeCategory.Any: - case TypeCategory.Never: { - return true; - } - - case TypeCategory.Union: { - return findSubtype(type, (subtype) => canBeFalsy(subtype, recursionCount)) !== undefined; - } - - case TypeCategory.Function: - case TypeCategory.Overloaded: - case TypeCategory.Module: - case TypeCategory.TypeVar: { - return false; - } - - case TypeCategory.Class: { - if (TypeBase.isInstantiable(type)) { - return false; - } - - // Viper 扩展:CPtr 指针类型可为 NULL(falsy)。 - // 允许 if sp: / if ptr: 的假分支可达,避免 slab_page | CPtr 这类联合的 - // 假分支被误判为 Never(如 free() 中 sp 为 NULL 指针的路径)。 - if (isCPtrType(type)) { - return true; - } - - // Viper 扩展:CType 整数/浮点子类可为 falsy(值为 0)。 - // CInt/CUInt32T/CFloat 等在底层等价于 int/float,值 0 为 falsy。 - // 这些类型是独立 class(非 built-in int/float),不匹配上方 2128 的 - // isBuiltIn(type, ['int']) 检查,也不匹配末尾 2178 的 isBuiltIn(object), - // 导致 canBeFalsy 默认返回 false(认为总是 truthy)。 - // 这使得 in_corner_zone: t.CInt = 0 后 if not in_corner_zone: 的真分支 - // 被误判为不可达(CInt 被认为恒 truthy)。此处让整数/浮点 CType 子类 - // 返回 true(可为 0/falsy),使条件分支的真假分支均可达。 - if ( - isCTypeSubclassType(type) && - (isViperCIntegerTypeName(type.shared.name) || - isViperCFloatTypeName(type.shared.name)) - ) { - return true; - } - - // Sentinels are always truthy. - if (isSentinelLiteral(type)) { - return false; - } - - // Handle tuples specially. - if (isTupleClass(type) && type.priv.tupleTypeArgs) { - return isUnboundedTupleClass(type) || type.priv.tupleTypeArgs.length === 0; - } - - // Handle subclasses of tuple, such as NamedTuple. - const tupleBaseClass = type.shared.mro.find((mroClass) => !isClass(mroClass) || isTupleClass(mroClass)); - if (tupleBaseClass && isClass(tupleBaseClass) && tupleBaseClass.priv.tupleTypeArgs) { - return isUnboundedTupleClass(tupleBaseClass) || tupleBaseClass.priv.tupleTypeArgs.length === 0; - } - - // Handle TypedDicts specially. If one or more entries are required - // or known to exist, we can say for sure that the type is not falsy. - if (ClassType.isTypedDictClass(type)) { - const tdEntries = getTypedDictMembersForClass(evaluatorInterface, type, /* allowNarrowed */ true); - if (tdEntries) { - for (const tdEntry of tdEntries.knownItems.values()) { - if (tdEntry.isRequired || tdEntry.isProvided) { - return false; - } - } - } - } - - // Check for bool, int, str and bytes literals that are never falsy. - if (type.priv.literalValue !== undefined) { - if (ClassType.isBuiltIn(type, ['bool', 'int', 'str', 'bytes'])) { - return !type.priv.literalValue || type.priv.literalValue === BigInt(0); - } - - if (type.priv.literalValue instanceof EnumLiteral) { - // Does the Enum class forward the truthiness check to the - // underlying member type? - if (type.priv.literalValue.isReprEnum) { - return canBeFalsy(type.priv.literalValue.itemType, recursionCount); - } - } - } - - // If this is a protocol class, don't make any assumptions about the absence - // of specific methods. These could be provided by a class that conforms - // to the protocol. - if (ClassType.isProtocolClass(type)) { - return true; - } - - const lenMethod = lookUpObjectMember(type, '__len__'); - if (lenMethod) { - return true; - } - - const boolMethod = lookUpObjectMember(type, '__bool__'); - if (boolMethod) { - const boolMethodType = getTypeOfMember(boolMethod); - - // If the __bool__ function unconditionally returns True, it can never be falsy. - if (isFunction(boolMethodType) && boolMethodType.shared.declaredReturnType) { - const returnType = boolMethodType.shared.declaredReturnType; - if ( - isClassInstance(returnType) && - ClassType.isBuiltIn(returnType, 'bool') && - returnType.priv.literalValue === true - ) { - return false; - } - } - - return true; - } - - // If the class is not final, it's possible that it could be overridden - // such that it is falsy. To be fully correct, we'd need to do the - // following: - // return !ClassType.isFinal(type); - // However, pragmatically if the class is not an `object`, it's typically - // OK to assume that it will not be overridden in this manner. - return ClassType.isBuiltIn(type, 'object'); - } - } - } - - function canBeTruthy(type: Type, recursionCount = 0): boolean { - type = makeTopLevelTypeVarsConcrete(type); - - if (recursionCount > maxTypeRecursionCount) { - return true; - } - recursionCount++; - - switch (type.category) { - case TypeCategory.Unknown: - case TypeCategory.Function: - case TypeCategory.Overloaded: - case TypeCategory.Module: - case TypeCategory.TypeVar: - case TypeCategory.Never: - case TypeCategory.Any: { - return true; - } - - case TypeCategory.Union: { - return findSubtype(type, (subtype) => canBeTruthy(subtype, recursionCount)) !== undefined; - } - - case TypeCategory.Unbound: { - return false; - } - - case TypeCategory.Class: { - if (TypeBase.isInstantiable(type)) { - return true; - } - - if (isNoneInstance(type)) { - return false; - } - - // // Check for tuple[()] (an empty tuple). - if (type.priv.tupleTypeArgs && type.priv.tupleTypeArgs.length === 0) { - return false; - } - - // Check for bool, int, str and bytes literals that are never falsy. - if (type.priv.literalValue !== undefined) { - if (ClassType.isBuiltIn(type, ['bool', 'int', 'str', 'bytes'])) { - return !!type.priv.literalValue && type.priv.literalValue !== BigInt(0); - } - - if (type.priv.literalValue instanceof EnumLiteral) { - // Does the Enum class forward the truthiness check to the - // underlying member type? - if (type.priv.literalValue.isReprEnum) { - return canBeTruthy(type.priv.literalValue.itemType, recursionCount); - } - } - } - - // If this is a protocol class, don't make any assumptions about the absence - // of specific methods. These could be provided by a class that conforms - // to the protocol. - if (ClassType.isProtocolClass(type)) { - return true; - } - - const boolMethod = lookUpObjectMember(type, '__bool__'); - if (boolMethod) { - const boolMethodType = getTypeOfMember(boolMethod); - - // If the __bool__ function unconditionally returns False, it can never be truthy. - if (isFunction(boolMethodType) && boolMethodType.shared.declaredReturnType) { - const returnType = boolMethodType.shared.declaredReturnType; - if ( - isClassInstance(returnType) && - ClassType.isBuiltIn(returnType, 'bool') && - returnType.priv.literalValue === false - ) { - return false; - } - } - } - - return true; - } - } - } - - // Filters a type such that that no part of it is definitely - // truthy. For example, if a type is a union of None - // and a custom class "Foo" that has no __len__ or __nonzero__ - // method, this method would strip off the "Foo" - // and return only the "None". - function removeTruthinessFromType(type: Type): Type { - return mapSubtypes(type, (subtype) => { - const concreteSubtype = makeTopLevelTypeVarsConcrete(subtype); - - if (isClassInstance(concreteSubtype)) { - if (concreteSubtype.priv.literalValue !== undefined) { - let isLiteralFalsy: boolean; - - if (concreteSubtype.priv.literalValue instanceof EnumLiteral) { - isLiteralFalsy = !canBeTruthy(concreteSubtype); - } else { - isLiteralFalsy = !concreteSubtype.priv.literalValue; - } - - // If the object is already definitely falsy, it's fine to - // include, otherwise it should be removed. - return isLiteralFalsy ? subtype : undefined; - } - - // If the object is a sentinel, we can eliminate it. - if (isSentinelLiteral(concreteSubtype)) { - return undefined; - } - - // If the object is a bool, make it "false", since - // "true" is a truthy value. - if (ClassType.isBuiltIn(concreteSubtype, 'bool')) { - return ClassType.cloneWithLiteral(concreteSubtype, /* value */ false); - } - - // If the object is an int, str or bytes, narrow to a literal type. - // This is slightly unsafe in that someone could subclass `int`, `str` - // or `bytes` and override the `__bool__` method to change its behavior, - // but this is extremely unlikely (and ill advised). - if (ClassType.isBuiltIn(concreteSubtype, 'int')) { - return ClassType.cloneWithLiteral(concreteSubtype, /* value */ 0); - } else if (ClassType.isBuiltIn(concreteSubtype, ['str', 'bytes'])) { - return ClassType.cloneWithLiteral(concreteSubtype, /* value */ ''); - } - } - - // If it's possible for the type to be falsy, include it. - if (canBeFalsy(subtype)) { - return subtype; - } - - return undefined; - }); - } - - // Filters a type such that that no part of it is definitely - // falsy. For example, if a type is a union of None - // and an "int", this method would strip off the "None" - // and return only the "int". - function removeFalsinessFromType(type: Type): Type { - return mapSubtypes(type, (subtype) => { - const concreteSubtype = makeTopLevelTypeVarsConcrete(subtype); - - if (isClassInstance(concreteSubtype)) { - if (concreteSubtype.priv.literalValue !== undefined) { - let isLiteralTruthy: boolean; - - if (concreteSubtype.priv.literalValue instanceof EnumLiteral) { - isLiteralTruthy = !canBeFalsy(concreteSubtype); - } else if (concreteSubtype.priv.literalValue instanceof SentinelLiteral) { - isLiteralTruthy = true; - } else { - isLiteralTruthy = !!concreteSubtype.priv.literalValue; - } - - // If the object is already definitely truthy, it's fine to - // include, otherwise it should be removed. - return isLiteralTruthy ? subtype : undefined; - } - - // If the object is a bool, make it "true", since - // "false" is a falsy value. - if (ClassType.isBuiltIn(concreteSubtype, 'bool')) { - return ClassType.cloneWithLiteral(concreteSubtype, /* value */ true); - } - - // If the object is a "None" instance, we can eliminate it. - if (isNoneInstance(concreteSubtype)) { - return undefined; - } - - // If this is an instance of a class that cannot be subclassed, - // we cannot say definitively that it's not falsy because a subclass - // could override `__bool__`. For this reason, the code should not - // remove any classes that are not final. - // if (!ClassType.isFinal(concreteSubtype)) { - // return subtype; - // } - // However, we're going to pragmatically assume that any classes - // other than `object` will not be overridden in this manner. - if (ClassType.isBuiltIn(concreteSubtype, 'object')) { - return subtype; - } - } - - // If it's possible for the type to be truthy, include it. - if (canBeTruthy(subtype)) { - return subtype; - } - - return undefined; - }); - } - - // If a type contains a TypeGuard or TypeIs, convert it to a bool. - function stripTypeGuard(type: Type): Type { - return mapSubtypes(type, (subtype) => { - if (isClassInstance(subtype) && ClassType.isBuiltIn(subtype, ['TypeGuard', 'TypeIs'])) { - return prefetched?.boolClass ? convertToInstance(prefetched.boolClass) : UnknownType.create(); - } - - return subtype; - }); - } - - function solveAndApplyConstraints( - type: Type, - constraints: ConstraintTracker, - applyOptions?: ApplyTypeVarOptions, - solveOptions?: SolveConstraintsOptions - ): Type { - const solution = solveConstraints(evaluatorInterface, constraints, solveOptions); - return applySolvedTypeVars(type, solution, applyOptions); - } - - // Gets a member type from an object or class. If it's a function, binds - // it to the object or class. If selfType is undefined, the binding is done - // using the objectType parameter. Callers can specify these separately - // to handle the case where we're fetching the object member from a - // metaclass but binding to the class. - function getTypeOfBoundMember( - errorNode: ExpressionNode | undefined, - objectType: ClassType, - memberName: string, - usage: EvaluatorUsage = { method: 'get' }, - diag: DiagnosticAddendum | undefined = undefined, - flags = MemberAccessFlags.Default, - selfType?: ClassType | TypeVarType, - recursionCount = 0 - ): TypeResult | undefined { - if (ClassType.isPartiallyEvaluated(objectType)) { - if (errorNode) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.classDefinitionCycle().format({ name: objectType.shared.name }), - errorNode - ); - } - return { type: UnknownType.create() }; - } - - // If this is an unspecialized generic class, specialize it using the default - // values for its type parameters. - if ( - isInstantiableClass(objectType) && - !objectType.priv.includeSubclasses && - objectType.shared.typeParams.length > 0 - ) { - // Skip this if we're suppressing the use of attribute access override, - // such as with dundered methods (like __call__). - if ((flags & MemberAccessFlags.SkipAttributeAccessOverride) === 0) { - objectType = specializeWithDefaultTypeArgs(objectType); - } - } - - // Determine the class that was used to instantiate the objectType. - // If the objectType is a class itself, then the class used to instantiate - // it is the metaclass. - const objectTypeIsInstantiable = TypeBase.isInstantiable(objectType); - const metaclass = objectType.shared.effectiveMetaclass; - - let memberInfo: ClassMemberLookup | undefined; - - // If the object type is an instantiable (i.e. it derives from "type") and - // we've been asked not to consider instance members, don't look in the class. - // Consider only the metaclass class variables in this case. - let skipObjectTypeLookup = objectTypeIsInstantiable && (flags & MemberAccessFlags.SkipInstanceMembers) !== 0; - - // Look up the attribute in the metaclass first. If the member is a descriptor - // (an object with a __get__ and __set__ method) and the access is a 'get', - // the Python runtime uses this descriptor to satisfy the lookup. Skip this - // costly lookup in the common case where the metaclass is 'type' since we know - // that `type` doesn't have any attributes that are descriptors. - if ( - usage.method === 'get' && - objectTypeIsInstantiable && - metaclass && - isInstantiableClass(metaclass) && - !ClassType.isBuiltIn(metaclass, 'type') && - !ClassType.isSameGenericClass(metaclass, objectType) - ) { - const descMemberInfo = getTypeOfClassMemberName( - errorNode, - metaclass, - memberName, - usage, - /* diag */ undefined, - flags | MemberAccessFlags.SkipAttributeAccessOverride | MemberAccessFlags.SkipTypedDictEntries, - objectType, - recursionCount - ); - - if (descMemberInfo) { - const isProperty = - isClassInstance(descMemberInfo.type) && ClassType.isPropertyClass(descMemberInfo.type); - if (isDescriptorInstance(descMemberInfo.type, /* requireSetter */ true) || isProperty) { - skipObjectTypeLookup = true; - } - } - } - - let subDiag: DiagnosticAddendum | undefined; - - if (!skipObjectTypeLookup) { - let effectiveFlags = flags | MemberAccessFlags.SkipTypedDictEntries; - - if (objectTypeIsInstantiable) { - effectiveFlags |= - MemberAccessFlags.SkipInstanceMembers | - MemberAccessFlags.SkipAttributeAccessOverride | - MemberAccessFlags.DisallowGenericInstanceVariableAccess; - effectiveFlags &= ~MemberAccessFlags.SkipClassMembers; - } else { - effectiveFlags |= MemberAccessFlags.DisallowClassVarWrites; - } - - subDiag = diag ? new DiagnosticAddendum() : undefined; - - // See if the member is present in the object itself. - memberInfo = getTypeOfClassMemberName( - errorNode, - objectType, - memberName, - usage, - subDiag, - effectiveFlags, - selfType, - recursionCount - ); - } - - // If it wasn't found on the object, see if it's part of the metaclass. - if (!memberInfo && metaclass && isInstantiableClass(metaclass)) { - let effectiveFlags = flags; - - // Class members cannot be accessed on a class's metaclass through - // an instance of a class. Limit access to metaclass instance members - // in this case. - if (!objectTypeIsInstantiable) { - effectiveFlags |= - MemberAccessFlags.SkipClassMembers | - MemberAccessFlags.SkipAttributeAccessOverride | - MemberAccessFlags.SkipTypeBaseClass; - effectiveFlags &= ~MemberAccessFlags.SkipInstanceMembers; - } - - const metaclassDiag = diag ? new DiagnosticAddendum() : undefined; - memberInfo = getTypeOfClassMemberName( - errorNode, - ClassType.cloneAsInstance(metaclass), - memberName, - usage, - metaclassDiag, - effectiveFlags, - objectTypeIsInstantiable ? objectType : ClassType.cloneAsInstantiable(objectType), - recursionCount - ); - - // If there was a descriptor error (as opposed to an error where the members - // was simply not found), use this diagnostic message. - if (memberInfo?.isDescriptorError) { - subDiag = metaclassDiag; - } - } - - if (memberInfo) { - if (memberInfo.isDescriptorError && diag && subDiag) { - diag.addAddendum(subDiag); - } - - return { - type: memberInfo.type, - classType: memberInfo.classType, - isIncomplete: !!memberInfo.isTypeIncomplete, - isAsymmetricAccessor: memberInfo.isAsymmetricAccessor, - narrowedTypeForSet: memberInfo.narrowedTypeForSet, - memberAccessDeprecationInfo: memberInfo.memberAccessDeprecationInfo, - typeErrors: memberInfo.isDescriptorError, - }; - } - - // If this is a type[Any] or type[Unknown], allow any other members. - if ( - isClassInstance(objectType) && - ClassType.isBuiltIn(objectType, 'type') && - objectType.priv.includeSubclasses - ) { - if ((flags & (MemberAccessFlags.SkipTypeBaseClass | MemberAccessFlags.SkipAttributeAccessOverride)) === 0) { - const typeArg = - objectType.priv.typeArgs && objectType.priv.typeArgs.length >= 1 - ? objectType.priv.typeArgs[0] - : UnknownType.create(); - - if (isAnyOrUnknown(typeArg)) { - return { type: typeArg, classType: UnknownType.create() }; - } - } - } - - if (diag && subDiag) { - diag.addAddendum(subDiag); - } - - // Viper 扩展:`__before_init__` 是编译器自动生成的默认成员方法 - // (在 __init__ 之前自动填充成员默认值,见 TransPyC wiki/06-oop.md), - // 所有类都隐式存在,无需在源码中定义。因此当成员查找失败且目标 - // 成员名是 `__before_init__` 时,合成一个 `() -> None` 的方法, - // 避免报 "attribute is unknown"。同时该成员不允许手动定义,手动 - // 定义会在 checker 中报错。 - if ( - memberName === '__before_init__' && - isClass(objectType) && - errorNode && - IsViperDialect(AnalyzerNodeInfo.getFileInfo(errorNode).executionEnvironment.dialect) && - (flags & MemberAccessFlags.SkipAttributeAccessOverride) === 0 - ) { - // 合成 `() -> None` 方法。注意:成员访问 `obj.__before_init__()` - // 已绑定实例,因此签名不包含 self 参数(与 __getattr__ 等 - // 动态成员的绑定行为一致),避免调用时误报 "missing self"。 - const beforeInitType = FunctionType.createSynthesizedInstance('__before_init__'); - beforeInitType.shared.declaredReturnType = getNoneType(); - - return { - type: beforeInitType, - isIncomplete: false, - isAsymmetricAccessor: false, - }; - } - - return undefined; - } - - function getBoundMagicMethod( - classType: ClassType, - memberName: string, - selfType?: ClassType | TypeVarType | undefined, - errorNode?: ExpressionNode | undefined, - diag?: DiagnosticAddendum, - recursionCount = 0 - ): FunctionType | OverloadedType | undefined { - const boundMethodResult = getTypeOfBoundMember( - errorNode, - classType, - memberName, - /* usage */ undefined, - diag, - MemberAccessFlags.SkipInstanceMembers | MemberAccessFlags.SkipAttributeAccessOverride, - selfType, - recursionCount - ); - - if (!boundMethodResult || boundMethodResult.typeErrors) { - return undefined; - } - - if (isFunctionOrOverloaded(boundMethodResult.type)) { - return boundMethodResult.type; - } - - if (isClassInstance(boundMethodResult.type)) { - if (recursionCount > maxTypeRecursionCount) { - return undefined; - } - recursionCount++; - - return getBoundMagicMethod( - boundMethodResult.type, - '__call__', - /* selfType */ undefined, - errorNode, - diag, - recursionCount - ); - } - - if (isAnyOrUnknown(boundMethodResult.type)) { - return getUnknownTypeForCallable(); - } - - return undefined; - } - - // Returns the signature(s) associated with a call node that contains - // the specified node. It also returns the index of the argument - // that contains the node. - function getCallSignatureInfo( - callNode: CallNode, - activeIndex: number, - activeOrFake: boolean - ): CallSignatureInfo | undefined { - const exprNode = callNode.d.leftExpr; - const callType = getType(exprNode); - if (!callType) { - return undefined; - } - - const argList: Arg[] = []; - let previousCategory = ArgCategory.Simple; - - // Empty arguments do not enter the AST as nodes, but instead are left blank. - // Instead, we detect when we appear to be between two known arguments or at the - // end of the argument list and insert a fake argument of an unknown type to have - // something to match later. - function addFakeArg() { - argList.push({ - argCategory: previousCategory, - typeResult: { type: UnknownType.create() }, - active: true, - }); - } - - callNode.d.args.forEach((arg, index) => { - let active = false; - if (index === activeIndex) { - if (activeOrFake) { - active = true; - } else { - addFakeArg(); - } - } - - previousCategory = arg.d.argCategory; - - argList.push({ - valueExpression: arg.d.valueExpr, - argCategory: arg.d.argCategory, - name: arg.d.name, - active: active, - }); - }); - - if (callNode.d.args.length < activeIndex) { - addFakeArg(); - } - - let signatures: CallSignature[] = []; - - function addOneFunctionToSignature(type: FunctionType) { - let callResult: CallResult | undefined; - const constraints = new ConstraintTracker(); - - useSpeculativeMode(callNode, () => { - callResult = validateArgs( - exprNode, - argList, - { type }, - constraints, - /* skipUnknownArgCheck */ true, - /* inferenceContext */ undefined - ); - }); - - const specializedType = solveAndApplyConstraints(type, constraints); - const finalType = isFunction(specializedType) ? specializedType : type; - const hasActiveArg = argList.some((arg) => arg.active); - - // If the type was specialized (e.g. ParamSpec expansion), the activeParam - // from the original validateArgs refers to parameters in the unspecialized - // type and won't match the specialized type's parameters. Re-run validateArgs - // against the specialized type to get the correct activeParam mapping. - let activeParam = callResult?.activeParam; - if (hasActiveArg && finalType !== type) { - let specializedActiveParam: typeof activeParam | undefined; - useSpeculativeMode(callNode, () => { - const specializedResult = validateArgs( - exprNode, - argList, - { type: finalType }, - new ConstraintTracker(), - /* skipUnknownArgCheck */ true, - /* inferenceContext */ undefined - ); - specializedActiveParam = specializedResult?.activeParam; - }); - if (specializedActiveParam) { - activeParam = specializedActiveParam; - } - } - - signatures.push({ - type: expandTypedKwargs(finalType), - activeParam, - }); - } - - function addFunctionToSignature(type: FunctionType | OverloadedType) { - if (isFunction(type)) { - addOneFunctionToSignature(type); - } else { - OverloadedType.getOverloads(type).forEach((func) => { - addOneFunctionToSignature(func); - }); - } - } - - doForEachSubtype(callType, (subtype) => { - switch (subtype.category) { - case TypeCategory.Function: - case TypeCategory.Overloaded: { - addFunctionToSignature(subtype); - break; - } - - case TypeCategory.Class: { - if (TypeBase.isInstantiable(subtype)) { - const constructorType = createFunctionFromConstructor(evaluatorInterface, subtype); - - if (constructorType) { - doForEachSubtype(constructorType, (subtype) => { - if (isFunctionOrOverloaded(subtype)) { - addFunctionToSignature(subtype); - } - }); - - // It's common for either the `__new__` or `__init__` methods to be - // simple (*args: Any, **kwargs: Any) signatures. If so, we'll try - // to filter out these signatures if they add nothing of value. - const filteredSignatures = signatures.filter( - (sig) => - !FunctionType.isGradualCallableForm(sig.type) || - sig.type.shared.parameters.length > 2 || - sig.type.shared.docString || - sig.type.shared.deprecatedMessage - ); - - if (filteredSignatures.length > 0) { - signatures = filteredSignatures; - } - } - } else { - const methodType = getBoundMagicMethod(subtype, '__call__'); - if (methodType) { - addFunctionToSignature(methodType); - } - } - break; - } - } - }); - - if (signatures.length === 0) { - return undefined; - } - - return { callNode, signatures }; - } - - // If the function includes a `**kwargs: Unpack[TypedDict]` parameter, the - // parameter is expanded to include individual keyword args. - function expandTypedKwargs(functionType: FunctionType): FunctionType { - const kwargsIndex = functionType.shared.parameters.findIndex( - (param) => param.category === ParamCategory.KwargsDict - ); - if (kwargsIndex < 0) { - return functionType; - } - assert(kwargsIndex === functionType.shared.parameters.length - 1); - - const kwargsType = FunctionType.getParamType(functionType, kwargsIndex); - if (!isClassInstance(kwargsType) || !ClassType.isTypedDictClass(kwargsType) || !kwargsType.priv.isUnpacked) { - return functionType; - } - - const tdEntries = kwargsType.priv.typedDictNarrowedEntries ?? kwargsType.shared.typedDictEntries?.knownItems; - if (!tdEntries) { - return functionType; - } - - const newFunction = FunctionType.clone(functionType); - newFunction.shared.parameters.splice(kwargsIndex); - if (newFunction.priv.specializedTypes) { - newFunction.priv.specializedTypes.parameterTypes.splice(kwargsIndex); - } - - const kwSeparatorIndex = functionType.shared.parameters.findIndex( - (param) => param.category === ParamCategory.ArgsList - ); - - // Add a keyword separator if necessary. - if (kwSeparatorIndex < 0 && tdEntries.size > 0) { - FunctionType.addKeywordOnlyParamSeparator(newFunction); - } - - tdEntries.forEach((tdEntry, name) => { - // A TypedDict entry type may carry the TypedDict's own type parameters (e.g. an unpacked - // generic TypedDict `**kwargs: Unpack[TD[int]]`), so specialize each entry against the - // (possibly generic) TypedDict instance. Specializing an already-concrete type is a - // no-op, so this is safe for non-generic TypedDicts. - const specializedValueType = partiallySpecializeType( - tdEntry.valueType, - kwargsType, - /* typeClassType */ undefined - ); - - FunctionType.addParam( - newFunction, - FunctionParam.create( - ParamCategory.Simple, - specializedValueType, - FunctionParamFlags.TypeDeclared, - name, - tdEntry.isRequired ? undefined : specializedValueType - ) - ); - }); - - const extraItemsType = kwargsType.shared.typedDictEntries?.extraItems?.valueType; - - if (extraItemsType && !isNever(extraItemsType)) { - // Specialized for the same reason as the per-entry types above. This PEP 728 - // `extra_items` branch uses the identical mechanism and is intentionally not - // separately tested. - const specializedExtraItemsType = partiallySpecializeType( - extraItemsType, - kwargsType, - /* typeClassType */ undefined - ); - - FunctionType.addParam( - newFunction, - FunctionParam.create( - ParamCategory.KwargsDict, - specializedExtraItemsType, - FunctionParamFlags.TypeDeclared, - 'kwargs' - ) - ); - } - - return newFunction; - } - - // Determines whether the specified expression is an explicit TypeAlias declaration. - function isDeclaredTypeAlias(expression: ExpressionNode): boolean { - if (expression.nodeType === ParseNodeType.TypeAnnotation) { - if (expression.d.valueExpr.nodeType === ParseNodeType.Name) { - const symbolWithScope = lookUpSymbolRecursive( - expression, - expression.d.valueExpr.d.value, - /* honorCodeFlow */ false - ); - if (symbolWithScope) { - const symbol = symbolWithScope.symbol; - return symbol.getDeclarations().find((decl) => isExplicitTypeAliasDeclaration(decl)) !== undefined; - } - } - } - - return false; - } - - // Determines whether the specified expression is a symbol with a declared type. - function getDeclaredTypeForExpression(expression: ExpressionNode, usage?: EvaluatorUsage): Type | undefined { - let symbol: Symbol | undefined; - let selfType: ClassType | TypeVarType | undefined; - let classOrObjectBase: ClassType | undefined; - let memberAccessClass: Type | undefined; - let bindFunction = true; - let useDescriptorSetterType = false; - - switch (expression.nodeType) { - case ParseNodeType.Name: { - const symbolWithScope = lookUpSymbolRecursive(expression, expression.d.value, /* honorCodeFlow */ true); - if (symbolWithScope) { - symbol = symbolWithScope.symbol; - - // Handle the case where the symbol is a class-level variable - // where the type isn't declared in this class but is in - // a parent class. - if ( - !getDeclaredTypeOfSymbol(symbol, expression)?.type && - symbolWithScope.scope.type === ScopeType.Class - ) { - const enclosingClass = ParseTreeUtils.getEnclosingClassOrFunction(expression); - if (enclosingClass && enclosingClass.nodeType === ParseNodeType.Class) { - const classTypeInfo = getTypeOfClass(enclosingClass); - if (classTypeInfo) { - const classMemberInfo = lookUpClassMember( - classTypeInfo.classType, - expression.d.value, - MemberAccessFlags.SkipInstanceMembers | MemberAccessFlags.DeclaredTypesOnly - ); - if (classMemberInfo) { - symbol = classMemberInfo.symbol; - } - } - } - } - } - break; - } - - case ParseNodeType.TypeAnnotation: { - return getDeclaredTypeForExpression(expression.d.valueExpr, usage); - } - - case ParseNodeType.MemberAccess: { - const baseType = getTypeOfExpression(expression.d.leftExpr, EvalFlags.MemberAccessBaseDefaults).type; - const baseTypeConcrete = makeTopLevelTypeVarsConcrete(baseType); - const memberName = expression.d.member.d.value; - - // Normally, baseTypeConcrete will not be a composite type (a union), - // but this can occur. In this case, it's not clear how to handle this - // correctly. For now, we'll just loop through the subtypes and - // use one of them. We'll sort the subtypes for determinism. - doForEachSubtype( - baseTypeConcrete, - (baseSubtype) => { - if (isClassInstance(baseSubtype)) { - const classMemberInfo = lookUpObjectMember( - baseSubtype, - memberName, - MemberAccessFlags.DeclaredTypesOnly - ); - - classOrObjectBase = baseSubtype; - memberAccessClass = classMemberInfo?.classType; - symbol = classMemberInfo?.symbol; - useDescriptorSetterType = true; - - // If this is an instance member (e.g. a dataclass field), don't - // bind it to the object if it's a function. - bindFunction = !classMemberInfo?.isInstanceMember; - } else if (isInstantiableClass(baseSubtype)) { - const classMemberInfo = lookUpClassMember( - baseSubtype, - memberName, - MemberAccessFlags.SkipInstanceMembers | MemberAccessFlags.DeclaredTypesOnly - ); - - classOrObjectBase = baseSubtype; - memberAccessClass = classMemberInfo?.classType; - symbol = classMemberInfo?.symbol; - useDescriptorSetterType = false; - bindFunction = true; - } else if (isModule(baseSubtype)) { - classOrObjectBase = undefined; - memberAccessClass = undefined; - symbol = ModuleType.getField(baseSubtype, memberName); - if (symbol && !symbol.hasTypedDeclarations()) { - // Do not use inferred types for the declared type. - symbol = undefined; - } - useDescriptorSetterType = false; - bindFunction = false; - } - }, - /* sortSubtypes */ true - ); - - if (isTypeVar(baseType)) { - selfType = baseType; - } - break; - } - - case ParseNodeType.Index: { - const baseType = makeTopLevelTypeVarsConcrete( - getTypeOfExpression(expression.d.leftExpr, EvalFlags.IndexBaseDefaults).type - ); - - if (baseType && isClassInstance(baseType)) { - if (ClassType.isTypedDictClass(baseType)) { - const typeFromTypedDict = getTypeOfIndexedTypedDict( - evaluatorInterface, - expression, - baseType, - usage || { method: 'get' } - ); - if (typeFromTypedDict) { - return typeFromTypedDict.type; - } - } - - let setItemType = getBoundMagicMethod(baseType, '__setitem__'); - if (!setItemType) { - break; - } - - if (isOverloaded(setItemType)) { - // Determine whether we need to use the slice overload. - const expectsSlice = - expression.d.items.length === 1 && - expression.d.items[0].d.valueExpr.nodeType === ParseNodeType.Slice; - const overloads = OverloadedType.getOverloads(setItemType); - setItemType = overloads.find((overload) => { - if (overload.shared.parameters.length < 2) { - return false; - } - - const keyType = FunctionType.getParamType(overload, 0); - const isSlice = isClassInstance(keyType) && ClassType.isBuiltIn(keyType, 'slice'); - return expectsSlice === isSlice; - }); - - if (!setItemType) { - break; - } - } - - if (isFunction(setItemType) && setItemType.shared.parameters.length >= 2) { - const paramType = FunctionType.getParamType(setItemType, 1); - if (!isAnyOrUnknown(paramType)) { - return paramType; - } - } - } - break; - } - - case ParseNodeType.Tuple: { - // If this is a tuple expression with at least one item and no - // unpacked items, and all of the items have declared types, - // we can assume a declared type for the resulting tuple. This - // is needed to enable bidirectional type inference when assigning - // to an unpacked tuple. - if ( - expression.d.items.length > 0 && - !expression.d.items.some((item) => item.nodeType === ParseNodeType.Unpack) - ) { - const itemTypes: Type[] = []; - expression.d.items.forEach((expr) => { - const itemType = getDeclaredTypeForExpression(expr, usage); - if (itemType) { - itemTypes.push(itemType); - } - }); - - if (itemTypes.length === expression.d.items.length) { - // If all items have a declared type, return a tuple of those types. - return makeTupleObject( - evaluatorInterface, - itemTypes.map((t) => { - return { type: t, isUnbounded: false }; - }) - ); - } - } - break; - } - } - - if (symbol) { - let declaredType = getDeclaredTypeOfSymbol(symbol)?.type; - if (declaredType) { - // If it's a descriptor, we need to get the setter type. - if (useDescriptorSetterType && isClassInstance(declaredType)) { - const setter = getBoundMagicMethod(declaredType, '__set__'); - if (setter && isFunction(setter) && setter.shared.parameters.length >= 2) { - declaredType = FunctionType.getParamType(setter, 1); - - if (isAnyOrUnknown(declaredType)) { - return undefined; - } - } - } - - if (classOrObjectBase) { - if (memberAccessClass && isInstantiableClass(memberAccessClass)) { - declaredType = partiallySpecializeType( - declaredType, - memberAccessClass, - getTypeClassType(), - selfType - ); - } - - if (isFunctionOrOverloaded(declaredType)) { - if (bindFunction) { - declaredType = bindFunctionToClassOrObject( - classOrObjectBase, - declaredType, - /* memberClass */ undefined, - /* treatConstructorAsClassMethod */ undefined, - selfType - ); - } - } - } - - return declaredType; - } - } - - return undefined; - } - - // Applies an "await" operation to the specified type and returns - // the result. According to PEP 492, await operates on an Awaitable - // (object that provides an __await__ that returns a generator object). - // If errorNode is undefined, no errors are reported. - function getTypeOfAwaitable(typeResult: TypeResult, errorNode?: ExpressionNode): TypeResult { - if ( - !prefetched?.awaitableClass || - !isInstantiableClass(prefetched.awaitableClass) || - prefetched.awaitableClass.shared.typeParams.length !== 1 - ) { - return { type: UnknownType.create(), isIncomplete: typeResult.isIncomplete }; - } - - const awaitableProtocolObj = ClassType.cloneAsInstance(prefetched.awaitableClass); - const isIncomplete = !!typeResult.isIncomplete; - - const type = mapSubtypes(typeResult.type, (subtype) => { - subtype = makeTopLevelTypeVarsConcrete(subtype); - - if (isAnyOrUnknown(subtype)) { - return subtype; - } - - const diag = errorNode ? new DiagnosticAddendum() : undefined; - - if (isClassInstance(subtype)) { - const constraints = new ConstraintTracker(); - - if (assignType(awaitableProtocolObj, subtype, diag, constraints)) { - const specializedType = solveAndApplyConstraints(awaitableProtocolObj, constraints); - - if ( - isClass(specializedType) && - specializedType.priv.typeArgs && - specializedType.priv.typeArgs.length > 0 - ) { - return specializedType.priv.typeArgs[0]; - } - - return UnknownType.create(); - } - } - - if (errorNode && !typeResult.isIncomplete) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeNotAwaitable().format({ type: printType(subtype) }) + diag?.getString(), - errorNode - ); - } - - return UnknownType.create(); - }); - - return { type, isIncomplete }; - } - - // Validates that the type is an iterator and returns the iterated type - // (i.e. the type returned from the '__next__' or '__anext__' method). - function getTypeOfIterator( - typeResult: TypeResult, - isAsync: boolean, - errorNode: ExpressionNode, - emitNotIterableError = true - ): TypeResult | undefined { - const iterMethodName = isAsync ? '__aiter__' : '__iter__'; - const nextMethodName = isAsync ? '__anext__' : '__next__'; - let isValidIterator = true; - let isIncomplete = typeResult.isIncomplete; - - let type = transformPossibleRecursiveTypeAlias(typeResult.type); - type = makeTopLevelTypeVarsConcrete(type); - type = removeUnbound(type); - - if (isOptionalType(type) && emitNotIterableError) { - if (!typeResult.isIncomplete) { - addDiagnostic(DiagnosticRule.reportOptionalIterable, LocMessage.noneNotIterable(), errorNode); - } - type = removeNoneFromUnion(type); - } - - const iterableType = mapSubtypes(type, (subtype) => { - subtype = makeTopLevelTypeVarsConcrete(subtype); - - if (isAnyOrUnknown(subtype)) { - return subtype; - } - - const diag = new DiagnosticAddendum(); - if (isClass(subtype)) { - // Viper 扩展:CPtr 类型支持迭代(指针遍历)。 - // C 语言中指针可以通过递增来遍历内存,CPtr[T] 迭代返回 T 类型。 - // 包含 CPtr 的联合类型中,修饰类型(CConst, CStatic 等)和 - // 整数类型(CChar = int)也跳过迭代检查,因为它们是类型组合的一部分 - // (如 CConst | CChar | CPtr 表示 const char*)。 - const isViperDialectIter = IsViperDialect(AnalyzerNodeInfo.getFileInfo(errorNode).executionEnvironment.dialect); - if (isViperDialectIter && TypeBase.isInstance(subtype)) { - if (isCPtrType(subtype)) { - const typeArgs = subtype.priv.typeArgs; - if (typeArgs && typeArgs.length > 0) { - return typeArgs[0]; - } - return UnknownType.create(isIncomplete); - } - // Viper 扩展:str/bytes 类型迭代返回 t.CChar(字符)。 - // Viper 语义:str/bytes = i8* 指针(const char*),迭代返回单个字符 CChar。 - // 使用 getViperCCharType 而非裸 int,保持与 s[i] 下标访问一致(t.CChar 显示)。 - if (ClassType.isBuiltIn(subtype, ['str', 'bytes'])) { - const ccharType = getViperCCharType(errorNode); - if (ccharType) { - return ccharType; - } - return getBuiltInObject(errorNode, 'int'); - } - // 检查联合类型中是否包含 CPtr - const typeContainsCPtr = findSubtype(type, (s) => - isClassInstance(s) && isCPtrType(s)) !== undefined; - if (typeContainsCPtr && ( - isCConstType(subtype) || isCStaticType(subtype) || isCVoidType(subtype) || - ClassType.isBuiltIn(subtype, 'int') - )) { - return UnknownType.create(isIncomplete); - } - } - - // Handle an empty tuple specially. - if ( - TypeBase.isInstance(subtype) && - isTupleClass(subtype) && - subtype.priv.tupleTypeArgs && - subtype.priv.tupleTypeArgs.length === 0 - ) { - return NeverType.createNever(); - } - - const iterReturnType = getTypeOfMagicMethodCall(subtype, iterMethodName, [], errorNode)?.type; - - if (!iterReturnType) { - // There was no __iter__. See if we can fall back to - // the __getitem__ method instead. - if (!isAsync && isClassInstance(subtype)) { - const getItemReturnType = getTypeOfMagicMethodCall( - subtype, - '__getitem__', - [ - { - type: - prefetched?.intClass && isInstantiableClass(prefetched.intClass) - ? ClassType.cloneAsInstance(prefetched.intClass) - : UnknownType.create(), - }, - ], - errorNode - )?.type; - if (getItemReturnType) { - return getItemReturnType; - } - } - - diag.addMessage(LocMessage.methodNotDefined().format({ name: iterMethodName })); - } else { - const iterReturnTypeDiag = new DiagnosticAddendum(); - - const returnType = mapSubtypesExpandTypeVars(iterReturnType, /* options */ undefined, (subtype) => { - if (isAnyOrUnknown(subtype)) { - return subtype; - } - - let nextReturnType = getTypeOfMagicMethodCall(subtype, nextMethodName, [], errorNode)?.type; - - if (!nextReturnType) { - iterReturnTypeDiag.addMessage( - LocMessage.methodNotDefinedOnType().format({ - name: nextMethodName, - type: printType(subtype), - }) - ); - } else { - // Convert any unpacked TypeVarTuples into object instances. We don't - // know anything more about them. - nextReturnType = mapSubtypes(nextReturnType, (returnSubtype) => { - if (isTypeVar(returnSubtype) && isUnpackedTypeVarTuple(returnSubtype)) { - return getObjectType(); - } - - return returnSubtype; - }); - - if (!isAsync) { - return nextReturnType; - } - - // If it's an async iteration, there's an implicit - // 'await' operator applied. - const awaitableResult = getTypeOfAwaitable( - { type: nextReturnType, isIncomplete: typeResult.isIncomplete }, - errorNode - ); - if (awaitableResult.isIncomplete) { - isIncomplete = true; - } - return awaitableResult.type; - } - - return undefined; - }); - - if (iterReturnTypeDiag.isEmpty()) { - return returnType; - } - - diag.addAddendum(iterReturnTypeDiag); - } - } - - if (!isIncomplete && emitNotIterableError) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeNotIterable().format({ type: printType(subtype) }) + diag.getString(), - errorNode - ); - } - - isValidIterator = false; - return undefined; - }); - - return isValidIterator ? { type: iterableType, isIncomplete } : undefined; - } - - // Validates that the type is an iterable and returns the iterable type argument. - function getTypeOfIterable( - typeResult: TypeResult, - isAsync: boolean, - errorNode: ExpressionNode, - emitNotIterableError = true - ): TypeResult | undefined { - const iterMethodName = isAsync ? '__aiter__' : '__iter__'; - let isValidIterable = true; - - let type = makeTopLevelTypeVarsConcrete(typeResult.type); - - if (isOptionalType(type)) { - if (!typeResult.isIncomplete && emitNotIterableError) { - addDiagnostic(DiagnosticRule.reportOptionalIterable, LocMessage.noneNotIterable(), errorNode); - } - type = removeNoneFromUnion(type); - } - - const iterableType = mapSubtypes(type, (subtype) => { - if (isAnyOrUnknown(subtype)) { - return subtype; - } - - if (isClass(subtype)) { - // Viper 扩展:CPtr 类型支持迭代(指针遍历)。 - // 与 getTypeOfIterator 中的逻辑保持一致。 - const isViperDialectIter = IsViperDialect(AnalyzerNodeInfo.getFileInfo(errorNode).executionEnvironment.dialect); - if (isViperDialectIter && TypeBase.isInstance(subtype)) { - if (isCPtrType(subtype)) { - const typeArgs = subtype.priv.typeArgs; - if (typeArgs && typeArgs.length > 0) { - return makeTopLevelTypeVarsConcrete(typeArgs[0]); - } - return UnknownType.create(); - } - const typeContainsCPtr = findSubtype(type, (s) => - isClassInstance(s) && isCPtrType(s)) !== undefined; - if (typeContainsCPtr && ( - isCConstType(subtype) || isCStaticType(subtype) || isCVoidType(subtype) || - ClassType.isBuiltIn(subtype, 'int') - )) { - return UnknownType.create(); - } - } - - const iterReturnType = getTypeOfMagicMethodCall(subtype, iterMethodName, [], errorNode)?.type; - - if (iterReturnType) { - return makeTopLevelTypeVarsConcrete(iterReturnType); - } - } - - if (emitNotIterableError) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeNotIterable().format({ type: printType(subtype) }), - errorNode - ); - } - - isValidIterable = false; - return undefined; - }); - - return isValidIterable ? { type: iterableType, isIncomplete: typeResult.isIncomplete } : undefined; - } - - function isTypeHashable(type: Type): boolean { - let isTypeHashable = true; - - doForEachSubtype(makeTopLevelTypeVarsConcrete(type), (subtype) => { - if (isClassInstance(subtype)) { - // Assume the class is hashable. - let isObjectHashable = true; - - // Have we already computed and cached the hashability? - if (subtype.shared.isInstanceHashable !== undefined) { - isObjectHashable = subtype.shared.isInstanceHashable; - } else { - const hashMember = lookUpObjectMember(subtype, '__hash__', MemberAccessFlags.SkipObjectBaseClass); - - if (hashMember && hashMember.isTypeDeclared) { - const decls = hashMember.symbol.getTypedDeclarations(); - const synthesizedType = hashMember.symbol.getSynthesizedType(); - - // Handle the case where the type is synthesized (used for - // dataclasses). - if (synthesizedType) { - isObjectHashable = !isNoneInstance(synthesizedType.type); - } else { - // Assume that if '__hash__' is declared as a variable, it is - // not hashable. If it's declared as a function, it is. We'll - // skip evaluating its full type because that's not needed in - // this case. - if (decls.every((decl) => decl.type === DeclarationType.Variable)) { - isObjectHashable = false; - } - } - } - - // Cache the hashability for next time. - subtype.shared.isInstanceHashable = isObjectHashable; - } - - if (!isObjectHashable) { - isTypeHashable = false; - } - } - }); - - return isTypeHashable; - } - - function getTypedDictClassType(): ClassType | undefined { - return prefetched?.typedDictPrivateClass && isInstantiableClass(prefetched.typedDictPrivateClass) - ? prefetched.typedDictPrivateClass - : undefined; - } - - function getTupleClassType(): ClassType | undefined { - return prefetched?.tupleClass && isInstantiableClass(prefetched.tupleClass) ? prefetched.tupleClass : undefined; - } - - function getDictClassType(): ClassType | undefined { - return prefetched?.dictClass && isInstantiableClass(prefetched.dictClass) ? prefetched.dictClass : undefined; - } - - function getStrClassType(): ClassType | undefined { - return prefetched?.strClass && isInstantiableClass(prefetched.strClass) ? prefetched.strClass : undefined; - } - - function getObjectType(): Type { - return prefetched?.objectClass ? convertToInstance(prefetched.objectClass) : UnknownType.create(); - } - - function getNoneType(): Type { - return prefetched?.noneTypeClass ? convertToInstance(prefetched.noneTypeClass) : UnknownType.create(); - } - - function getUnionClassType(): Type { - return prefetched?.unionTypeClass ?? UnknownType.create(); - } - - function getTypeClassType(): ClassType | undefined { - if (prefetched?.typeClass && isInstantiableClass(prefetched.typeClass)) { - return prefetched.typeClass; - } - return undefined; - } - - function getTypingType(node: ParseNode, symbolName: string): Type | undefined { - return ( - getTypeOfModule(node, symbolName, ['typing']) ?? getTypeOfModule(node, symbolName, ['typing_extensions']) - ); - } - - function getTypeCheckerInternalsType(node: ParseNode, symbolName: string): Type | undefined { - return getTypeOfModule(node, symbolName, ['_typeshed', '_type_checker_internals']); - } - - function getTypesType(node: ParseNode, symbolName: string): Type | undefined { - return getTypeOfModule(node, symbolName, ['types']); - } - - function getTypeshedType(node: ParseNode, symbolName: string): Type | undefined { - return getTypeOfModule(node, symbolName, ['_typeshed']); - } - - function getTypeOfModule(node: ParseNode, symbolName: string, nameParts: string[]) { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - const lookupResult = importLookup({ nameParts, importingFileUri: fileInfo.fileUri }); - - if (!lookupResult) { - return undefined; - } - - const symbol = lookupResult.symbolTable.get(symbolName); - if (!symbol) { - return undefined; - } - - return getEffectiveTypeOfSymbol(symbol); - } - - function checkCodeFlowTooComplex(node: ParseNode): boolean { - const scopeNode = node.nodeType === ParseNodeType.Function ? node : ParseTreeUtils.getExecutionScopeNode(node); - const codeComplexity = AnalyzerNodeInfo.getCodeFlowComplexity(scopeNode); - - if (codeComplexity > maxCodeComplexity) { - let errorRange: TextRange = scopeNode; - if (scopeNode.nodeType === ParseNodeType.Function) { - errorRange = scopeNode.d.name; - } else if (scopeNode.nodeType === ParseNodeType.Module) { - errorRange = { start: 0, length: 0 }; - } - - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - addDiagnosticForTextRange( - fileInfo, - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.codeTooComplexToAnalyze(), - errorRange - ); - - return true; - } - - return false; - } - - function isNodeReachable(node: ParseNode, sourceNode?: ParseNode): boolean { - return getNodeReachability(node, sourceNode) === Reachability.Reachable; - } - - function isAfterNodeReachable(node: ParseNode): boolean { - return getAfterNodeReachability(node) === Reachability.Reachable; - } - - function getNodeReachability(node: ParseNode, sourceNode?: ParseNode): Reachability { - if (checkCodeFlowTooComplex(node)) { - return Reachability.Reachable; - } - - const flowNode = AnalyzerNodeInfo.getFlowNode(node); - if (!flowNode) { - if (node.parent) { - return getNodeReachability(node.parent, sourceNode); - } - return Reachability.UnreachableStructural; - } - - const sourceFlowNode = sourceNode ? AnalyzerNodeInfo.getFlowNode(sourceNode) : undefined; - - return codeFlowEngine.getFlowNodeReachability(flowNode, sourceFlowNode); - } - - function getAfterNodeReachability(node: ParseNode): Reachability { - const returnFlowNode = AnalyzerNodeInfo.getAfterFlowNode(node); - if (!returnFlowNode) { - return Reachability.UnreachableStructural; - } - - if (checkCodeFlowTooComplex(node)) { - return Reachability.Reachable; - } - - const reachability = codeFlowEngine.getFlowNodeReachability(returnFlowNode); - if (reachability !== Reachability.Reachable) { - return reachability; - } - - const executionScopeNode = ParseTreeUtils.getExecutionScopeNode(node); - if (!isFlowNodeReachableUsingNeverNarrowing(executionScopeNode, returnFlowNode)) { - return Reachability.UnreachableByAnalysis; - } - - return Reachability.Reachable; - } - - // Although isFlowNodeReachable indicates that the node is reachable, it - // may not be reachable if we apply "never narrowing". - function isFlowNodeReachableUsingNeverNarrowing(node: ExecutionScopeNode, flowNode: FlowNode) { - const analyzer = getCodeFlowAnalyzerForNode(node, /* typeAtStart */ undefined); - - if (checkCodeFlowTooComplex(node)) { - return true; - } - - const codeFlowResult = analyzer.getTypeFromCodeFlow(flowNode, /* reference */ undefined, { - typeAtStart: { type: UnboundType.create() }, - }); - - return codeFlowResult.type !== undefined && !isNever(codeFlowResult.type); - } - - // Determines whether there is a code flow path from sourceNode to sinkNode. - function isFlowPathBetweenNodes(sourceNode: ParseNode, sinkNode: ParseNode, allowSelf = true) { - if (checkCodeFlowTooComplex(sourceNode)) { - return true; - } - - const sourceFlowNode = AnalyzerNodeInfo.getFlowNode(sourceNode); - const sinkFlowNode = AnalyzerNodeInfo.getFlowNode(sinkNode); - if (!sourceFlowNode || !sinkFlowNode) { - return false; - } - if (sourceFlowNode === sinkFlowNode) { - return allowSelf; - } - - return ( - codeFlowEngine.getFlowNodeReachability(sinkFlowNode, sourceFlowNode, /* ignoreNoReturn */ true) === - Reachability.Reachable - ); - } - - function addInformation(message: string, node: ParseNode, range?: TextRange) { - return addDiagnosticWithSuppressionCheck('information', message, node, range); - } - - function addUnreachableCode(node: ParseNode, reachability: Reachability, textRange: TextRange) { - if (reachability === Reachability.Reachable) { - return; - } - - if (!isDiagnosticSuppressedForNode(node)) { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - const reportTypeReachability = fileInfo.diagnosticRuleSet.enableReachabilityAnalysis; - - if ( - reachability === Reachability.UnreachableStructural || - reachability === Reachability.UnreachableStaticCondition || - reportTypeReachability - ) { - fileInfo.diagnosticSink.addUnreachableCodeWithTextRange( - reachability === Reachability.UnreachableStructural - ? LocMessage.unreachableCodeStructure() - : reachability === Reachability.UnreachableStaticCondition - ? LocMessage.unreachableCodeCondition() - : LocMessage.unreachableCodeType(), - textRange - ); - } - } - } - - function addDeprecated(message: string, node: ParseNode) { - if (!isDiagnosticSuppressedForNode(node)) { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - fileInfo.diagnosticSink.addDeprecatedWithTextRange(message, node); - } - } - - function addDiagnosticWithSuppressionCheck( - diagLevel: DiagnosticLevel, - message: string, - node: ParseNode, - range?: TextRange - ) { - if (isDiagnosticSuppressedForNode(node)) { - // See if this node is suppressed but the diagnostic should be generated - // anyway so it can be used by the caller that requested the suppression. - const suppressionEntry = suppressedNodeStack.find( - (suppressedNode) => - ParseTreeUtils.isNodeContainedWithin(node, suppressedNode.node) && suppressedNode.suppressedDiags - ); - suppressionEntry?.suppressedDiags?.push(message); - - return undefined; - } - - if (isNodeReachable(node)) { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - return fileInfo.diagnosticSink.addDiagnosticWithTextRange(diagLevel, message, range ?? node); - } - - return undefined; - } - - function isDiagnosticSuppressedForNode(node: ParseNode) { - if (speculativeTypeTracker.isSpeculative(node, /* ignoreIfDiagnosticsAllowed */ true)) { - return true; - } - - return suppressedNodeStack.some((suppressedNode) => - ParseTreeUtils.isNodeContainedWithin(node, suppressedNode.node) - ); - } - - // This function is similar to isDiagnosticSuppressedForNode except that it - // returns false if diagnostics are suppressed for the node but the caller - // has requested that diagnostics be generated anyway. - function canSkipDiagnosticForNode(node: ParseNode) { - if (speculativeTypeTracker.isSpeculative(node, /* ignoreIfDiagnosticsAllowed */ true)) { - return true; - } - - const suppressedEntries = suppressedNodeStack.filter((suppressedNode) => - ParseTreeUtils.isNodeContainedWithin(node, suppressedNode.node) - ); - - if (suppressedEntries.length === 0) { - return false; - } - - return suppressedEntries.every((entry) => !entry.suppressedDiags); - } - - function addDiagnostic(rule: DiagnosticRule, message: string, node: ParseNode, range?: TextRange) { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - const diagLevel = fileInfo.diagnosticRuleSet[rule] as DiagnosticLevel; - - if (diagLevel === 'none') { - return undefined; - } - - const containingFunction = ParseTreeUtils.getEnclosingFunction(node); - - if (containingFunction) { - // Should we suppress this diagnostic because it's within an unannotated function? - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - if (!fileInfo.diagnosticRuleSet.analyzeUnannotatedFunctions) { - // Is the target node within the body of the function? If so, suppress the diagnostic. - if ( - ParseTreeUtils.isUnannotatedFunction(containingFunction) && - ParseTreeUtils.isNodeContainedWithin(node, containingFunction.d.suite) - ) { - return undefined; - } - } - - // Should we suppress this diagnostic because it's within a no_type_check function? - const containingClassNode = ParseTreeUtils.getEnclosingClass(containingFunction, /* stopAtFunction */ true); - const functionInfo = getFunctionInfoFromDecorators( - evaluatorInterface, - containingFunction, - !!containingClassNode - ); - - if ((functionInfo.flags & FunctionTypeFlags.NoTypeCheck) !== 0) { - return undefined; - } - } - - const diagnostic = addDiagnosticWithSuppressionCheck(diagLevel, message, node, range); - if (diagnostic) { - diagnostic.setRule(rule); - } - - return diagnostic; - } - - function addDiagnosticForTextRange( - fileInfo: AnalyzerFileInfo, - rule: DiagnosticRule, - message: string, - range: TextRange - ) { - const diagLevel = fileInfo.diagnosticRuleSet[rule] as DiagnosticLevel; - - if (diagLevel === 'none') { - return undefined; - } - - const diagnostic = fileInfo.diagnosticSink.addDiagnosticWithTextRange(diagLevel, message, range); - if (rule) { - diagnostic.setRule(rule); - } - - return diagnostic; - } - - function assignTypeToNameNode( - nameNode: NameNode, - typeResult: TypeResult, - ignoreEmptyContainers: boolean, - srcExpression?: ParseNode, - allowAssignmentToFinalVar = false, - expectedTypeDiagAddendum?: DiagnosticAddendum - ) { - const nameValue = nameNode.d.value; - - const symbolWithScope = lookUpSymbolRecursive(nameNode, nameValue, /* honorCodeFlow */ false); - if (!symbolWithScope) { - // This can happen when we are evaluating a piece of code that was - // determined to be unreachable by the binder. - return; - } - - const declarations = symbolWithScope.symbol.getDeclarations(); - let declaredType = getDeclaredTypeOfSymbol(symbolWithScope.symbol)?.type; - const fileInfo = AnalyzerNodeInfo.getFileInfo(nameNode); - - // Viper 扩展:禁止函数内部隐式写入全局变量(无 global 声明且无局部声明)。 - // 规则: - // 1. 全局变量存在 + 局部再次声明赋值(无 global)→ 不报错(局部优先) - // 2. 全局变量存在 + 局部无声明 + 有 global + 赋值 → 不报错 - // 3. 全局变量存在 + 局部无声明 + 无 global + 仅读取 → 不报错(读取处不检查) - // 4. 全局变量存在 + 局部无声明 + 无 global + 赋值 → 报错 - // 判断关键:symbolWithScope.scope.type === Module 说明局部没有声明, - // 查找到的是全局符号;此时若无 global 声明且在函数内赋值,则报错。 - // 跳过非变量类型(模块导入、函数、类、类型别名)和 CDefine 常量。 - if (IsViperDialect(fileInfo.executionEnvironment.dialect)) { - const containingFunction = ParseTreeUtils.getEnclosingFunction(nameNode); - if ( - containingFunction && - !symbolWithScope.usesGlobalBinding && - !symbolWithScope.usesNonlocalBinding - ) { - // 查找模块作用域中的同名全局变量。 - // 情况 1:符号直接在 Module 作用域(无局部声明)→ 直接使用。 - // 情况 2:符号在 Function 作用域(绑定器因赋值创建局部符号), - // 但无类型注解声明(不是合法局部变量)→ 查找模块作用域全局。 - let globalSymbol: typeof symbolWithScope.symbol | undefined; - if (symbolWithScope.scope.type === ScopeType.Module) { - globalSymbol = symbolWithScope.symbol; - } else if (!symbolWithScope.symbol.hasTypedDeclarations()) { - const globalScopeResult = symbolWithScope.scope.getGlobalScope(); - if (globalScopeResult) { - globalSymbol = globalScopeResult.scope.lookUpSymbol(nameValue); - } - } - - if (globalSymbol) { - // 只对变量声明(Variable)报错,跳过函数、类、模块导入、类型别名 - const globalDecls = globalSymbol.getDeclarations(); - const isGlobalVariable = globalDecls.some( - (decl) => decl.type === DeclarationType.Variable - ); - if (isGlobalVariable) { - // 跳过 CDefine 常量(编译期宏定义,可在任意作用域使用) - const globalEffectiveType = getEffectiveTypeOfSymbol(globalSymbol); - if (!isCDefineType(globalEffectiveType)) { - addDiagnostic( - DiagnosticRule.reportUndefinedVariable, - LocMessage.viperGlobalAssignmentWithoutGlobal().format({ - name: nameValue, - }), - nameNode - ); - } - } - } - } - } - - // If this is a class scope and there is no type declared for this class variable, - // see if a parent class has a type declared. - if (declaredType === undefined && symbolWithScope.scope.type === ScopeType.Class) { - const containingClass = ParseTreeUtils.getEnclosingClass(nameNode); - if (containingClass) { - const classType = getTypeOfClass(containingClass); - if (classType) { - const memberInfo = lookUpClassMember( - classType.classType, - nameNode.d.value, - MemberAccessFlags.SkipOriginalClass - ); - if (memberInfo?.isTypeDeclared) { - declaredType = getTypeOfMember(memberInfo); - } - } - } - } - - // We found an existing declared type. Make sure the type is assignable. - let destType = typeResult.type; - const isTypeAlias = - !!declaredType && - isClassInstance(declaredType) && - (ClassType.isBuiltIn(declaredType, 'TypeAlias') || - // Viper 扩展:t.CTypedef 是 TypeAlias 的别名(CTypedef: TypeAlias = TypeAlias), - // 但来自 vipershed-fallback 没有 BuiltIn 标志,需要额外检查类型名称。 - (IsViperDialect(fileInfo.executionEnvironment.dialect) && - (declaredType.shared.name === 'CTypedef' || - declaredType.shared.fullName === 't.CTypedef'))); - - if (declaredType && !isTypeAlias) { - let diagAddendum = new DiagnosticAddendum(); - - const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(nameNode); - const boundDeclaredType = makeTypeVarsBound(declaredType, liveScopeIds); - const srcType = makeTypeVarsBound(typeResult.type, liveScopeIds); - - if (!assignType(boundDeclaredType, srcType, diagAddendum)) { - // If there was an expected type mismatch, use that diagnostic - // addendum because it will be more informative. - if (expectedTypeDiagAddendum) { - diagAddendum = expectedTypeDiagAddendum; - } - - if (!typeResult.isIncomplete) { - addDiagnostic( - DiagnosticRule.reportAssignmentType, - LocMessage.typeAssignmentMismatch().format(printSrcDestTypes(typeResult.type, declaredType)) + - diagAddendum.getString(), - srcExpression ?? nameNode, - diagAddendum.getEffectiveTextRange() ?? srcExpression ?? nameNode - ); - } - - // Replace the assigned type with the (unnarrowed) declared type. - destType = declaredType; - } else { - // Constrain the resulting type to match the declared type. - destType = narrowTypeBasedOnAssignment(declaredType, typeResult).type; - } - } else { - // If this is a member name (within a class scope) and the member name - // appears to be a constant, use the strict source type. If it's a member - // variable that can be overridden by a child class, use the more general - // version by stripping off the literal and TypeForm. - const scope = ScopeUtils.getScopeForNode(nameNode); - if (scope?.type === ScopeType.Class) { - if ( - TypeBase.isInstance(destType) && - !isConstantName(nameValue) && - !isFinalVariable(symbolWithScope.symbol) - ) { - destType = stripTypeForm(stripLiteralValue(destType)); - } - } - } - - const varDeclIndex = declarations.findIndex((decl) => decl.type === DeclarationType.Variable); - const varDecl = varDeclIndex >= 0 ? declarations[varDeclIndex] : undefined; - - // Are there any non-var decls before the var decl? - const nonVarDecl = declarations.find( - (decl, index) => varDeclIndex < index && decl.type !== DeclarationType.Variable - ); - - if (varDecl && varDecl.type === DeclarationType.Variable) { - if (varDecl.isConstant) { - // A constant variable can be assigned only once. If this - // isn't the first assignment, generate an error. - if (nameNode !== getNameNodeForDeclaration(declarations[0]) || !!nonVarDecl) { - addDiagnostic( - DiagnosticRule.reportConstantRedefinition, - LocMessage.constantRedefinition().format({ name: nameValue }), - nameNode - ); - } - } else if (isFinalVariableDeclaration(varDecl) && !allowAssignmentToFinalVar) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.finalReassigned().format({ name: nameValue }), - nameNode - ); - } - } - - if (!typeResult.isIncomplete) { - reportPossibleUnknownAssignment( - fileInfo.diagnosticRuleSet.reportUnknownVariableType, - DiagnosticRule.reportUnknownVariableType, - nameNode, - typeResult.type, - nameNode, - ignoreEmptyContainers - ); - } - - writeTypeCache(nameNode, { type: destType, isIncomplete: typeResult.isIncomplete }, EvalFlags.None); - } - - function assignTypeToMemberAccessNode( - target: MemberAccessNode, - typeResult: TypeResult, - srcExpr?: ExpressionNode, - expectedTypeDiagAddendum?: DiagnosticAddendum - ) { - const baseTypeResult = getTypeOfExpression(target.d.leftExpr, EvalFlags.MemberAccessBaseDefaults); - const baseType = makeTopLevelTypeVarsConcrete(baseTypeResult.type); - let enclosingClass: ClassType | undefined; - - // Handle member accesses (e.g. self.x or cls.y). - if (target.d.leftExpr.nodeType === ParseNodeType.Name) { - // Determine whether we're writing to a class or instance member. - const enclosingClassNode = ParseTreeUtils.getEnclosingClass(target); - - if (enclosingClassNode) { - const classTypeResults = getTypeOfClass(enclosingClassNode); - - if (classTypeResults && isInstantiableClass(classTypeResults.classType)) { - enclosingClass = classTypeResults.classType; - - if (isClassInstance(baseType)) { - if ( - ClassType.isSameGenericClass( - ClassType.cloneAsInstantiable(baseType), - classTypeResults.classType - ) - ) { - assignTypeToMemberVariable(target, typeResult, /* isInstanceMember */ true, srcExpr); - } - } else if (isInstantiableClass(baseType)) { - if (ClassType.isSameGenericClass(baseType, classTypeResults.classType)) { - assignTypeToMemberVariable(target, typeResult, /* isInstanceMember */ false, srcExpr); - } - } - - // Assignments to instance or class variables through "self" or "cls" is not - // allowed for protocol classes unless it is also declared within the class. - if (ClassType.isProtocolClass(classTypeResults.classType)) { - const memberSymbol = ClassType.getSymbolTable(classTypeResults.classType).get( - target.d.member.d.value - ); - if (memberSymbol) { - const classLevelDecls = memberSymbol.getDeclarations().filter((decl) => { - return !ParseTreeUtils.getEnclosingFunction(decl.node); - }); - if (classLevelDecls.length === 0) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.assignmentInProtocol(), - target.d.member - ); - } - } - } - } - } - } - - const setTypeResult = getTypeOfMemberAccessWithBaseType( - target, - baseTypeResult, - { - method: 'set', - setType: typeResult, - setErrorNode: srcExpr, - setExpectedTypeDiag: expectedTypeDiagAddendum, - }, - EvalFlags.None - ); - - if (setTypeResult.isAsymmetricAccessor) { - setAsymmetricDescriptorAssignment(target); - } - - const resultToCache: TypeResult = { - type: setTypeResult.narrowedTypeForSet ?? typeResult.type, - isIncomplete: typeResult.isIncomplete, - memberAccessDeprecationInfo: setTypeResult.memberAccessDeprecationInfo, - }; - writeTypeCache(target, resultToCache, EvalFlags.None); - - // If the target is an instance or class variable, update any class-scoped - // type variables so the inferred type of the variable uses "external" - // type variables. - let memberResultToCache = resultToCache; - if (enclosingClass?.shared.typeVarScopeId) { - memberResultToCache = { - ...resultToCache, - type: makeTypeVarsFree(resultToCache.type, [enclosingClass.shared.typeVarScopeId]), - memberAccessDeprecationInfo: setTypeResult.memberAccessDeprecationInfo, - }; - } - writeTypeCache(target.d.member, memberResultToCache, EvalFlags.None); - } - - function assignTypeToMemberVariable( - node: MemberAccessNode, - typeResult: TypeResult, - isInstanceMember: boolean, - srcExprNode?: ExpressionNode - ) { - const memberName = node.d.member.d.value; - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - - const classDef = ParseTreeUtils.getEnclosingClass(node); - if (!classDef) { - return; - } - - const classTypeInfo = getTypeOfClass(classDef); - if (classTypeInfo && isInstantiableClass(classTypeInfo.classType)) { - let memberInfo = lookUpClassMember( - classTypeInfo.classType, - memberName, - isInstanceMember ? MemberAccessFlags.Default : MemberAccessFlags.SkipInstanceMembers - ); - - const memberFields = ClassType.getSymbolTable(classTypeInfo.classType); - if (memberInfo) { - // Are we accessing an existing member on this class, or is - // it a member on a parent class? - const memberClass = isInstantiableClass(memberInfo.classType) ? memberInfo.classType : undefined; - const isThisClass = memberClass && ClassType.isSameGenericClass(classTypeInfo.classType, memberClass); - - // Check for an attempt to write to an instance variable that is - // not defined by __slots__. - if (isThisClass && isInstanceMember && memberClass) { - const inheritedSlotsNames = ClassType.getInheritedSlotsNames(memberClass); - - if (inheritedSlotsNames && memberClass.shared.localSlotsNames) { - // Skip this check if the local slots is specified but empty - // and the class isn't final. This pattern is used in a - // legitimate manner for mix-in classes. - if ( - (memberClass.shared.localSlotsNames.length > 0 || ClassType.isFinal(memberClass)) && - !inheritedSlotsNames.some((name) => name === memberName) - ) { - // Determine whether the assignment corresponds to a descriptor - // that was assigned as a class variable. If so, then slots will not - // apply in this case. - const classMemberDetails = lookUpClassMember( - memberClass, - memberName, - MemberAccessFlags.SkipInstanceMembers - ); - let isPotentiallyDescriptor = false; - - if (classMemberDetails) { - const classMemberSymbolType = getEffectiveTypeOfSymbol(classMemberDetails.symbol); - if ( - isAnyOrUnknown(classMemberSymbolType) || - isUnbound(classMemberSymbolType) || - isMaybeDescriptorInstance(classMemberSymbolType) - ) { - isPotentiallyDescriptor = true; - } - } - - if (!isPotentiallyDescriptor) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.slotsAttributeError().format({ name: memberName }), - node.d.member - ); - } - } - } - } - - if (isThisClass && memberInfo.isInstanceMember === isInstanceMember) { - const symbol = memberFields.get(memberName)!; - assert(symbol !== undefined); - - const typedDecls = symbol.getDeclarations(); - - // Check for an attempt to overwrite a constant member variable. - if ( - typedDecls.length > 0 && - typedDecls[0].type === DeclarationType.Variable && - srcExprNode && - node.d.member !== typedDecls[0].node - ) { - if (typedDecls[0].isConstant) { - addDiagnostic( - DiagnosticRule.reportConstantRedefinition, - LocMessage.constantRedefinition().format({ name: node.d.member.d.value }), - node.d.member - ); - } - } - } else { - // Is the target a property? - const declaredType = getDeclaredTypeOfSymbol(memberInfo.symbol)?.type; - if (declaredType && !isProperty(declaredType)) { - // Handle the case where there is a class variable defined with the same - // name, but there's also now an instance variable introduced. Combine the - // type of the class variable with that of the new instance variable. - if (!memberInfo.isInstanceMember && isInstanceMember) { - // The class variable is accessed in this case. - setSymbolAccessed(fileInfo, memberInfo.symbol, node.d.member); - const memberType = getTypeOfMember(memberInfo); - typeResult = { ...typeResult, type: combineTypes([typeResult.type, memberType]) }; - } - } - } - } - - // Look up the member info again, now that we've potentially updated it. - memberInfo = lookUpClassMember(classTypeInfo.classType, memberName, MemberAccessFlags.DeclaredTypesOnly); - - if (!memberInfo && srcExprNode && !typeResult.isIncomplete) { - reportPossibleUnknownAssignment( - fileInfo.diagnosticRuleSet.reportUnknownMemberType, - DiagnosticRule.reportUnknownMemberType, - node.d.member, - typeResult.type, - node, - /* ignoreEmptyContainers */ true - ); - } - } - } - - function assignTypeToTupleOrListNode( - target: TupleNode | ListNode, - typeResult: TypeResult, - srcExpr: ExpressionNode - ) { - const targetExpressions = target.nodeType === ParseNodeType.List ? target.d.items : target.d.items; - - // Initialize the array of target types, one for each target. - const targetTypes: Type[][] = new Array(targetExpressions.length); - for (let i = 0; i < targetExpressions.length; i++) { - targetTypes[i] = []; - } - const targetUnpackIndex = targetExpressions.findIndex((expr) => expr.nodeType === ParseNodeType.Unpack); - - // Do any of the targets use an unpack operator? If so, it will consume all of the - // entries at that location. - const unpackIndex = targetExpressions.findIndex((expr) => expr.nodeType === ParseNodeType.Unpack); - - typeResult = { ...typeResult, type: makeTopLevelTypeVarsConcrete(typeResult.type) }; - - const diagAddendum = new DiagnosticAddendum(); - - doForEachSubtype(typeResult.type, (subtype) => { - // Is this subtype a tuple? - const tupleType = getSpecializedTupleType(subtype); - if (tupleType && tupleType.priv.tupleTypeArgs) { - const sourceEntryTypes = tupleType.priv.tupleTypeArgs.map((t) => - addConditionToType(t.type, getTypeCondition(subtype), { skipSelfCondition: true }) - ); - - const unboundedIndex = tupleType.priv.tupleTypeArgs.findIndex((t) => t.isUnbounded); - - if (unboundedIndex >= 0) { - if (sourceEntryTypes.length < targetTypes.length) { - const typeToReplicate = - sourceEntryTypes.length > 0 ? sourceEntryTypes[unboundedIndex] : AnyType.create(); - - // Add elements to make the count match the target count. - while (sourceEntryTypes.length < targetTypes.length) { - sourceEntryTypes.splice(unboundedIndex, 0, typeToReplicate); - } - } - - if (sourceEntryTypes.length > targetTypes.length) { - // Remove elements to make the count match the target count. - sourceEntryTypes.splice(unboundedIndex, 1); - } - } - - // If there's an unpack operator in the target and we have too many source elements, - // combine them to assign to the unpacked target. - if (targetUnpackIndex >= 0) { - if (sourceEntryTypes.length > targetTypes.length) { - const removedEntries = sourceEntryTypes.splice( - targetUnpackIndex, - sourceEntryTypes.length - targetTypes.length + 1 - ); - let combinedTypes = combineTypes(removedEntries); - if (target.nodeType === ParseNodeType.List) { - combinedTypes = stripLiteralValue(combinedTypes); - } - sourceEntryTypes.splice(targetUnpackIndex, 0, combinedTypes); - } else if (sourceEntryTypes.length === targetTypes.length - 1) { - sourceEntryTypes.splice(targetUnpackIndex, 0, NeverType.createNever()); - } - } - - sourceEntryTypes.forEach((type, targetIndex) => { - if (targetIndex < targetTypes.length) { - targetTypes[targetIndex].push(type); - } - }); - - // Have we accounted for all of the targets and sources? If not, we have a size mismatch. - if (sourceEntryTypes.length !== targetExpressions.length) { - const subDiag = diagAddendum.createAddendum(); - subDiag.addMessage( - (target.nodeType === ParseNodeType.List - ? LocAddendum.listAssignmentMismatch() - : LocAddendum.tupleAssignmentMismatch() - ).format({ - type: printType(subtype), - }) - ); - - subDiag.createAddendum().addMessage( - (unpackIndex >= 0 - ? LocAddendum.tupleSizeMismatchIndeterminateDest() - : LocAddendum.tupleSizeMismatch() - ).format({ - expected: unpackIndex >= 0 ? targetExpressions.length - 1 : targetExpressions.length, - received: sourceEntryTypes.length, - }) - ); - } - } else { - // The assigned expression isn't a tuple, so it had better - // be some iterable type. - const iterableType = - getTypeOfIterator( - { type: subtype, isIncomplete: typeResult.isIncomplete }, - /* isAsync */ false, - srcExpr - )?.type ?? UnknownType.create(); - for (let index = 0; index < targetExpressions.length; index++) { - targetTypes[index].push(addConditionToType(iterableType, getTypeCondition(subtype))); - } - } - }); - - if (!diagAddendum.isEmpty()) { - addDiagnostic( - DiagnosticRule.reportAssignmentType, - (target.nodeType === ParseNodeType.List - ? LocMessage.listAssignmentMismatch() - : LocMessage.tupleAssignmentMismatch() - ).format({ - type: printType(typeResult.type), - }) + diagAddendum.getString(), - target - ); - } - - // Assign the resulting types to the individual names in the tuple - // or list target expression. - targetExpressions.forEach((expr, index) => { - const typeList = targetTypes[index]; - const targetType = typeList.length === 0 ? UnknownType.create() : combineTypes(typeList); - - assignTypeToExpression( - expr, - { type: targetType, isIncomplete: typeResult.isIncomplete }, - srcExpr, - /* ignoreEmptyContainers */ true - ); - }); - - writeTypeCache(target, typeResult, EvalFlags.None); - } - - // If the type includes promotion types, expand these to their constituent types. - function expandPromotionTypes(node: ParseNode, type: Type, excludeBytes = false): Type { - return mapSubtypes(type, (subtype) => { - if (!isClass(subtype) || !subtype.priv.includePromotions || subtype.priv.literalValue !== undefined) { - return subtype; - } - - if (excludeBytes && ClassType.isBuiltIn(subtype, 'bytes')) { - return subtype; - } - - const typesToCombine: Type[] = [ClassType.cloneRemoveTypePromotions(subtype)]; - - const promotionTypeNames = typePromotions.get(subtype.shared.fullName); - if (promotionTypeNames) { - for (const promotionTypeName of promotionTypeNames) { - const nameSplit = promotionTypeName.split('.'); - let promotionSubtype = getBuiltInType(node, nameSplit[nameSplit.length - 1]); - - if (promotionSubtype && isInstantiableClass(promotionSubtype)) { - promotionSubtype = ClassType.cloneRemoveTypePromotions(promotionSubtype); - - if (isClassInstance(subtype)) { - promotionSubtype = ClassType.cloneAsInstance(promotionSubtype); - } - - promotionSubtype = addConditionToType(promotionSubtype, subtype.props?.condition); - typesToCombine.push(promotionSubtype); - } - } - } - - return combineTypes(typesToCombine); - }); - } - - // Replaces all of the top-level TypeVars (as opposed to TypeVars - // used as type arguments in other types) with their concrete form. - // If conditionFilter is specified and the TypeVar is a constrained - // TypeVar, only the conditions that match the filter will be included. - function makeTopLevelTypeVarsConcrete( - type: Type, - makeParamSpecsConcrete = false, - conditionFilter?: TypeCondition[] - ): Type { - type = transformPossibleRecursiveTypeAlias(type); - - return mapSubtypes(type, (subtype) => { - if (isParamSpec(subtype)) { - if (subtype.priv.paramSpecAccess === 'args') { - return makeTupleObject(evaluatorInterface, [{ type: getObjectType(), isUnbounded: true }]); - } else if (subtype.priv.paramSpecAccess === 'kwargs') { - if ( - prefetched?.dictClass && - isInstantiableClass(prefetched.dictClass) && - prefetched?.strClass && - isInstantiableClass(prefetched.strClass) - ) { - return ClassType.cloneAsInstance( - ClassType.specialize(prefetched.dictClass, [ - convertToInstance(prefetched.strClass), - getObjectType(), - ]) - ); - } - - return UnknownType.create(); - } - } - - // If this is a function that contains only a ParamSpec (no additional - // parameters), convert it to a concrete type of (*args: Unknown, **kwargs: Unknown). - if (makeParamSpecsConcrete && isFunction(subtype)) { - const convertedType = simplifyFunctionToParamSpec(subtype); - if (isParamSpec(convertedType)) { - return ParamSpecType.getUnknown(); - } - } - - if (isTypeVarTuple(subtype)) { - // If it's in a union, convert to type or object. - if (subtype.priv.isInUnion) { - if (TypeBase.isInstantiable(subtype)) { - if (prefetched?.typeClass && isInstantiableClass(prefetched.typeClass)) { - return prefetched.typeClass; - } - } else { - return getObjectType(); - } - - return AnyType.create(); - } - - // Fall back to "*tuple[object, ...]". - return makeTupleObject( - evaluatorInterface, - [{ type: getObjectType(), isUnbounded: true }], - /* isUnpacked */ true - ); - } - - if (isTypeVar(subtype)) { - // If this is a recursive type alias placeholder - // that hasn't yet been resolved, return it as is. - if (subtype.shared.recursiveAlias) { - return subtype; - } - - if (TypeVarType.hasConstraints(subtype)) { - const typesToCombine: Type[] = []; - - // Expand the list of constrained subtypes, filtering out any that are - // disallowed by the conditionFilter. - subtype.shared.constraints.forEach((constraintType, constraintIndex) => { - if (conditionFilter) { - const typeVarName = TypeVarType.getNameWithScope(subtype); - const applicableConstraint = conditionFilter.find( - (filter) => filter.typeVar.priv.nameWithScope === typeVarName - ); - - // If this type variable is being constrained to a single index, - // don't include the other indices. - if (applicableConstraint && applicableConstraint.constraintIndex !== constraintIndex) { - return; - } - } - - if (TypeBase.isInstantiable(subtype)) { - constraintType = convertToInstantiable(constraintType); - } - - typesToCombine.push( - addConditionToType(constraintType, [{ typeVar: subtype, constraintIndex }]) - ); - }); - - return combineTypes(typesToCombine); - } - - if (subtype.shared.isExemptFromBoundCheck) { - return AnyType.create(); - } - - // Fall back to a bound of "object" if no bound is provided. - let boundType = subtype.shared.boundType ?? getObjectType(); - - // If this is a synthesized self/cls type var, self-specialize its type arguments. - if (TypeVarType.isSelf(subtype) && isClass(boundType) && !ClassType.isPseudoGenericClass(boundType)) { - boundType = selfSpecializeClass(boundType, { - useBoundTypeVars: TypeVarType.isBound(subtype), - }); - } - - if (subtype.priv.isUnpacked && isClass(boundType)) { - boundType = ClassType.cloneForUnpacked(boundType); - } - - boundType = TypeBase.isInstantiable(subtype) ? convertToInstantiable(boundType) : boundType; - - return addConditionToType(boundType, [{ typeVar: subtype, constraintIndex: 0 }]); - } - - return subtype; - }); - } - - // Creates a new type by mapping an existing type (which could be a union) - // to another type or types. The callback is called for each subtype. - // Top-level TypeVars are expanded (e.g. a bound TypeVar is expanded to - // its bound type and a constrained TypeVar is expanded to its individual - // constrained types). If conditionFilter is specified, conditions that - // do not match will be ignored. - function mapSubtypesExpandTypeVars( - type: Type, - options: MapSubtypesOptions | undefined, - callback: (expandedSubtype: Type, unexpandedSubtype: Type, isLastIteration: boolean) => Type | undefined, - recursionCount = 0 - ): Type { - const newSubtypes: Type[] = []; - let typeChanged = false; - - function expandSubtype(unexpandedType: Type, isLastSubtype: boolean) { - let expandedType = isUnion(unexpandedType) ? unexpandedType : makeTopLevelTypeVarsConcrete(unexpandedType); - - expandedType = transformPossibleRecursiveTypeAlias(expandedType); - if (options?.expandCallback) { - expandedType = options.expandCallback(expandedType); - } - - doForEachSubtype( - expandedType, - (subtype, index, allSubtypes) => { - if (options?.conditionFilter) { - const filteredType = applyConditionFilterToType( - subtype, - options.conditionFilter, - recursionCount - ); - if (!filteredType) { - return undefined; - } - - subtype = filteredType; - } - - let transformedType = callback( - subtype, - unexpandedType, - isLastSubtype && index === allSubtypes.length - 1 - ); - - if (transformedType !== unexpandedType) { - typeChanged = true; - } - - if (transformedType) { - // Apply the type condition if it's associated with a constrained TypeVar. - const typeCondition = getTypeCondition(subtype)?.filter((condition) => - TypeVarType.hasConstraints(condition.typeVar) - ); - - if (typeCondition && typeCondition.length > 0) { - transformedType = addConditionToType(transformedType, typeCondition); - } - - // This code path can often produce many duplicate subtypes. We can - // reduce the cost of the combineTypes call below by filtering out these - // duplicates proactively. - if ( - newSubtypes.length === 0 || - !isTypeSame(transformedType, newSubtypes[newSubtypes.length - 1]) - ) { - newSubtypes.push(transformedType); - } - } - return undefined; - }, - options?.sortSubtypes - ); - } - - if (isUnion(type)) { - const subtypes = options?.sortSubtypes ? sortTypes(type.priv.subtypes) : type.priv.subtypes; - subtypes.forEach((subtype, index) => { - expandSubtype(subtype, index === type.priv.subtypes.length - 1); - }); - } else { - expandSubtype(type, /* isLastSubtype */ true); - } - - if (!typeChanged) { - return type; - } - - const newType = combineTypes(newSubtypes); - - // Do our best to retain type aliases. - if (newType.category === TypeCategory.Union) { - UnionType.addTypeAliasSource(newType, type); - } - return newType; - } - - function applyConditionFilterToType( - type: Type, - conditionFilter: TypeCondition[], - recursionCount: number - ): Type | undefined { - if (recursionCount > maxTypeRecursionCount) { - return type; - } - recursionCount++; - - // If the type has a condition associated with it, make sure it's compatible. - if (!TypeCondition.isCompatible(getTypeCondition(type), conditionFilter)) { - return undefined; - } - - // If the type is generic, see if any of its type arguments should be filtered. - // This is possible only in cases where the type parameter is covariant. - - // TODO - handle functions and tuples - if (isClass(type) && type.priv.typeArgs && !type.priv.tupleTypeArgs) { - inferVarianceForClass(type); - - let typeWasTransformed = false; - - const filteredTypeArgs = type.priv.typeArgs.map((typeArg, index) => { - if (index >= type.shared.typeParams.length) { - return typeArg; - } - - const variance = TypeVarType.getVariance(type.shared.typeParams[index]); - if (variance !== Variance.Covariant) { - return typeArg; - } - - // Don't expand recursive type aliases because they can - // cause infinite recursion. - if (isTypeVar(typeArg) && typeArg.shared.recursiveAlias) { - return typeArg; - } - - const filteredTypeArg = mapSubtypesExpandTypeVars( - typeArg, - { conditionFilter }, - (expandedSubtype) => { - return expandedSubtype; - }, - recursionCount - ); - - if (filteredTypeArg !== typeArg) { - typeWasTransformed = true; - } - - return filteredTypeArg; - }); - - if (typeWasTransformed) { - return ClassType.specialize(type, filteredTypeArgs); - } - } - - return type; - } - - function markNamesAccessed(node: ParseNode, names: string[]) { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - const scope = ScopeUtils.getScopeForNode(node); - - if (scope) { - names.forEach((symbolName) => { - const symbolInScope = scope.lookUpSymbolRecursive(symbolName); - if (symbolInScope) { - setSymbolAccessed(fileInfo, symbolInScope.symbol, node); - } - }); - } - } - - function assignTypeToExpression( - target: ExpressionNode, - typeResult: TypeResult, - srcExpr: ExpressionNode, - ignoreEmptyContainers = false, - allowAssignmentToFinalVar = false, - expectedTypeDiagAddendum?: DiagnosticAddendum - ) { - // Is the source expression a TypeVar() call? - if (isTypeVar(typeResult.type)) { - if (srcExpr && srcExpr.nodeType === ParseNodeType.Call) { - const callType = getTypeOfExpression(srcExpr.d.leftExpr, EvalFlags.CallBaseDefaults).type; - if ( - isInstantiableClass(callType) && - (ClassType.isBuiltIn(callType, 'TypeVar') || - ClassType.isBuiltIn(callType, 'TypeVarTuple') || - ClassType.isBuiltIn(callType, 'ParamSpec')) - ) { - const typeVarTarget = - target.nodeType === ParseNodeType.TypeAnnotation ? target.d.valueExpr : target; - if ( - typeVarTarget.nodeType !== ParseNodeType.Name || - typeVarTarget.d.value !== typeResult.type.shared.name - ) { - const name = TypeVarType.getReadableName(typeResult.type); - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - isParamSpec(typeResult.type) - ? LocMessage.paramSpecAssignedName().format({ name }) - : LocMessage.typeVarAssignedName().format({ name }), - typeVarTarget - ); - } - } - } - } - - // If the type was partially unbound, an error will have already been logged. - // Remove the unbound before assigning to the target expression so the unbound - // error doesn't propagate. - if (findSubtype(typeResult.type, (subtype) => isUnbound(subtype))) { - typeResult = { ...typeResult, type: removeUnbound(typeResult.type) }; - } - - switch (target.nodeType) { - case ParseNodeType.Name: { - assignTypeToNameNode( - target, - typeResult, - ignoreEmptyContainers, - srcExpr, - allowAssignmentToFinalVar, - expectedTypeDiagAddendum - ); - break; - } - - case ParseNodeType.MemberAccess: { - assignTypeToMemberAccessNode(target, typeResult, srcExpr, expectedTypeDiagAddendum); - break; - } - - case ParseNodeType.Index: { - const baseTypeResult = getTypeOfExpression(target.d.leftExpr, EvalFlags.IndexBaseDefaults); - - getTypeOfIndexWithBaseType( - target, - baseTypeResult, - { - method: 'set', - setType: typeResult, - setErrorNode: srcExpr, - setExpectedTypeDiag: expectedTypeDiagAddendum, - }, - EvalFlags.None - ); - - writeTypeCache(target, typeResult, EvalFlags.None); - break; - } - - case ParseNodeType.List: - case ParseNodeType.Tuple: { - assignTypeToTupleOrListNode(target, typeResult, srcExpr); - break; - } - - case ParseNodeType.TypeAnnotation: { - getTypeOfAnnotation(target.d.annotation, { - varTypeAnnotation: true, - allowFinal: isFinalAllowedForAssignmentTarget(target.d.valueExpr), - allowClassVar: isClassVarAllowedForAssignmentTarget(target.d.valueExpr), - }); - - assignTypeToExpression( - target.d.valueExpr, - typeResult, - srcExpr, - ignoreEmptyContainers, - allowAssignmentToFinalVar, - expectedTypeDiagAddendum - ); - break; - } - - case ParseNodeType.Unpack: { - assignTypeToExpression( - target.d.expr, - { - type: getBuiltInObject(target.d.expr, 'list', [typeResult.type]), - isIncomplete: typeResult.isIncomplete, - }, - srcExpr, - ignoreEmptyContainers, - allowAssignmentToFinalVar, - expectedTypeDiagAddendum - ); - break; - } - - case ParseNodeType.Error: { - // Evaluate the child expression as best we can so the - // type information is cached for the completion handler. - if (target.d.child) { - suppressDiagnostics(target.d.child, () => { - getTypeOfExpression(target.d.child!); - }); - } - break; - } - - default: { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.assignmentTargetExpr(), target); - break; - } - } - } - - function isClassVarAllowedForAssignmentTarget(targetNode: ExpressionNode): boolean { - // ClassVar is allowed only in a class body. - const classNode = ParseTreeUtils.getEnclosingClass(targetNode, /* stopAtFunction */ true); - if (!classNode) { - return false; - } - - // ClassVar is not allowed in a TypedDict or a NamedTuple class. - return !isInTypedDictOrNamedTuple(classNode); - } - - function isFinalAllowedForAssignmentTarget(targetNode: ExpressionNode): boolean { - const classNode = ParseTreeUtils.getEnclosingClass(targetNode, /* stopAtFunction */ true); - - // Final is not allowed in the body of a TypedDict or NamedTuple class. - if (classNode && isInTypedDictOrNamedTuple(classNode)) { - return false; - } - - return ParseTreeUtils.isFinalAllowedForAssignmentTarget(targetNode); - } - - function isInTypedDictOrNamedTuple(classNode: ClassNode): boolean { - const classType = getTypeOfClass(classNode)?.classType; - if (!classType) { - return false; - } - - return ClassType.isTypedDictClass(classType) || !!classType.shared.namedTupleEntries; - } - - function verifyRaiseExceptionType(node: ExpressionNode, allowNone: boolean) { - const baseExceptionType = getBuiltInType(node, 'BaseException'); - const exceptionType = getTypeOfExpression(node).type; - - // Validate that the argument of "raise" is an exception object or class. - // If it is a class, validate that the class's constructor accepts zero - // arguments. - if (exceptionType && baseExceptionType && isInstantiableClass(baseExceptionType)) { - const diag = new DiagnosticAddendum(); - - doForEachSubtype(exceptionType, (subtype) => { - const concreteSubtype = makeTopLevelTypeVarsConcrete(subtype); - - if (isAnyOrUnknown(concreteSubtype) || isNever(concreteSubtype)) { - return; - } - - if (allowNone && isNoneInstance(concreteSubtype)) { - return; - } - - if (isInstantiableClass(concreteSubtype) && concreteSubtype.priv.literalValue === undefined) { - if (!derivesFromClassRecursive(concreteSubtype, baseExceptionType, /* ignoreUnknown */ false)) { - diag.addMessage( - LocMessage.exceptionTypeIncorrect().format({ - type: printType(subtype), - }) - ); - } else { - let callResult: CallResult | undefined; - suppressDiagnostics(node, () => { - callResult = validateConstructorArgs( - evaluatorInterface, - node, - [], - concreteSubtype, - /* skipUnknownArgCheck */ false, - /* inferenceContext */ undefined - ); - }); - - if (callResult && callResult.argumentErrors) { - diag.addMessage( - LocMessage.exceptionTypeNotInstantiable().format({ - type: printType(subtype), - }) - ); - } - } - } else if (isClassInstance(concreteSubtype)) { - if ( - !derivesFromClassRecursive( - ClassType.cloneAsInstantiable(concreteSubtype), - baseExceptionType, - /* ignoreUnknown */ false - ) - ) { - diag.addMessage( - LocMessage.exceptionTypeIncorrect().format({ - type: printType(subtype), - }) - ); - } - } else { - diag.addMessage( - LocMessage.exceptionTypeIncorrect().format({ - type: printType(subtype), - }) - ); - } - }); - - if (!diag.isEmpty()) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.expectedExceptionClass() + diag.getString(), - node - ); - } - } - } - - function verifyDeleteExpression(node: ExpressionNode) { - switch (node.nodeType) { - case ParseNodeType.Name: { - // Get the type to evaluate whether it's bound - // and to mark it accessed. - getTypeOfExpression(node); - break; - } - - case ParseNodeType.MemberAccess: { - const baseTypeResult = getTypeOfExpression(node.d.leftExpr, EvalFlags.MemberAccessBaseDefaults); - const delAccessResult = getTypeOfMemberAccessWithBaseType( - node, - baseTypeResult, - { method: 'del' }, - EvalFlags.None - ); - const resultToCache: TypeResult = { - type: delAccessResult.type, - memberAccessDeprecationInfo: delAccessResult.memberAccessDeprecationInfo, - }; - writeTypeCache(node.d.member, resultToCache, EvalFlags.None); - writeTypeCache(node, resultToCache, EvalFlags.None); - break; - } - - case ParseNodeType.Index: { - const baseTypeResult = getTypeOfExpression(node.d.leftExpr, EvalFlags.IndexBaseDefaults); - getTypeOfIndexWithBaseType(node, baseTypeResult, { method: 'del' }, EvalFlags.None); - writeTypeCache(node, { type: UnboundType.create() }, EvalFlags.None); - break; - } - - case ParseNodeType.Tuple: { - node.d.items.forEach((expr) => { - verifyDeleteExpression(expr); - }); - break; - } - - case ParseNodeType.Error: { - // Evaluate the child expression as best we can so the - // type information is cached for the completion handler. - if (node.d.child) { - suppressDiagnostics(node.d.child, () => { - getTypeOfExpression(node.d.child!); - }); - } - break; - } - - default: { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.delTargetExpr(), node); - break; - } - } - } - - function setSymbolAccessed(fileInfo: AnalyzerFileInfo, symbol: Symbol, node: ParseNode) { - if (!isSpeculativeModeInUse(node)) { - fileInfo.accessedSymbolSet.add(symbol.id); - } - } - - function getTypeOfName(node: NameNode, flags: EvalFlags): TypeResult { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - const name = node.d.value; - let symbol: Symbol | undefined; - let type: Type | undefined; - let isIncomplete = false; - const allowForwardReferences = (flags & EvalFlags.ForwardRefs) !== 0 || fileInfo.isStubFile; - - // Look for the scope that contains the value definition and - // see if it has a declared type. - let symbolWithScope = lookUpSymbolRecursive( - node, - name, - !allowForwardReferences, - allowForwardReferences && (flags & EvalFlags.TypeExpression) !== 0 - ); - - if (!symbolWithScope) { - // If the node is part of a "from X import Y as Z" statement and the node - // is the "Y" (non-aliased) name, we need to look up the alias symbol - // since the non-aliased name is not in the symbol table. - const alias = getAliasFromImport(node); - if (alias) { - symbolWithScope = lookUpSymbolRecursive( - alias, - alias.d.value, - !allowForwardReferences, - allowForwardReferences && (flags & EvalFlags.TypeExpression) !== 0 - ); - } - } - - if (symbolWithScope) { - let useCodeFlowAnalysis = !allowForwardReferences; - - // If the symbol is implicitly imported from the builtin - // scope, there's no need to use code flow analysis. - if (symbolWithScope.scope.type === ScopeType.Builtin) { - useCodeFlowAnalysis = false; - - // Viper 扩展:模拟编译器的自动 import 机制。 - // Viper 编译器的 Phase2 会自动检测 list/dict 容器的使用并注入 - // `import _list` / `import _dict`(见 TransPyC Phase2Translator._inject_auto_imports), - // 因此用户源码无需手写 import。 - // LSP 若不模拟此机制,裸名 list/dict 会解析到 Python builtins 的 - // `list.__init__(iterable: Iterable[_T])`,把内存分配器实参(如 - // memhub.MemBuddy)当作可迭代对象检查而误报。 - // 这里在解析到 builtin 的 list/dict 时,尝试用 includes 中的 - // _list.vp / _dict.vp 真实定义替换(容器构造签名是 - // `__init__(self, pool: memhub.MemManager | t.CPtr, ...)`)。 - const viperBuiltinModule = - IsViperDialect(fileInfo.executionEnvironment.dialect) && - (name === 'list' || name === 'dict') - ? (name === 'list' ? '_list' : '_dict') - : undefined; - if (viperBuiltinModule) { - const lookupResult = importLookup({ - nameParts: [viperBuiltinModule], - importingFileUri: fileInfo.fileUri, - }); - if (lookupResult) { - const containerSymbol = lookupResult.symbolTable.get(name); - if (containerSymbol) { - symbolWithScope = { - symbol: containerSymbol, - isOutsideCallerModule: false, - isBeyondExecutionScope: false, - usesNonlocalBinding: false, - usesGlobalBinding: false, - scope: symbolWithScope.scope, - }; - } - } - } - } - - symbol = symbolWithScope.symbol; - setSymbolAccessed(fileInfo, symbol, node); - - // If we're not supposed to be analyzing this function, skip the remaining work - // to determine the name's type. Simply evaluate its type as Any. - if (!fileInfo.diagnosticRuleSet.analyzeUnannotatedFunctions) { - const containingFunction = ParseTreeUtils.getEnclosingFunction(node); - if (containingFunction && ParseTreeUtils.isUnannotatedFunction(containingFunction)) { - return { - type: AnyType.create(), - isIncomplete: false, - }; - } - } - - // Get the effective type (either the declared type or the inferred type). - // If we're using code flow analysis, pass the usage node so we consider - // only the assignment nodes that are reachable from this usage. - const effectiveTypeInfo = getEffectiveTypeOfSymbolForUsage(symbol, useCodeFlowAnalysis ? node : undefined); - let effectiveType = transformPossibleRecursiveTypeAlias(effectiveTypeInfo.type); - - if (effectiveTypeInfo.isIncomplete) { - if (isUnbound(effectiveType)) { - effectiveType = UnknownType.create(/* isIncomplete */ true); - } - isIncomplete = true; - } - - // Viper 扩展:带类型注解的变量,若声明类型求值失败返回 UnboundType, - // 转换为 UnknownType 避免误报 unbound。 - // Viper 是 AOT 静态语言,变量有显式类型注解即视为已声明绑定, - // 即使类型注解中引用了未解析的前向符号(如 uhci_td | t.CPtr 中 - // uhci_td 尚未定义),变量本身也已绑定,不应报告 unbound。 - if ( - !effectiveTypeInfo.isIncomplete && - isUnbound(effectiveType) && - IsViperDialect(fileInfo.executionEnvironment.dialect) && - symbol.hasTypedDeclarations() - ) { - effectiveType = UnknownType.create(); - } - - if (effectiveTypeInfo.isRecursiveDefinition && isNodeReachable(node)) { - // Viper 扩展:循环引用包含 CPtr 时不报错。 - // C 语义中,struct 含指向自身的指针是合法的(如链表节点): - // struct Node { struct Node* next; } - // 对应 Viper: next: 'Node' | t.CPtr - // 指针大小固定,不需要知道结构体的完整定义,打破了无限递归。 - // 但无 CPtr 的循环引用(如 next: 'Node')是真正的无限递归,应报错。 - const isViperPtrCycle = - IsViperDialect(fileInfo.executionEnvironment.dialect) && - symbol.getTypedDeclarations().some((decl) => { - if (decl.type !== DeclarationType.Variable || !decl.typeAnnotationNode) { - return false; - } - return typeAnnotationContainsCPtr(decl.typeAnnotationNode); - }); - - if (!isViperPtrCycle) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.recursiveDefinition().format({ name }), - node - ); - } - } - - const isSpecialBuiltIn = - !!effectiveType && isInstantiableClass(effectiveType) && ClassType.isSpecialBuiltIn(effectiveType); - - type = effectiveType; - if (useCodeFlowAnalysis && !isSpecialBuiltIn) { - // See if code flow analysis can tell us anything more about the type. - // If the symbol is declared outside of our execution scope, use its effective - // type. If it's declared inside our execution scope, it generally starts - // as unbound at the start of the code flow. - let typeAtStart = effectiveType; - let isTypeAtStartIncomplete = false; - - if (!symbolWithScope.isBeyondExecutionScope && symbol.isInitiallyUnbound()) { - // Viper 扩展:在 Viper 方言下,带类型注解的变量声明 - // (如 `ncpu_cr: t.CArray[t.CChar, 32]`)视为栈上分配, - // 变量已"绑定"到其声明类型,不应标记为 unbound。 - // 保持 typeAtStart = effectiveType(声明的类型注解)。 - const isViperTypedDecl = - IsViperDialect(fileInfo.executionEnvironment.dialect) && - symbol.hasTypedDeclarations(); - - if (!isViperTypedDecl) { - typeAtStart = UnboundType.create(); - - // Viper 扩展:函数内局部变量未绑定时,回退到模块作用域的全局变量。 - // Viper 语义:读取全局变量不需要 global 声明,仅写入需要。 - // 当绑定器因函数内赋值将变量视为局部变量,但读取发生在赋值之前时, - // 应解析到同名的全局变量而非报 unbound 错误。 - if ( - IsViperDialect(fileInfo.executionEnvironment.dialect) && - symbolWithScope.scope.type !== ScopeType.Module - ) { - const globalScopeResult = symbolWithScope.scope.getGlobalScope(); - if (globalScopeResult) { - const globalSymbol = globalScopeResult.scope.lookUpSymbol(name); - if (globalSymbol) { - const globalEffectiveType = getEffectiveTypeOfSymbolForUsage(globalSymbol); - typeAtStart = globalEffectiveType.type; - } - } - } - - // Is this a module-level scope? If so, see if it's an alias of a builtin. - if (symbolWithScope.scope.type === ScopeType.Module) { - assert(symbolWithScope.scope.parent); - const builtInSymbol = symbolWithScope.scope.parent.lookUpSymbol(name); - if (builtInSymbol) { - const builtInEffectiveType = getEffectiveTypeOfSymbolForUsage(builtInSymbol); - typeAtStart = builtInEffectiveType.type; - } - } - } - } - - if (symbolWithScope.isBeyondExecutionScope) { - const outerScopeTypeResult = getCodeFlowTypeForCapturedVariable( - node, - symbolWithScope, - effectiveType - ); - - if (outerScopeTypeResult?.type) { - type = outerScopeTypeResult.type; - typeAtStart = type; - isTypeAtStartIncomplete = !!outerScopeTypeResult.isIncomplete; - } - } - - const codeFlowTypeResult = getFlowTypeOfReference(node, /* startNode */ undefined, { - targetSymbolId: symbol.id, - typeAtStart: { type: typeAtStart, isIncomplete: isTypeAtStartIncomplete }, - skipConditionalNarrowing: (flags & EvalFlags.TypeExpression) !== 0, - }); - - if (codeFlowTypeResult.type) { - type = codeFlowTypeResult.type; - } - - // Viper 扩展:带类型注解的变量保持声明类型(C 静态类型语义)。 - // 在 Viper 方言下,变量类型在声明时确定,不应被赋值收窄。 - // 例如 `s: Scheduler | t.CPtr = _sched_ptr` 应保持声明类型 - // `Scheduler | t.CPtr`,不被 `_sched_ptr` 的推断类型收窄。 - if ( - IsViperDialect(fileInfo.executionEnvironment.dialect) && - symbol.hasTypedDeclarations() && - effectiveType && - !isUnbound(effectiveType) - ) { - type = effectiveType; - } - - if (codeFlowTypeResult.isIncomplete) { - isIncomplete = true; - } - } - - // Detect, report, and fill in missing type arguments if appropriate. - type = reportMissingTypeArgs(node, type, flags); - - // Report inappropriate use of variables in type expressions. - if ((flags & EvalFlags.TypeExpression) !== 0) { - type = validateSymbolIsTypeExpression(node, type, !!effectiveTypeInfo.includesVariableDecl); - } - - if (isTypeVar(type) && !type.shared.isSynthesized) { - type = validateTypeVarUsage(node, type, flags); - } - - // Add TypeForm details if appropriate. - type = addTypeFormForSymbol(node, type, flags, !!effectiveTypeInfo.includesVariableDecl); - } else { - // Handle the special case of "reveal_type" and "reveal_locals". - if (name === 'reveal_type' || name === 'reveal_locals') { - type = AnyType.create(); - } else { - // Viper 扩展:前向引用支持。 - // Viper 是 AOT 语言,结构体/函数不在乎定义顺序(类似 C 语言的前向声明)。 - // 当符号未找到时(可能在更后方定义),不报 undefinedVariable 错误, - // 而是返回 UnknownType,允许后续定义的符号被引用。 - type = UnknownType.create(); - } - } - - if (isParamSpec(type) && type.priv.scopeId) { - if (flags & EvalFlags.NoParamSpec) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.paramSpecContext(), node); - type = UnknownType.create(); - } - } - - // If we're expecting a type expression and got a sentinel literal instance, - // treat it as its instantiable counterpart. This is similar to how None - // is treated in a type expression context. - if ((flags & EvalFlags.InstantiableType) !== 0 && isClassInstance(type) && isSentinelLiteral(type)) { - type = ClassType.cloneAsInstantiable(type); - } - - type = convertSpecialFormToRuntimeValue(type, flags); - - if ((flags & EvalFlags.TypeExpression) === 0) { - reportUseOfTypeCheckOnly(type, node); - } - - if ((flags & EvalFlags.InstantiableType) !== 0) { - if ((flags & EvalFlags.AllowGeneric) === 0) { - if (isInstantiableClass(type) && ClassType.isBuiltIn(type, 'Generic')) { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.genericNotAllowed(), node); - } - } - } - - return { type, isIncomplete }; - } - - function addTypeFormForSymbol(node: ExpressionNode, type: Type, flags: EvalFlags, includesVarDecl: boolean): Type { - const isValid = isSymbolValidTypeExpression(type, includesVarDecl); - - // If the type already has type information associated with it, don't replace. - if (type.props?.typeForm) { - // If the NoConvertSpecialForm flag is set, we are evaluating in - // the interior of a type expression, so variables are not allowed. - // Clear any existing type form type for this symbol in this case. - if ((flags & EvalFlags.NoConvertSpecialForm) !== 0 && !isValid) { - type = TypeBase.cloneWithTypeForm(type, undefined); - } - return type; - } - - // If the symbol is not valid for a type expression (e.g. it's a variable), - // don't add TypeForm info. - if (!isValid) { - return type; - } - - if (isTypeVar(type) && type.priv.scopeId && !type.shared.isSynthesized) { - if (!isTypeVarTuple(type) || !type.priv.isInUnion) { - const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(node); - type = TypeBase.cloneWithTypeForm(type, convertToInstance(makeTypeVarsBound(type, liveScopeIds))); - } - } else if (isInstantiableClass(type) && !type.priv.includeSubclasses && !ClassType.isSpecialBuiltIn(type)) { - if (ClassType.isBuiltIn(type, 'Any')) { - type = TypeBase.cloneWithTypeForm(type, AnyType.create()); - } else { - type = TypeBase.cloneWithTypeForm(type, ClassType.cloneAsInstance(specializeWithDefaultTypeArgs(type))); - } - } - - if (type.props?.typeAliasInfo && TypeBase.isInstantiable(type)) { - let typeFormType = type; - if ((flags & EvalFlags.NoSpecialize) === 0) { - typeFormType = specializeTypeAliasWithDefaults(typeFormType, /* errorNode */ undefined); - } - - type = TypeBase.cloneWithTypeForm(type, convertToInstance(typeFormType)); - } - - return type; - } - - function isSymbolValidTypeExpression(type: Type, includesVarDecl: boolean): boolean { - // Verify that the name does not refer to a (non type alias) variable. - if (!includesVarDecl || type.props?.typeAliasInfo) { - return true; - } - - if (isTypeAliasPlaceholder(type)) { - return true; - } - - if (isTypeVar(type)) { - if (type.props?.specialForm || type.props?.typeAliasInfo) { - return true; - } - } - - // Exempts class types that are created by calling NewType, NamedTuple, etc. - if (isClass(type) && !type.priv.includeSubclasses && ClassType.isValidTypeAliasClass(type)) { - return true; - } - - if (isSentinelLiteral(type)) { - return true; - } - - // Viper 扩展:t.CDefine 常量允许在类型表达式中使用。 - // CDefine 是编译时常量(类似 C 的 #define),在 Viper 中可作为 - // 数组长度等类型参数使用,如: - // UHCI_INT_SLOTS: t.CDefine = 4 - // _int_slots: t.CArray[int_slot, UHCI_INT_SLOTS] - if (isCDefineType(type)) { - return true; - } - - // Viper 扩展:整数字面量允许在类型表达式中使用。 - // Viper 中 CArray[T, N] 的 N 是数组长度(整数字面量), - // 如 CArray[t.CUInt32T, 6]。 - if (isClassInstance(type) && isLiteralType(type)) { - return true; - } - - return false; - } - - // Reports diagnostics if type isn't valid within a type expression. - function validateSymbolIsTypeExpression(node: ExpressionNode, type: Type, includesVarDecl: boolean): Type { - if (isSymbolValidTypeExpression(type, includesVarDecl)) { - return type; - } - - // Disable for assignments in the typings.pyi file, since it defines special forms. - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - if (fileInfo.isTypingStubFile) { - return type; - } - - // Viper 扩展:Viper 是 AOT 静态语言,类型系统比 Python 更灵活。 - // 跨模块 import 的 typedef、TypeSet、前向引用等场景下, - // 类型求值可能返回 Unknown、Union、类实例等非标准类型表达式类型。 - // 在 Viper 模式下,不报告 "Variable not allowed in type expression" 错误。 - if (IsViperDialect(fileInfo.executionEnvironment.dialect)) { - return type; - } - - // Viper 扩展(已废弃,保留注释):TypeSet(联合类型)允许作为类型表达式。 - // Viper 中 `|` 表示类型组合(TypeSet),如 `CUInt32T | CPtr` 表示 uint32_t*。 - // 类型别名(如 UINT32PTR: t.CTypedef = t.CUInt32T | t.CPtr)的值是联合类型, - // 在 Viper 模式下应允许作为类型表达式使用。 - if ( - IsViperDialect(fileInfo.executionEnvironment.dialect) && - isUnion(type) - ) { - return type; - } - - // Viper 扩展:Unknown 类型在 Viper 模式下允许作为类型表达式。 - // 跨模块 import 的 typedef(如 from ... import * 后使用 UINT32PTR) - // 可能因 resolveAliasDeclaration 解析失败而返回 Unknown。 - // Viper 是 AOT 语言,前向引用和跨模块引用是合法的, - // 不应因类型求值失败而报 "Variable not allowed in type expression"。 - if ( - IsViperDialect(fileInfo.executionEnvironment.dialect) && - isUnknown(type) - ) { - return type; - } - - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeAnnotationVariable(), node); - return UnknownType.create(); - } - - // If the value is a special form (like a TypeVar or `Any`) and is being - // evaluated in a value expression context, convert it from its special - // meaning to its runtime value. If convertModule is true, a module is - // converted to an instance of types.ModuleType. - function convertSpecialFormToRuntimeValue(type: Type, flags: EvalFlags, convertModule = false) { - const exemptFlags = EvalFlags.TypeExpression | EvalFlags.InstantiableType | EvalFlags.NoConvertSpecialForm; - - if ((flags & exemptFlags) !== 0) { - return type; - } - - if ( - convertModule && - isModule(type) && - prefetched?.moduleTypeClass && - isInstantiableClass(prefetched.moduleTypeClass) - ) { - return ClassType.cloneAsInstance(prefetched.moduleTypeClass); - } - - // Isinstance treats traditional (non-PEP 695) type aliases that are unions - // as tuples of classes rather than unions. - if ((flags & EvalFlags.IsinstanceArg) !== 0) { - if (isUnion(type) && type.props?.typeAliasInfo && !type.props.typeAliasInfo.shared.isTypeAliasType) { - return type; - } - } - - if (!type.props?.specialForm) { - return type; - } - - // If this is a type alias and we are not supposed to specialize it, return it as is. - if ((flags & EvalFlags.NoSpecialize) !== 0 && type.props?.typeAliasInfo) { - // Special-case TypeAliasType which should be converted in this case. - if (!ClassType.isBuiltIn(type.props.specialForm, 'TypeAliasType')) { - return type; - } - } - - if (type.props?.typeForm) { - return TypeBase.cloneWithTypeForm(type.props.specialForm, type.props.typeForm); - } - - return type.props.specialForm; - } - - // Handles the case where a variable or parameter is defined in an outer - // scope and captured by an inner scope (a function, lambda, or comprehension). - function getCodeFlowTypeForCapturedVariable( - node: NameNode, - symbolWithScope: SymbolWithScope, - effectiveType: Type - ): FlowNodeTypeResult | undefined { - // This function applies only to captured variables, not those that - // are accessed via an explicit nonlocal or global binding. - if (symbolWithScope.usesGlobalBinding || symbolWithScope.usesNonlocalBinding) { - return undefined; - } - - // This function applies only to variables, parameters, and imports, not to other - // types of symbols. - const decls = symbolWithScope.symbol.getDeclarations(); - if ( - !decls.every( - (decl) => - decl.type === DeclarationType.Variable || - decl.type === DeclarationType.Param || - decl.type === DeclarationType.Alias - ) - ) { - return undefined; - } - - // If the symbol is modified in scopes other than the one in which it is - // declared (e.g. through a nonlocal or global binding), it is not eligible - // for code flow analysis. - if ( - !decls.every( - (decl) => - decl.type === DeclarationType.Param || - ScopeUtils.getScopeForNode(decl.node) === symbolWithScope.scope - ) - ) { - return undefined; - } - - // If the symbol is a non-final variable in the global scope, it is not - // eligible because it could be modified by other modules. - if ( - !decls.every( - (decl) => - decl.type !== DeclarationType.Variable || - decl.isFinal || - ScopeUtils.getScopeForNode(decl.node)?.type !== ScopeType.Module - ) - ) { - return undefined; - } - - // If the symbol is a variable captured by an inner function - // or lambda, see if we can infer the type from the outer scope. - const scopeHierarchy = ScopeUtils.getScopeHierarchy(node, symbolWithScope.scope); - - if (scopeHierarchy && scopeHierarchy.length >= 2) { - // Find the parse node associated with the scope that is just inside of the - // scope that declares the captured variable. - const innerScopeNode = ScopeUtils.findTopNodeInScope(node, scopeHierarchy[scopeHierarchy.length - 2]); - if ( - innerScopeNode?.nodeType === ParseNodeType.Function || - innerScopeNode?.nodeType === ParseNodeType.Lambda || - innerScopeNode?.nodeType === ParseNodeType.Class - ) { - const innerScopeCodeFlowNode = AnalyzerNodeInfo.getFlowNode(innerScopeNode); - if (innerScopeCodeFlowNode) { - // See if any of the assignments of the symbol are reachable - // from this node. If so, we cannot apply any narrowing because - // the type could change after the capture. - if ( - symbolWithScope.symbol.getDeclarations().every((decl) => { - // Parameter declarations always start life at the beginning - // of the execution scope, so they are always safe to narrow. - if (decl.type === DeclarationType.Param) { - return true; - } - - const declCodeFlowNode = AnalyzerNodeInfo.getFlowNode(decl.node); - if (!declCodeFlowNode) { - return false; - } - - return ( - codeFlowEngine.getFlowNodeReachability( - declCodeFlowNode, - innerScopeCodeFlowNode, - /* ignoreNoReturn */ true - ) !== Reachability.Reachable - ); - }) - ) { - let typeAtStart = effectiveType; - // Viper 扩展:带类型注解的变量声明视为栈上分配,保持声明类型。 - const capturedFileInfo = AnalyzerNodeInfo.getFileInfo(node); - const isViperTypedDecl = - IsViperDialect(capturedFileInfo.executionEnvironment.dialect) && - symbolWithScope.symbol.hasTypedDeclarations(); - if (symbolWithScope.symbol.isInitiallyUnbound() && !isViperTypedDecl) { - typeAtStart = UnboundType.create(); - } - - return getFlowTypeOfReference(node, innerScopeNode, { - targetSymbolId: symbolWithScope.symbol.id, - typeAtStart: { type: typeAtStart }, - }); - } - } - } - } - - return undefined; - } - - // Validates that a TypeVar is valid in this context. If so, it clones it - // and provides a scope ID defined by its containing scope (class, function - // or type alias). If not, it emits errors indicating why the TypeVar - // cannot be used in this location. - function validateTypeVarUsage(node: ExpressionNode, type: TypeVarType, flags: EvalFlags) { - if (!TypeBase.isInstantiable(type) || isTypeAliasPlaceholder(type)) { - return type; - } - - // If the TypeVar doesn't have a scope ID, try to assign one. - if (!type.priv.scopeId) { - type = assignTypeVarScopeId(node, type, flags); - } - - // If this is a free type var, see if we need to make it into a bound type var. - if (type.priv.scopeId && !TypeVarType.isBound(type)) { - // If this is a reference to a TypeVar defined in an outer scope, - // mark it as bound. - const scopedNode = findScopedTypeVar(node, type)?.scopeNode; - - if (scopedNode) { - const enclosingSuite = ParseTreeUtils.getEnclosingClassOrFunctionSuite(node); - - if (enclosingSuite && ParseTreeUtils.isNodeContainedWithin(enclosingSuite, scopedNode)) { - if (scopedNode.nodeType !== ParseNodeType.Class || scopedNode.d.suite !== enclosingSuite) { - type = TypeVarType.cloneAsBound(type); - } - } - } - } - - // If this is a TypeVarTuple, the name refers to the packed form. It - // must be unpacked in most contexts. - if (isUnpackedTypeVarTuple(type)) { - type = TypeVarType.cloneForPacked(type); - } - - if ((flags & EvalFlags.EnforceClassTypeVarScope) !== 0 && !enforceClassTypeVarScope(node, type)) { - return UnknownType.create(); - } - - return type; - } - - function assignTypeVarScopeId(node: ExpressionNode, type: TypeVarType, flags: EvalFlags): TypeVarType { - const scopedTypeVarInfo = findScopedTypeVar(node, type); - type = scopedTypeVarInfo.type; - - if ((flags & EvalFlags.NoTypeVarWithScopeId) !== 0 && !!type.priv.scopeId) { - if (type.shared.isSynthesized || isParamSpec(type)) { - return type; - } - - // This TypeVar already has a scope ID assigned to it. See if it - // originates from type parameter syntax. If so, allow it. - if (type.shared.isTypeParamSyntax) { - return type; - } - - // If this type variable expression is used within a generic class, - // function, or type alias that uses type parameter syntax, there is - // no need to report an error here. - const typeVarScopeNode = ParseTreeUtils.getTypeVarScopeNode(node); - if ( - typeVarScopeNode && - typeVarScopeNode.d.typeParams && - !typeVarScopeNode.d.typeParams.d.params.some((t) => t.d.name === node) - ) { - return type; - } - - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarUsedByOuterScope().format({ name: type.shared.name }), - node - ); - - return type; - } - - if ((flags & EvalFlags.TypeVarGetsCurScope) !== 0) { - if (type.priv.scopeId) { - return type; - } - - if (scopedTypeVarInfo.foundInterveningClass) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarUsedByOuterScope().format({ name: type.shared.name }), - node - ); - return type; - } - - let enclosingScope = ParseTreeUtils.getEnclosingClassOrFunction(node); - - // Handle P.args and P.kwargs as a special case for inner functions. - if ( - enclosingScope && - node.parent?.nodeType === ParseNodeType.MemberAccess && - node.parent.d.leftExpr === node - ) { - const memberName = node.parent.d.member.d.value; - if (memberName === 'args' || memberName === 'kwargs') { - const outerFunctionScope = ParseTreeUtils.getEnclosingClassOrFunction(enclosingScope); - - if (outerFunctionScope?.nodeType === ParseNodeType.Function) { - enclosingScope = outerFunctionScope; - } else if (!scopedTypeVarInfo.type.priv.scopeId) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.paramSpecNotUsedByOuterScope().format({ - name: type.shared.name, - }), - node - ); - } - } - } - - if (!enclosingScope) { - fail('AssociateTypeVarsWithCurrentScope flag was set but enclosing scope not found'); - } - - // If the enclosing scope is using type parameter syntax, traditional - // type variables can't be used in this context. - if ( - enclosingScope.d.typeParams && - !enclosingScope.d.typeParams.d.params.some((param) => param.d.name.d.value === type.shared.name) - ) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeParameterNotDeclared().format({ - name: type.shared.name, - container: enclosingScope.d.name.d.value, - }), - node - ); - } - - const scopeIdToAssign = ParseTreeUtils.getScopeIdForNode(enclosingScope); - - return TypeVarType.cloneForScopeId( - type, - scopeIdToAssign, - enclosingScope.d.name.d.value, - enclosingScope.nodeType === ParseNodeType.Function ? TypeVarScopeType.Function : TypeVarScopeType.Class - ); - } - - if ((flags & EvalFlags.AllowTypeVarWithoutScopeId) === 0) { - if (type.priv.scopeId && !scopedTypeVarInfo.foundInterveningClass) { - return type; - } - - if (!type.shared.isSynthesized && (flags & EvalFlags.InstantiableType) !== 0) { - const message = isParamSpec(type) - ? LocMessage.paramSpecNotUsedByOuterScope() - : LocMessage.typeVarNotUsedByOuterScope(); - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, message.format({ name: type.shared.name }), node); - } - } - - return type; - } - - // Enforce that the type variable is scoped to the enclosing class or - // an outer class that contains the class definition. - function enforceClassTypeVarScope(node: ExpressionNode, type: TypeVarType): boolean { - const scopeId = type.priv.freeTypeVar?.priv.scopeId ?? type.priv.scopeId; - if (!scopeId) { - return true; - } - - const enclosingClass = ParseTreeUtils.getEnclosingClass(node); - if (enclosingClass) { - const liveTypeVarScopeIds = ParseTreeUtils.getTypeVarScopesForNode(enclosingClass); - if (!liveTypeVarScopeIds.includes(scopeId)) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarInvalidForMemberVariable().format({ - name: TypeVarType.getReadableName(type), - }), - node - ); - - return false; - } - } - - return true; - } - - // Determines if the type is a generic class or type alias with missing - // type arguments. If so, it fills in these type arguments with Unknown - // and optionally reports an error. - function reportMissingTypeArgs(node: ExpressionNode, type: Type, flags: EvalFlags): Type { - if ((flags & EvalFlags.NoSpecialize) !== 0) { - return type; - } - - // Is this a generic class that needs to be specialized? - if (isInstantiableClass(type)) { - if ((flags & EvalFlags.InstantiableType) !== 0 && (flags & EvalFlags.AllowMissingTypeArgs) === 0) { - if (!type.props?.typeAliasInfo && requiresTypeArgs(type)) { - if (!type.priv.typeArgs || !type.priv.isTypeArgExplicit) { - addDiagnostic( - DiagnosticRule.reportMissingTypeArgument, - LocMessage.typeArgsMissingForClass().format({ - name: type.priv.aliasName || type.shared.name, - }), - node - ); - } - } - } - - if (!type.priv.typeArgs) { - type = createSpecializedClassType(type, /* typeArgs */ undefined, flags, node)?.type; - } - } - - // Is this a generic type alias that needs to be specialized? - if ((flags & EvalFlags.InstantiableType) !== 0) { - type = specializeTypeAliasWithDefaults(type, node); - } - - return type; - } - - // Walks up the parse tree to find a function, class, or type alias - // declaration that provides the context for a type variable. - function findScopedTypeVar(node: ExpressionNode, type: TypeVarType): ScopedTypeVarResult { - let curNode: ParseNode | undefined = node; - let nestedClassCount = 0; - - assert(TypeBase.isInstantiable(type)); - - while (curNode) { - const scopeNode = ParseTreeUtils.getTypeVarScopeNode(curNode); - if (!scopeNode) { - break; - } - curNode = scopeNode; - - let typeParamsForScope: TypeVarType[] | undefined; - let scopeUsesTypeParamSyntax = false; - - if (curNode.nodeType === ParseNodeType.Class) { - const classTypeInfo = getTypeOfClass(curNode); - if (classTypeInfo && !ClassType.isPartiallyEvaluated(classTypeInfo.classType)) { - typeParamsForScope = classTypeInfo.classType.shared.typeParams; - } - - scopeUsesTypeParamSyntax = !!curNode.d.typeParams; - nestedClassCount++; - } else if (curNode.nodeType === ParseNodeType.Function) { - const functionType = getTypeOfFunctionPredecorated(curNode); - if (functionType) { - const functionDetails = functionType.shared; - typeParamsForScope = functionDetails.typeParams; - } - - scopeUsesTypeParamSyntax = !!curNode.d.typeParams; - } else if (curNode.nodeType === ParseNodeType.TypeAlias) { - scopeUsesTypeParamSyntax = !!curNode.d.typeParams; - } - - if (typeParamsForScope) { - const match = typeParamsForScope.find((typeVar) => typeVar.shared.name === type.shared.name); - - if ( - match?.priv.scopeId !== undefined && - match.priv.scopeName !== undefined && - match.priv.scopeType !== undefined - ) { - // Use the scoped version of the TypeVar rather than the (unscoped) original type. - type = TypeVarType.cloneForScopeId( - type, - match.priv.scopeId, - match.priv.scopeName, - match.priv.scopeType - ); - type.shared.declaredVariance = match.shared.declaredVariance; - return { - type, - scopeNode, - foundInterveningClass: nestedClassCount > 1 && !scopeUsesTypeParamSyntax, - }; - } - } - - curNode = curNode.parent; - } - - // See if this is part of an assignment statement that is defining a type alias. - curNode = node; - while (curNode) { - let leftType: Type | undefined; - let typeAliasNode: TypeAliasNode | undefined; - let scopeNode: TypeAliasNode | AssignmentNode | undefined; - - if (curNode.nodeType === ParseNodeType.TypeAlias) { - leftType = readTypeCache(curNode.d.name, EvalFlags.None); - typeAliasNode = curNode; - scopeNode = curNode; - } else if (curNode.nodeType === ParseNodeType.Assignment) { - leftType = readTypeCache(curNode.d.leftExpr, EvalFlags.None); - scopeNode = curNode; - } - - if (leftType && scopeNode) { - // Is this a placeholder that was temporarily written to the cache for - // purposes of resolving type aliases? - if (leftType && isTypeVar(leftType) && leftType.shared.recursiveAlias) { - // Type alias statements cannot be used with old-style type variables. - if (typeAliasNode && !type.shared.isTypeParamSyntax && !type.props?.typeAliasInfo) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeParameterNotDeclared().format({ - name: type.shared.name, - container: typeAliasNode.d.name.d.value, - }), - node - ); - } else { - // If this is a TypeAliasType call, the recursive type parameters will already - // be populated, and we need to verify that the type parameter is in the - // list of allowed type parameters. - const allowedTypeParams = leftType.shared.recursiveAlias?.typeParams; - if (allowedTypeParams) { - if (!allowedTypeParams.some((param) => param.shared.name === type.shared.name)) { - // Return the original type. - return { type, scopeNode, foundInterveningClass: false }; - } - } - } - - return { - type: TypeVarType.cloneForScopeId( - type, - leftType.shared.recursiveAlias.typeVarScopeId, - leftType.shared.recursiveAlias.name, - TypeVarScopeType.TypeAlias - ), - scopeNode, - foundInterveningClass: false, - }; - } - } - - curNode = curNode.parent; - } - - // Return the original type. - return { type, scopeNode: undefined, foundInterveningClass: false }; - } - - function getTypeOfMemberAccess(node: MemberAccessNode, flags: EvalFlags): TypeResult { - // Compute flags specifically for evaluating the left expression. - let leftExprFlags = EvalFlags.MemberAccessBaseDefaults; - leftExprFlags |= - flags & - (EvalFlags.TypeExpression | - EvalFlags.VarTypeAnnotation | - EvalFlags.ForwardRefs | - EvalFlags.NotParsed | - EvalFlags.NoTypeVarWithScopeId | - EvalFlags.TypeVarGetsCurScope); - - // Handle special casing for ParamSpec "args" and "kwargs" accesses. - if ((flags & EvalFlags.InstantiableType) !== 0) { - const memberName = node.d.member.d.value; - if (memberName === 'args' || memberName === 'kwargs') { - leftExprFlags |= EvalFlags.NoConvertSpecialForm; - } - } - const baseTypeResult = getTypeOfExpression(node.d.leftExpr, leftExprFlags); - - if (isTypeAliasPlaceholder(baseTypeResult.type)) { - return { - type: UnknownType.create(/* isIncomplete */ true), - isIncomplete: true, - }; - } - - const typeResult = getTypeOfMemberAccessWithBaseType( - node, - baseTypeResult, - { method: 'get' }, - flags | EvalFlags.NoSpecialize - ); - - if (isCodeFlowSupportedForReference(node)) { - // Before performing code flow analysis, update the cache to prevent recursion. - writeTypeCache(node, { ...typeResult, isIncomplete: true }, flags); - writeTypeCache(node.d.member, { ...typeResult, isIncomplete: true }, flags); - - // If the type is initially unbound, see if there's a parent class that - // potentially initialized the value. - let typeAtStart = typeResult.type; - let isTypeAtStartIncomplete = !!typeResult.isIncomplete; - if (isUnbound(typeAtStart)) { - const baseType = makeTopLevelTypeVarsConcrete(baseTypeResult.type); - - let classMemberInfo: ClassMember | undefined; - if (isInstantiableClass(baseType)) { - classMemberInfo = lookUpClassMember( - baseType, - node.d.member.d.value, - MemberAccessFlags.SkipOriginalClass - ); - } else if (isClassInstance(baseType)) { - classMemberInfo = lookUpObjectMember( - baseType, - node.d.member.d.value, - MemberAccessFlags.SkipOriginalClass - ); - } - - if (classMemberInfo) { - typeAtStart = getTypeOfMember(classMemberInfo); - isTypeAtStartIncomplete = false; - } - } - - // See if we can refine the type based on code flow analysis. - const codeFlowTypeResult = getFlowTypeOfReference(node, /* startNode */ undefined, { - targetSymbolId: indeterminateSymbolId, - typeAtStart: { type: typeAtStart, isIncomplete: isTypeAtStartIncomplete }, - skipConditionalNarrowing: (flags & EvalFlags.TypeExpression) !== 0, - }); - - if (codeFlowTypeResult.type) { - typeResult.type = codeFlowTypeResult.type; - } - - if (codeFlowTypeResult.isIncomplete) { - typeResult.isIncomplete = true; - } - - // Detect, report, and fill in missing type arguments if appropriate. - typeResult.type = reportMissingTypeArgs(node, typeResult.type, flags); - - // Add TypeForm details if appropriate. - typeResult.type = addTypeFormForSymbol(node, typeResult.type, flags, /* includesVarDecl */ false); - } - - if (baseTypeResult.isIncomplete) { - typeResult.isIncomplete = true; - } - - // See if we need to log an "unknown member access" diagnostic. - let skipPartialUnknownCheck = typeResult.isIncomplete; - - // Don't report an error if the type is a partially-specialized - // class being passed as an argument. This comes up frequently in - // cases where a type is passed as an argument (e.g. "defaultdict(list)"). - // It can also come up in cases like "isinstance(x, (list, dict))". - // We need to check for functions as well to handle Callable. - if ( - (isInstantiableClass(typeResult.type) && !typeResult.type.priv.includeSubclasses) || - typeResult.type.props?.specialForm - ) { - const argNode = ParseTreeUtils.getParentNodeOfType(node, ParseNodeType.Argument); - if (argNode && argNode?.parent?.nodeType === ParseNodeType.Call) { - skipPartialUnknownCheck = true; - } - } - - if (!skipPartialUnknownCheck) { - reportPossibleUnknownAssignment( - AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.reportUnknownMemberType, - DiagnosticRule.reportUnknownMemberType, - node.d.member, - typeResult.type, - node, - /* ignoreEmptyContainers */ false - ); - } - - // Cache the type information in the member name node. - writeTypeCache(node.d.member, typeResult, flags); - - return typeResult; - } - - function getTypeOfMemberAccessWithBaseType( - node: MemberAccessNode, - baseTypeResult: TypeResult, - usage: EvaluatorUsage, - flags: EvalFlags - ): TypeResult { - let baseType = transformPossibleRecursiveTypeAlias(baseTypeResult.type); - const memberName = node.d.member.d.value; - let diag = new DiagnosticAddendum(); - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - let type: Type | undefined; - let narrowedTypeForSet: Type | undefined; - let typeErrors = false; - let isIncomplete = !!baseTypeResult.isIncomplete; - let isAsymmetricAccessor: boolean | undefined; - const isRequired = false; - const isNotRequired = false; - let memberAccessDeprecationInfo: MemberAccessDeprecationInfo | undefined; - - if (usage?.setType?.isIncomplete) { - isIncomplete = true; - } - - // If the base type was incomplete and unbound, don't proceed - // because false positive errors will be generated. - if (baseTypeResult.isIncomplete && isUnbound(baseType)) { - return { type: UnknownType.create(/* isIncomplete */ true), isIncomplete: true }; - } - - if (baseType.props?.specialForm && (flags & EvalFlags.TypeExpression) === 0) { - baseType = baseType.props.specialForm; - } - - if (isParamSpec(baseType) && baseType.priv.paramSpecAccess) { - baseType = makeTopLevelTypeVarsConcrete(baseType); - } - - switch (baseType.category) { - case TypeCategory.Any: - case TypeCategory.Unknown: - case TypeCategory.Never: { - // Viper 扩展:Unknown 类型成员访问时标黄警告"对象可能未定义"。 - // 主要用于未解析的导入模块(如 import serial 失败后 serial.puts 访问)。 - if (isUnknown(baseType) && IsViperDialect(fileInfo.executionEnvironment.dialect)) { - fileInfo.diagnosticSink.addDiagnosticWithTextRange( - 'warning', - LocMessage.viperUnknownMemberAccess().format({ - memberName, - }), - node.d.member - ); - } - type = baseType; - break; - } - - case TypeCategory.Unbound: { - break; - } - - case TypeCategory.TypeVar: { - if (isParamSpec(baseType)) { - // Handle special cases for "P.args" and "P.kwargs". - if (memberName === 'args' || memberName === 'kwargs') { - const isArgs = memberName === 'args'; - const paramNode = ParseTreeUtils.getEnclosingParam(node); - const expectedCategory = isArgs ? ParamCategory.ArgsList : ParamCategory.KwargsDict; - - if (!paramNode || paramNode.d.category !== expectedCategory) { - const errorMessage = isArgs - ? LocMessage.paramSpecArgsUsage() - : LocMessage.paramSpecKwargsUsage(); - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, errorMessage, node); - type = UnknownType.create(isIncomplete); - break; - } - - type = TypeVarType.cloneForParamSpecAccess(baseType, memberName); - break; - } - - if (!isIncomplete) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.paramSpecUnknownMember().format({ name: memberName }), - node - ); - } - - type = UnknownType.create(isIncomplete); - break; - } - - // It's illegal to reference a member from a type variable. - if ((flags & EvalFlags.TypeExpression) !== 0) { - if (!isIncomplete) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarNoMember().format({ - type: printType(baseType), - name: memberName, - }), - node.d.leftExpr - ); - } - - type = UnknownType.create(isIncomplete); - break; - } - - if (baseType.shared.recursiveAlias) { - type = UnknownType.create(/* isIncomplete */ true); - isIncomplete = true; - break; - } - - if (isTypeVarTuple(baseType)) { - break; - } - - return getTypeOfMemberAccessWithBaseType( - node, - { - type: makeTopLevelTypeVarsConcrete(baseType), - bindToSelfType: TypeBase.isInstantiable(baseType) ? convertToInstance(baseType) : baseType, - isIncomplete, - }, - usage, - EvalFlags.None - ); - } - - case TypeCategory.Class: { - let typeResult: TypeResult | undefined; - - // Viper 扩展:CPtr[Struct] 指针解引用语义。 - // 当访问 CPtr[Struct] 实例的成员时,将 baseType 替换为 Struct 实例, - // 然后继续正常的成员访问路径。这模拟 C 语言的 ptr->field 语义, - // 统一覆盖 get/set/del 三种场景,避免 CPtr 类的 __getattr__/__setattr__ - // 干扰成员查找,也避免 applyAttributeAccessOverride 在赋值场景下失效。 - if ( - IsViperDialect(fileInfo.executionEnvironment.dialect) && - isCPtrType(baseType) && - baseType.priv.typeArgs && - baseType.priv.typeArgs.length > 0 - ) { - const pointeeType = baseType.priv.typeArgs[0]; - if (isClass(pointeeType)) { - baseType = TypeBase.isInstance(pointeeType) - ? pointeeType - : ClassType.cloneAsInstance(pointeeType); - } - } - - // If this is a class-like function created via NewType, treat - // it like a function for purposes of member accesses. - if ( - ClassType.isNewTypeClass(baseType) && - !baseType.priv.includeSubclasses && - prefetched?.functionClass && - isClass(prefetched.functionClass) - ) { - baseType = ClassType.cloneAsInstance(prefetched.functionClass); - } - - const enumMemberResult = getTypeOfEnumMember( - evaluatorInterface, - node, - baseType, - memberName, - isIncomplete - ); - - if (enumMemberResult) { - if (usage.method === 'get') { - typeResult = enumMemberResult; - } else { - // Is this an attempt to delete or overwrite an enum member? - if ( - isClassInstance(enumMemberResult.type) && - ClassType.isSameGenericClass(enumMemberResult.type, ClassType.cloneAsInstance(baseType)) && - enumMemberResult.type.priv.literalValue !== undefined - ) { - const diagMessage = - usage.method === 'set' ? LocMessage.enumMemberSet() : LocMessage.enumMemberDelete(); - addDiagnostic( - DiagnosticRule.reportAttributeAccessIssue, - diagMessage.format({ name: memberName }) + diag.getString(), - node.d.member, - diag.getEffectiveTextRange() ?? node.d.member - ); - } - } - } - - if (!typeResult) { - typeResult = getTypeOfBoundMember( - node.d.member, - baseType, - memberName, - usage, - diag, - (flags & EvalFlags.TypeExpression) === 0 ? undefined : MemberAccessFlags.TypeExpression, - baseTypeResult.bindToSelfType - ); - } - - if (typeResult) { - if (!typeResult.typeErrors) { - type = addConditionToType(typeResult.type, getTypeCondition(baseType), { - skipSelfCondition: true, - skipBoundTypeVars: true, - }); - } else { - typeErrors = true; - } - - if (typeResult.isAsymmetricAccessor) { - isAsymmetricAccessor = true; - } - - if (typeResult.isIncomplete) { - isIncomplete = true; - } - - if (typeResult.narrowedTypeForSet) { - narrowedTypeForSet = addConditionToType( - typeResult.narrowedTypeForSet, - getTypeCondition(baseType), - { skipSelfCondition: true, skipBoundTypeVars: true } - ); - } - - if (typeResult.memberAccessDeprecationInfo) { - memberAccessDeprecationInfo = typeResult.memberAccessDeprecationInfo; - } - } - break; - } - - case TypeCategory.Module: { - let symbol = ModuleType.getField(baseType, memberName); - - // If the symbol isn't found in the module's symbol table, - // see if it's defined in the `ModuleType` class. This is - // needed for modules that are synthesized for namespace - // packages. - if (!symbol && prefetched?.moduleTypeClass && isInstantiableClass(prefetched.moduleTypeClass)) { - symbol = ClassType.getSymbolTable(prefetched.moduleTypeClass).get(memberName); - } - - if (symbol && !symbol.isExternallyHidden()) { - if (usage.method === 'get') { - setSymbolAccessed(fileInfo, symbol, node.d.member); - } - - const typeResult = getEffectiveTypeOfSymbolForUsage( - symbol, - /* usageNode */ undefined, - /* useLastDecl */ true - ); - type = typeResult.type; - - if ((flags & EvalFlags.TypeExpression) !== 0) { - type = validateSymbolIsTypeExpression(node, type, !!typeResult.includesVariableDecl); - } - - // Add TypeForm details if appropriate. - type = addTypeFormForSymbol(node, type, flags, !!typeResult.includesVariableDecl); - - if (isTypeVar(type)) { - type = validateTypeVarUsage(node, type, flags); - } - - // If the type resolved to "unbound", treat it as "unknown" in - // the case of a module reference because if it's truly unbound, - // that error will be reported within the module and should not - // leak into other modules that import it. - if (isUnbound(type)) { - type = UnknownType.create(/* isIncomplete */ true); - } - - if (symbol.isPrivateMember()) { - addDiagnostic( - DiagnosticRule.reportPrivateUsage, - LocMessage.privateUsedOutsideOfModule().format({ - name: memberName, - }), - node.d.member - ); - } - - if (symbol.isPrivatePyTypedImport()) { - addDiagnostic( - DiagnosticRule.reportPrivateImportUsage, - LocMessage.privateImportFromPyTypedModule().format({ - name: memberName, - module: baseType.priv.moduleName, - }), - node.d.member - ); - } - } else { - // Viper 扩展:模块级 __getattr__(PEP 562)语义不存在,跳过动态属性解析。 - - // If the field was not found and the module type is marked - // such that all fields should be Any/Unknown, return that type. - if (!type && baseType.priv.notPresentFieldType) { - type = baseType.priv.notPresentFieldType; - } - - if (!type) { - if (!isIncomplete) { - addDiagnostic( - DiagnosticRule.reportAttributeAccessIssue, - LocMessage.moduleUnknownMember().format({ - memberName, - moduleName: baseType.priv.moduleName, - }), - node.d.member - ); - } - type = evaluatorOptions.evaluateUnknownImportsAsAny ? AnyType.create() : UnknownType.create(); - } - } - break; - } - - case TypeCategory.Union: { - type = mapSubtypes(baseType, (subtype) => { - if (isUnbound(subtype)) { - // Don't do anything if it's unbound. The error will already - // be reported elsewhere. - return undefined; - } - - if (isNoneInstance(subtype)) { - assert(isClassInstance(subtype)); - const typeResult = getTypeOfBoundMember(node.d.member, subtype, memberName, usage, diag); - - if (typeResult && !typeResult.typeErrors) { - type = addConditionToType(typeResult.type, getTypeCondition(baseType), { - skipBoundTypeVars: true, - }); - if (typeResult.isIncomplete) { - isIncomplete = true; - } - - return type; - } - - if (!isIncomplete) { - addDiagnostic( - DiagnosticRule.reportOptionalMemberAccess, - LocMessage.noneUnknownMember().format({ name: memberName }), - node.d.member - ); - } - - return undefined; - } - - // Viper 扩展:联合类型 Struct | CPtr 表示"结构体或指向它的指针"。 - // 在 Viper 方言下,CPtr/CVoid 子类型不参与成员访问(指针本身无字段), - // 直接跳过,只使用其他结构体子类型的结果。 - // 这模拟 C 语言中指针通过 "->"/"." 访问结构体成员的语义, - // 避免对 CPtr 报告 "Attribute is unknown" 错误。 - // 注意:必须在 getTypeOfMemberAccessWithBaseType 之前跳过, - // 否则错误诊断已在该函数内部生成。 - if ( - IsViperDialect(fileInfo.executionEnvironment.dialect) && - (isCPtrType(subtype) || isCVoidType(subtype)) - ) { - return undefined; - } - - const typeResult = getTypeOfMemberAccessWithBaseType( - node, - { - type: subtype, - isIncomplete: baseTypeResult.isIncomplete, - }, - usage, - EvalFlags.None - ); - - if (typeResult.isIncomplete) { - isIncomplete = true; - } - - if (typeResult.memberAccessDeprecationInfo) { - memberAccessDeprecationInfo = typeResult.memberAccessDeprecationInfo; - } - - if (typeResult.typeErrors) { - typeErrors = true; - } - - return typeResult.type; - }); - break; - } - - case TypeCategory.Function: - case TypeCategory.Overloaded: { - const hasSelf = isMethodType(baseType); - - if (memberName === '__self__' && hasSelf) { - // Handle "__self__" specially because MethodType defines - // it simply as "object". We can do better here. - let functionType: FunctionType | undefined; - - if (isFunction(baseType)) { - functionType = baseType; - } else { - const overloads = OverloadedType.getOverloads(baseType); - if (overloads.length > 0) { - functionType = overloads[0]; - } - } - - type = functionType?.priv.boundToType; - } else { - const altType = hasSelf ? prefetched?.methodClass : prefetched?.functionClass; - type = getTypeOfMemberAccessWithBaseType( - node, - { type: altType ? convertToInstance(altType) : UnknownType.create() }, - usage, - flags - ).type; - } - break; - } - - default: - assertNever(baseType); - } - - // If type is undefined, emit a general error message indicating that the - // member could not be accessed. - if (!type) { - const isFunctionRule = - isFunctionOrOverloaded(baseType) || - (isClassInstance(baseType) && ClassType.isBuiltIn(baseType, ['function', 'FunctionType'])); - - if (!baseTypeResult.isIncomplete) { - let diagMessage = LocMessage.memberAccess(); - if (usage.method === 'set') { - diagMessage = LocMessage.memberSet(); - } else if (usage.method === 'del') { - diagMessage = LocMessage.memberDelete(); - } - - // If there is an expected type diagnostic addendum (used for assignments), - // use that rather than the local diagnostic addendum because it will be - // more informative. - if (usage.setExpectedTypeDiag && !usage.setExpectedTypeDiag.isEmpty()) { - diag = usage.setExpectedTypeDiag; - } - - // If the class is a TypedDict, and there's a key with the same name, - // suggest that they user want to use ["key"] name instead. - if (isClass(baseType) && baseType.shared.typedDictEntries) { - const tdKey = baseType.shared.typedDictEntries.knownItems.get(memberName); - if (tdKey) { - const subDiag = new DiagnosticAddendum(); - subDiag.addMessage(LocAddendum.typedDictKeyAccess().format({ name: memberName })); - diag.addAddendum(subDiag); - } - } - - const rule = isFunctionRule - ? DiagnosticRule.reportFunctionMemberAccess - : DiagnosticRule.reportAttributeAccessIssue; - - addDiagnostic( - rule, - diagMessage.format({ name: memberName, type: printType(baseType) }) + diag.getString(), - node.d.member, - diag.getEffectiveTextRange() ?? node.d.member - ); - } - - // If this is member access on a function, use "Any" so if the - // reportFunctionMemberAccess rule is disabled, we don't trigger - // additional reportUnknownMemberType diagnostics. - type = isFunctionRule ? AnyType.create() : UnknownType.create(); - } - - if ((flags & EvalFlags.TypeExpression) === 0) { - reportUseOfTypeCheckOnly(type, node.d.member); - } - - type = convertSpecialFormToRuntimeValue(type, flags); - - return { - type, - isIncomplete, - isAsymmetricAccessor, - narrowedTypeForSet, - isRequired, - isNotRequired, - memberAccessDeprecationInfo, - typeErrors, - }; - } - - function getTypeOfClassMemberName( - errorNode: ExpressionNode | undefined, - classType: ClassType, - memberName: string, - usage: EvaluatorUsage, - diag: DiagnosticAddendum | undefined, - flags: MemberAccessFlags, - selfType?: ClassType | TypeVarType, - recursionCount = 0 - ): ClassMemberLookup | undefined { - const isAccessedThroughObject = TypeBase.isInstance(classType); - - // Always look for a member with a declared type first. - let memberInfo = lookUpClassMember(classType, memberName, flags | MemberAccessFlags.DeclaredTypesOnly); - - // If we couldn't find a symbol with a declared type, use - // a symbol with an inferred type. - if (!memberInfo) { - memberInfo = lookUpClassMember(classType, memberName, flags); - } - - if (!memberInfo) { - // No attribute of that name was found. If this is a member access - // through an object, see if there's an attribute access override - // method ("__getattr__", etc.). - if ((flags & MemberAccessFlags.SkipAttributeAccessOverride) === 0 && errorNode) { - const generalAttrType = applyAttributeAccessOverride(errorNode, classType, usage, memberName, selfType); - if (generalAttrType) { - return { - symbol: undefined, - type: generalAttrType.type, - isTypeIncomplete: false, - isDescriptorError: false, - isClassMember: false, - isClassVar: false, - isAsymmetricAccessor: !!generalAttrType.isAsymmetricAccessor, - }; - } - } - - // Report that the member could not be accessed. - diag?.addMessage(LocAddendum.memberUnknown().format({ name: memberName })); - return undefined; - } - - let type: Type | undefined; - let isTypeIncomplete = false; - let narrowedTypeForSet: Type | undefined; - - if (memberInfo.symbol.isInitVar()) { - diag?.addMessage(LocAddendum.memberIsInitVar().format({ name: memberName })); - return undefined; - } - - if (usage.method !== 'get' && errorNode) { - // If the usage indicates a 'set' or 'delete' and the access is within the - // class definition itself, use only the declared type to avoid circular - // type evaluation. - const containingClass = ParseTreeUtils.getEnclosingClass(errorNode); - if (containingClass) { - const containingClassType = getTypeOfClass(containingClass)?.classType; - if ( - containingClassType && - isInstantiableClass(containingClassType) && - ClassType.isSameGenericClass( - isAccessedThroughObject ? ClassType.cloneAsInstance(containingClassType) : containingClassType, - classType - ) - ) { - type = getDeclaredTypeOfSymbol(memberInfo.symbol)?.type; - if (type && isInstantiableClass(memberInfo.classType)) { - type = partiallySpecializeType( - type, - memberInfo.classType, - /* typeClassType */ undefined, - selfType - ); - } - - // If we're setting a class variable via a write through an object, - // this is normally considered a type violation. But it is allowed - // if the class variable is a descriptor object. In this case, we will - // clear the flag that causes an error to be generated. - if ( - usage.method === 'set' && - isEffectivelyClassVar(memberInfo.symbol, ClassType.isDataClass(containingClassType)) && - isAccessedThroughObject - ) { - const selfClass = selfType ?? memberName === '__new__' ? undefined : classType; - const typeResult = getTypeOfMemberInternal(errorNode, memberInfo, selfClass, flags); - - if (typeResult) { - if (isDescriptorInstance(typeResult.type, /* requireSetter */ true)) { - type = typeResult.type; - flags &= MemberAccessFlags.DisallowClassVarWrites; - } - } - } - - if (!type) { - type = UnknownType.create(); - } - } - } - } - - if (!type) { - let selfClass: ClassType | TypeVarType | undefined; - - if (selfType) { - selfClass = convertToInstantiable(selfType) as TypeVarType | ClassType; - } else { - // Skip this for __new__ methods because they are not bound - // to the class but rather assume the type of the cls argument. - if (memberName !== '__new__') { - selfClass = classType; - } - } - - const typeResult = getTypeOfMemberInternal(errorNode, memberInfo, selfClass, flags); - - type = typeResult?.type ?? UnknownType.create(); - if (typeResult?.isIncomplete) { - isTypeIncomplete = true; - } - } - - // Don't include variables within typed dict classes. - if (isClass(memberInfo.classType) && ClassType.isTypedDictClass(memberInfo.classType)) { - const typedDecls = memberInfo.symbol.getTypedDeclarations(); - if (typedDecls.length > 0 && typedDecls[0].type === DeclarationType.Variable) { - diag?.addMessage(LocAddendum.memberUnknown().format({ name: memberName })); - return undefined; - } - } - - if (usage.method === 'get') { - // Mark the member accessed if it's not coming from a parent class. - if ( - errorNode && - isInstantiableClass(memberInfo.classType) && - ClassType.isSameGenericClass( - memberInfo.classType, - isAccessedThroughObject ? ClassType.cloneAsInstantiable(classType) : classType - ) - ) { - setSymbolAccessed(AnalyzerNodeInfo.getFileInfo(errorNode), memberInfo.symbol, errorNode); - } - - // Special-case `__init_subclass` and `__class_getitem__` because - // these are always treated as class methods even if they're not - // decorated as such. - if (memberName === '__init_subclass__' || memberName === '__class_getitem__') { - if (isFunction(type) && !FunctionType.isClassMethod(type)) { - type = FunctionType.cloneWithNewFlags(type, type.shared.flags | FunctionTypeFlags.ClassMethod); - } - } - } - - // If the member is a descriptor object, apply the descriptor protocol - // now. If the member is an instance or class method, bind the method. - let isDescriptorError = false; - let isAsymmetricAccessor = false; - let isDescriptorApplied = false; - let memberAccessDeprecationInfo: MemberAccessDeprecationInfo | undefined; - - type = mapSubtypes( - type, - (subtype) => { - const concreteSubtype = makeTopLevelTypeVarsConcrete(subtype); - const isClassMember = !memberInfo || (memberInfo.isClassMember && !memberInfo.isSlotsMember); - let resultType: Type; - - if (isClass(concreteSubtype) && isClassMember && errorNode) { - const descResult = applyDescriptorAccessMethod( - subtype, - concreteSubtype, - memberInfo, - classType, - selfType, - flags, - errorNode, - memberName, - usage, - diag - ); - - if (descResult.isAsymmetricAccessor) { - isAsymmetricAccessor = true; - } - - if (descResult.memberAccessDeprecationInfo) { - memberAccessDeprecationInfo = descResult.memberAccessDeprecationInfo; - } - - if (descResult.typeErrors) { - isDescriptorError = true; - } - - if (descResult.isDescriptorApplied) { - isDescriptorApplied = true; - } - - resultType = descResult.type; - } else if (isFunctionOrOverloaded(concreteSubtype) && TypeBase.isInstance(concreteSubtype)) { - const typeResult = bindMethodForMemberAccess( - subtype, - concreteSubtype, - memberInfo, - classType, - selfType, - flags, - memberName, - usage, - diag, - recursionCount - ); - - resultType = typeResult.type; - if (typeResult.typeErrors) { - isDescriptorError = true; - } - } else { - resultType = subtype; - } - - // If this is a "set" or "delete" operation, we have a bit more work to do. - if (usage.method === 'get') { - return resultType; - } - - // Check for an attempt to overwrite or delete a ClassVar member from an instance. - if ( - !isDescriptorApplied && - memberInfo && - isEffectivelyClassVar(memberInfo.symbol, ClassType.isDataClass(classType)) && - (flags & MemberAccessFlags.DisallowClassVarWrites) !== 0 - ) { - diag?.addMessage(LocAddendum.memberSetClassVar().format({ name: memberName })); - isDescriptorError = true; - } - - // Check for an attempt to overwrite or delete a final member variable. - const finalVarTypeDecl = memberInfo?.symbol - .getDeclarations() - .find((decl) => isFinalVariableDeclaration(decl)); - - if ( - finalVarTypeDecl && - errorNode && - !ParseTreeUtils.isNodeContainedWithin(errorNode, finalVarTypeDecl.node) - ) { - // If a Final instance variable is declared in the class body but is - // being assigned within an __init__ method, it's allowed. - const enclosingFunctionNode = ParseTreeUtils.getEnclosingFunction(errorNode); - if ( - !enclosingFunctionNode || - enclosingFunctionNode.d.name.d.value !== '__init__' || - (finalVarTypeDecl as VariableDeclaration).inferredTypeSource !== undefined || - isInstantiableClass(classType) - ) { - diag?.addMessage(LocMessage.finalReassigned().format({ name: memberName })); - isDescriptorError = true; - } - } - - // Check for an attempt to overwrite or delete an instance variable that is - // read-only (e.g. in a named tuple). - if (memberInfo?.isInstanceMember && isClass(memberInfo.classType) && memberInfo.isReadOnly) { - diag?.addMessage(LocAddendum.readOnlyAttribute().format({ name: memberName })); - isDescriptorError = true; - } - - return resultType; - }, - { retainTypeAlias: true } - ); - - if (!isDescriptorError && usage.method === 'set' && usage.setType) { - if (errorNode && memberInfo.symbol.hasTypedDeclarations()) { - // This is an assignment to a member with a declared type. Apply - // narrowing logic based on the assigned type. Skip this for - // descriptor-based accesses. - narrowedTypeForSet = isDescriptorApplied - ? usage.setType.type - : narrowTypeBasedOnAssignment(type, usage.setType).type; - } - - // Verify that the assigned type is compatible. - if (!assignType(type, usage.setType.type, diag?.createAddendum())) { - if (!usage.setType.isIncomplete) { - diag?.addMessage( - LocAddendum.memberAssignment().format({ - type: printType(usage.setType.type), - name: memberName, - classType: printObjectTypeForClass(classType), - }) - ); - } - - // Do not narrow the type in this case. Assume the declared type. - narrowedTypeForSet = type; - isDescriptorError = true; - } - - if ( - isInstantiableClass(memberInfo.classType) && - ClassType.isDataClassFrozen(memberInfo.classType) && - isAccessedThroughObject - ) { - diag?.addMessage( - LocAddendum.dataClassFrozen().format({ - name: printType(ClassType.cloneAsInstance(memberInfo.classType)), - }) - ); - - isDescriptorError = true; - } - } - - return { - symbol: memberInfo.symbol, - type, - isTypeIncomplete, - isDescriptorError, - isClassMember: !memberInfo.isInstanceMember, - isClassVar: memberInfo.isClassVar, - classType: memberInfo.classType, - isAsymmetricAccessor, - narrowedTypeForSet, - memberAccessDeprecationInfo, - }; - } - - // Applies descriptor access methods "__get__", "__set__", or "__delete__" - // if they apply. - function applyDescriptorAccessMethod( - memberType: Type, - concreteMemberType: ClassType, - memberInfo: ClassMember | undefined, - classType: ClassType, - selfType: ClassType | TypeVarType | undefined, - flags: MemberAccessFlags, - errorNode: ExpressionNode, - memberName: string, - usage: EvaluatorUsage, - diag: DiagnosticAddendum | undefined - ): MemberAccessTypeResult { - const isAccessedThroughObject = TypeBase.isInstance(classType); - - let accessMethodName: string; - if (usage.method === 'get') { - accessMethodName = '__get__'; - } else if (usage.method === 'set') { - accessMethodName = '__set__'; - } else { - accessMethodName = '__delete__'; - } - - const subDiag = diag ? new DiagnosticAddendum() : undefined; - - const methodTypeResult = getTypeOfBoundMember( - errorNode, - concreteMemberType, - accessMethodName, - /* usage */ undefined, - subDiag, - MemberAccessFlags.SkipInstanceMembers | MemberAccessFlags.SkipAttributeAccessOverride - ); - - if (!methodTypeResult || methodTypeResult.typeErrors) { - // Provide special error messages for properties. - if (ClassType.isPropertyClass(concreteMemberType) && usage.method !== 'get') { - const message = - usage.method === 'set' ? LocAddendum.propertyMissingSetter() : LocAddendum.propertyMissingDeleter(); - diag?.addMessage(message.format({ name: memberName })); - return { type: AnyType.create(), typeErrors: true }; - } - - if (classType.shared.typeVarScopeId) { - memberType = makeTypeVarsBound(memberType, [classType.shared.typeVarScopeId]); - } - - return { type: memberType }; - } - - const methodClassType = methodTypeResult.classType; - let methodType = methodTypeResult.type; - - if (methodTypeResult.typeErrors || !methodClassType) { - if (diag && subDiag) { - diag.addAddendum(subDiag); - } - return { type: UnknownType.create(), typeErrors: true }; - } - - if (!isFunctionOrOverloaded(methodType)) { - if (isAnyOrUnknown(methodType)) { - return { type: methodType }; - } - - // TODO - emit an error for this condition. - return { type: memberType, typeErrors: true }; - } - - // Special-case logic for properties. - if ( - ClassType.isPropertyClass(concreteMemberType) && - memberInfo && - isInstantiableClass(memberInfo.classType) && - methodType - ) { - // If the property is being accessed from a protocol class (not an instance), - // flag this as an error because a property within a protocol is meant to be - // interpreted as a read-only attribute rather than a protocol, so accessing - // it directly from the class has an ambiguous meaning. - if ((flags & MemberAccessFlags.SkipInstanceMembers) !== 0 && ClassType.isProtocolClass(classType)) { - diag?.addMessage(LocAddendum.propertyAccessFromProtocolClass()); - return { type: memberType, typeErrors: true }; - } - - // Infer return types before specializing. Otherwise a generic inferred - // return type won't be properly specialized. - inferReturnTypeIfNecessary(methodType); - - // This specialization is required specifically for properties, which should be - // generic but are not defined that way. Because of this, we use type variables - // in the synthesized methods (e.g. __get__) for the property class that are - // defined in the class that declares the fget method. - let accessMethodClass: ClassType | undefined; - if (usage.method === 'get') { - accessMethodClass = concreteMemberType.priv.fgetInfo?.classType; - } else if (usage.method === 'set') { - accessMethodClass = concreteMemberType.priv.fsetInfo?.classType; - } else { - accessMethodClass = concreteMemberType.priv.fdelInfo?.classType; - } - - if (accessMethodClass) { - const constraints = new ConstraintTracker(); - accessMethodClass = selfSpecializeClass(accessMethodClass); - assignType( - ClassType.cloneAsInstance(accessMethodClass), - ClassType.cloneAsInstance(memberInfo.classType), - /* diag */ undefined, - constraints - ); - accessMethodClass = solveAndApplyConstraints(accessMethodClass, constraints) as ClassType; - - const specializedType = partiallySpecializeType( - methodType, - accessMethodClass, - getTypeClassType(), - selfType ? (convertToInstantiable(selfType) as ClassType | TypeVarType) : classType - ); - - if (isFunctionOrOverloaded(specializedType)) { - methodType = specializedType; - } - } - } - - // Determine if we're calling __set__ on an asymmetric descriptor or property. - let isAsymmetricAccessor = false; - if (usage.method === 'set' && isClass(methodClassType)) { - if (isAsymmetricDescriptorClass(methodClassType)) { - isAsymmetricAccessor = true; - } - } - - if (!methodType) { - diag?.addMessage( - LocAddendum.descriptorAccessBindingFailed().format({ - name: accessMethodName, - className: printType(convertToInstance(methodClassType)), - }) - ); - - return { - type: UnknownType.create(), - typeErrors: true, - isDescriptorApplied: true, - isAsymmetricAccessor, - }; - } - - // Simulate a call to the access method. - const argList: Arg[] = []; - - // Provide "obj" argument. - let objArgType: Type; - if (ClassType.isClassProperty(concreteMemberType)) { - // Handle "class properties" as a special case. We need to pass - // the class rather than the object instance in this case. - objArgType = isAccessedThroughObject ? ClassType.cloneAsInstantiable(classType) : classType; - } else if (isAccessedThroughObject) { - objArgType = selfType ?? ClassType.cloneAsInstance(classType); - } else { - objArgType = getNoneType(); - } - - argList.push({ - argCategory: ArgCategory.Simple, - typeResult: { type: objArgType }, - }); - - if (usage.method === 'get') { - let classArgType: Type; - if (selfType) { - classArgType = convertToInstantiable(selfType); - } else { - classArgType = isAccessedThroughObject ? ClassType.cloneAsInstantiable(classType) : classType; - } - - // Provide "owner" argument. - argList.push({ - argCategory: ArgCategory.Simple, - typeResult: { type: classArgType }, - }); - } else if (usage.method === 'set') { - // Provide "value" argument. - argList.push({ - argCategory: ArgCategory.Simple, - typeResult: { - type: usage.setType?.type ?? UnknownType.create(), - isIncomplete: !!usage.setType?.isIncomplete, - }, - }); - } - - // Suppress diagnostics for these method calls because they would be redundant. - const callResult = suppressDiagnostics( - errorNode, - () => { - return validateCallArgs( - errorNode, - argList, - { type: methodType }, - /* constraints */ undefined, - /* skipUnknownArgCheck */ true, - /* inferenceContext */ undefined - ); - }, - (suppressedDiags) => { - // If diagnostics were recorded when suppressed, add them to the - // diagnostic as messages. - if (diag) { - suppressedDiags.forEach((message) => { - diag?.addMessageMultiline(message); - }); - } - } - ); - - // Collect deprecation information associated with the member access method. - let deprecationInfo: MemberAccessDeprecationInfo | undefined; - if (callResult.overloadsUsedForCall && callResult.overloadsUsedForCall.length >= 1) { - const overloadUsed = callResult.overloadsUsedForCall[0]; - if (overloadUsed.shared.deprecatedMessage) { - deprecationInfo = { - deprecatedMessage: overloadUsed.shared.deprecatedMessage, - accessType: ClassType.isPropertyClass(concreteMemberType) ? 'property' : 'descriptor', - accessMethod: usage.method, - }; - } - } - - if (!callResult.argumentErrors) { - return { - // For set or delete, always return Any. - type: usage.method === 'get' ? callResult.returnType ?? UnknownType.create() : AnyType.create(), - isDescriptorApplied: true, - isAsymmetricAccessor, - memberAccessDeprecationInfo: deprecationInfo, - }; - } - - return { - type: UnknownType.create(), - typeErrors: true, - isDescriptorApplied: true, - isAsymmetricAccessor, - memberAccessDeprecationInfo: deprecationInfo, - }; - } - - function bindMethodForMemberAccess( - type: Type, - concreteType: FunctionType | OverloadedType, - memberInfo: ClassMember | undefined, - classType: ClassType, - selfType: ClassType | TypeVarType | undefined, - flags: MemberAccessFlags, - memberName: string, - usage: EvaluatorUsage, - diag: DiagnosticAddendum | undefined, - recursionCount = 0 - ): TypeResult { - // Check for an attempt to overwrite a final method. - if (usage.method === 'set') { - const impl = isFunction(concreteType) ? concreteType : OverloadedType.getImplementation(concreteType); - - if (impl && isFunction(impl) && FunctionType.isFinal(impl) && memberInfo && isClass(memberInfo.classType)) { - diag?.addMessage( - LocMessage.finalMethodOverride().format({ - name: memberName, - className: memberInfo.classType.shared.name, - }) - ); - - return { type: UnknownType.create(), typeErrors: true }; - } - } - - // If this function is an instance member (e.g. a lambda that was - // assigned to an instance variable), don't perform any binding. - if (TypeBase.isInstance(classType)) { - if (!memberInfo || memberInfo.isInstanceMember) { - return { type: type }; - } - } - - const boundType = bindFunctionToClassOrObject( - classType, - concreteType, - memberInfo && isInstantiableClass(memberInfo.classType) ? memberInfo.classType : undefined, - (flags & MemberAccessFlags.TreatConstructorAsClassMethod) !== 0, - selfType && isClass(selfType) ? ClassType.cloneIncludeSubclasses(selfType) : selfType, - diag, - recursionCount - ); - - return { type: boundType ?? UnknownType.create(), typeErrors: !boundType }; - } - - function isAsymmetricDescriptorClass(classType: ClassType): boolean { - // If the value has already been cached in this type, return the cached value. - if (classType.priv.isAsymmetricDescriptor !== undefined) { - return classType.priv.isAsymmetricDescriptor; - } - - let isAsymmetric = false; - - const getterSymbolResult = lookUpClassMember(classType, '__get__', MemberAccessFlags.SkipBaseClasses); - const setterSymbolResult = lookUpClassMember(classType, '__set__', MemberAccessFlags.SkipBaseClasses); - - if (!getterSymbolResult || !setterSymbolResult) { - isAsymmetric = false; - } else { - let getterType = getTypeOfMember(getterSymbolResult); - const setterType = getTypeOfMember(setterSymbolResult); - - // If this is an overload, find the appropriate overload. - if (isOverloaded(getterType)) { - const getOverloads = OverloadedType.getOverloads(getterType).filter((overload) => { - if (overload.shared.parameters.length < 2) { - return false; - } - const param1Type = FunctionType.getParamType(overload, 1); - return !isNoneInstance(param1Type); - }); - - if (getOverloads.length === 1) { - getterType = getOverloads[0]; - } else { - isAsymmetric = true; - } - } - - // If this is an overload, find the appropriate overload. - if (isOverloaded(setterType)) { - isAsymmetric = true; - } - - // If either the setter or getter is an overload (or some other non-function type), - // conservatively assume that it's not asymmetric. - if (isFunction(getterType) && isFunction(setterType)) { - // If there's no declared return type on the getter, assume it's symmetric. - if (setterType.shared.parameters.length >= 3 && getterType.shared.declaredReturnType) { - const setterValueType = FunctionType.getParamType(setterType, 2); - const getterReturnType = FunctionType.getEffectiveReturnType(getterType) ?? UnknownType.create(); - - if (!isTypeSame(setterValueType, getterReturnType)) { - isAsymmetric = true; - } - } - } - } - - // Cache the value for next time. - classType.priv.isAsymmetricDescriptor = isAsymmetric; - return isAsymmetric; - } - - function isClassWithAsymmetricAttributeAccessor(classType: ClassType): boolean { - // If the value has already been cached in this type, return the cached value. - if (classType.priv.isAsymmetricAttributeAccessor !== undefined) { - return classType.priv.isAsymmetricAttributeAccessor; - } - - let isAsymmetric = false; - - const getterSymbolResult = lookUpClassMember(classType, '__getattr__', MemberAccessFlags.SkipBaseClasses); - const setterSymbolResult = lookUpClassMember(classType, '__setattr__', MemberAccessFlags.SkipBaseClasses); - - if (!getterSymbolResult || !setterSymbolResult) { - isAsymmetric = false; - } else { - const getterType = getEffectiveTypeOfSymbol(getterSymbolResult.symbol); - const setterType = getEffectiveTypeOfSymbol(setterSymbolResult.symbol); - - // If either the setter or getter is an overload (or some other non-function type), - // conservatively assume that it's not asymmetric. - if (isFunction(getterType) && isFunction(setterType)) { - // If there's no declared return type on the getter, assume it's symmetric. - if (setterType.shared.parameters.length >= 3 && getterType.shared.declaredReturnType) { - const setterValueType = FunctionType.getParamType(setterType, 2); - const getterReturnType = FunctionType.getEffectiveReturnType(getterType) ?? UnknownType.create(); - - if (!isTypeSame(setterValueType, getterReturnType)) { - isAsymmetric = true; - } - } - } - } - - // Cache the value for next time. - classType.priv.isAsymmetricAttributeAccessor = isAsymmetric; - return isAsymmetric; - } - - // Applies the __getattr__, __setattr__ or __delattr__ method if present. - // If it's not applicable, returns undefined. - function applyAttributeAccessOverride( - errorNode: ExpressionNode, - classType: ClassType, - usage: EvaluatorUsage, - memberName: string, - selfType?: ClassType | TypeVarType - ): MemberAccessTypeResult | undefined { - // Viper is an AOT static language without Python's instance-level - // descriptor protocol (__getattr__/__getattribute__/__setattr__). - if (IsViperDialect(AnalyzerNodeInfo.getFileInfo(errorNode).executionEnvironment.dialect)) { - // Viper 扩展:CPtr[Struct] 指针解引用语义。 - // 当访问 CPtr[Struct] 实例的成员时,将属性访问转发到 Struct 类型。 - // 模拟 C 语言的 ptr->field 语义。 - if (isCPtrType(classType) && classType.priv.typeArgs && classType.priv.typeArgs.length > 0) { - const pointeeType = classType.priv.typeArgs[0]; - if (isClass(pointeeType)) { - // 确保是实例形式以便访问实例属性 - const pointeeInstance = TypeBase.isInstance(pointeeType) - ? pointeeType - : ClassType.cloneAsInstance(pointeeType); - const memberInfo = lookUpClassMember( - pointeeInstance, - memberName, - MemberAccessFlags.Default - ); - if (memberInfo) { - const memberType = getEffectiveTypeOfSymbol(memberInfo.symbol); - return { - type: memberType, - isAsymmetricAccessor: false, - }; - } - } - } - return undefined; - } - - const getAttributeAccessMember = (name: string) => { - return getTypeOfBoundMember( - errorNode, - classType, - name, - /* usage */ undefined, - /* diag */ undefined, - MemberAccessFlags.SkipInstanceMembers | - MemberAccessFlags.SkipObjectBaseClass | - MemberAccessFlags.SkipTypeBaseClass | - MemberAccessFlags.SkipAttributeAccessOverride, - selfType - )?.type; - }; - - let accessMemberType: Type | undefined; - if (usage.method === 'get') { - accessMemberType = getAttributeAccessMember('__getattribute__') ?? getAttributeAccessMember('__getattr__'); - } else if (usage.method === 'set') { - accessMemberType = getAttributeAccessMember('__setattr__'); - } else { - assert(usage.method === 'del'); - accessMemberType = getAttributeAccessMember('__delattr__'); - } - - if (!accessMemberType) { - return undefined; - } - - const argList: Arg[] = []; - - // Provide "name" argument. - argList.push({ - argCategory: ArgCategory.Simple, - typeResult: { - type: - prefetched?.strClass && isInstantiableClass(prefetched.strClass) - ? ClassType.cloneWithLiteral(ClassType.cloneAsInstance(prefetched.strClass), memberName) - : AnyType.create(), - }, - }); - - if (usage.method === 'set') { - // Provide "value" argument. - argList.push({ - argCategory: ArgCategory.Simple, - typeResult: { - type: usage.setType?.type ?? UnknownType.create(), - isIncomplete: !!usage.setType?.isIncomplete, - }, - }); - } - - if (!isFunctionOrOverloaded(accessMemberType)) { - if (isAnyOrUnknown(accessMemberType)) { - return { type: accessMemberType }; - } - - // TODO - emit an error for this condition. - return undefined; - } - - const callResult = validateCallArgs( - errorNode, - argList, - { type: accessMemberType }, - /* constraints */ undefined, - /* skipUnknownArgCheck */ true, - /* inferenceContext */ undefined - ); - - let isAsymmetricAccessor = false; - if (usage.method === 'set') { - isAsymmetricAccessor = isClassWithAsymmetricAttributeAccessor(classType); - } - - return { - type: callResult.returnType ?? UnknownType.create(), - typeErrors: callResult.argumentErrors, - isAsymmetricAccessor, - }; - } - - function getTypeOfIndex(node: IndexNode, flags = EvalFlags.None): TypeResult { - const baseTypeResult = getTypeOfExpression(node.d.leftExpr, flags | EvalFlags.IndexBaseDefaults); - - // If this is meant to be a type and the base expression is a string expression, - // emit an error because this is an illegal annotation form and will generate a - // runtime exception. - if (flags & EvalFlags.InstantiableType) { - if (node.d.leftExpr.nodeType === ParseNodeType.StringList) { - addDiagnostic(DiagnosticRule.reportIndexIssue, LocMessage.stringNotSubscriptable(), node.d.leftExpr); - } - } - - // Viper 扩展:AOT 静态语言无 PEP 585 运行时 subscript 限制,跳过检查。 - - const indexTypeResult = getTypeOfIndexWithBaseType(node, baseTypeResult, { method: 'get' }, flags); - - if (isCodeFlowSupportedForReference(node)) { - // We limit type narrowing for index expressions to built-in types that are - // known to have symmetric __getitem__ and __setitem__ methods (i.e. the value - // passed to __setitem__ is the same type as the value returned by __getitem__). - let baseTypeSupportsIndexNarrowing = !isAny(baseTypeResult.type); - mapSubtypesExpandTypeVars(baseTypeResult.type, /* options */ undefined, (subtype) => { - if ( - !isClassInstance(subtype) || - !(ClassType.isBuiltIn(subtype) || ClassType.isTypedDictClass(subtype)) - ) { - baseTypeSupportsIndexNarrowing = false; - } - - return undefined; - }); - - if (baseTypeSupportsIndexNarrowing) { - // Before performing code flow analysis, update the cache to prevent recursion. - writeTypeCache(node, { ...indexTypeResult, isIncomplete: true }, flags); - - // See if we can refine the type based on code flow analysis. - const codeFlowTypeResult = getFlowTypeOfReference(node, /* startNode */ undefined, { - targetSymbolId: indeterminateSymbolId, - typeAtStart: { - type: indexTypeResult.type, - isIncomplete: !!baseTypeResult.isIncomplete || !!indexTypeResult.isIncomplete, - }, - skipConditionalNarrowing: (flags & EvalFlags.TypeExpression) !== 0, - }); - - if (codeFlowTypeResult.type) { - indexTypeResult.type = codeFlowTypeResult.type; - } - - if (codeFlowTypeResult.isIncomplete) { - indexTypeResult.isIncomplete = true; - } - } - } - - if (baseTypeResult.isIncomplete) { - indexTypeResult.isIncomplete = true; - } - - return indexTypeResult; - } - - // If the list of type parameters includes a TypeVarTuple, we may need to adjust - // the supplied type arguments to map to the type parameter list. - function adjustTypeArgsForTypeVarTuple( - typeArgs: TypeResultWithNode[], - typeParams: TypeVarType[], - errorNode: ExpressionNode - ): TypeResultWithNode[] { - const variadicIndex = typeParams.findIndex((param) => isTypeVarTuple(param)); - - // Is there a *tuple[T, ...] somewhere in the type arguments that we can expand if needed? - let srcUnboundedTupleType: Type | undefined; - const findUnboundedTupleIndex = (startArgIndex: number) => { - return typeArgs.findIndex((arg, index) => { - if (index < startArgIndex) { - return false; - } - if ( - isUnpackedClass(arg.type) && - arg.type.priv.tupleTypeArgs && - arg.type.priv.tupleTypeArgs.length === 1 && - arg.type.priv.tupleTypeArgs[0].isUnbounded - ) { - srcUnboundedTupleType = arg.type.priv.tupleTypeArgs[0].type; - return true; - } - - return false; - }); - }; - let srcUnboundedTupleIndex = findUnboundedTupleIndex(0); - - // Allow only one unpacked tuple that maps to a TypeVarTuple. - if (srcUnboundedTupleIndex >= 0) { - const secondUnboundedTupleIndex = findUnboundedTupleIndex(srcUnboundedTupleIndex + 1); - if (secondUnboundedTupleIndex >= 0) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.variadicTypeArgsTooMany(), - typeArgs[secondUnboundedTupleIndex].node - ); - } - } - - if ( - srcUnboundedTupleType && - srcUnboundedTupleIndex >= 0 && - variadicIndex >= 0 && - typeArgs.length < typeParams.length - ) { - // "Smear" the tuple type across type argument slots prior to the TypeVarTuple. - while (variadicIndex > srcUnboundedTupleIndex) { - typeArgs = [ - ...typeArgs.slice(0, srcUnboundedTupleIndex), - { node: typeArgs[srcUnboundedTupleIndex].node, type: srcUnboundedTupleType }, - ...typeArgs.slice(srcUnboundedTupleIndex), - ]; - srcUnboundedTupleIndex++; - } - - // "Smear" the tuple type across type argument slots following the TypeVarTuple. - while (typeArgs.length < typeParams.length) { - typeArgs = [ - ...typeArgs.slice(0, srcUnboundedTupleIndex + 1), - { node: typeArgs[srcUnboundedTupleIndex].node, type: srcUnboundedTupleType }, - ...typeArgs.slice(srcUnboundedTupleIndex + 1), - ]; - } - } - - // Do we need to adjust the type arguments to map to a variadic type - // param somewhere in the list? - if (variadicIndex >= 0) { - const variadicTypeVar = typeParams[variadicIndex]; - - // If the type param list ends with a ParamSpec with a default value, - // we can ignore it for purposes of finding type args that map to the - // TypeVarTuple. - let typeParamCount = typeParams.length; - while (typeParamCount > 0) { - const lastTypeParam = typeParams[typeParamCount - 1]; - if (!isParamSpec(lastTypeParam) || !lastTypeParam.shared.isDefaultExplicit) { - break; - } - - typeParamCount--; - } - - if (variadicIndex < typeArgs.length) { - // If there are typeArg lists at the end, these should map to ParamSpecs rather - // than the TypeVarTuple, so exclude them. - let variadicEndIndex = variadicIndex + 1 + typeArgs.length - typeParamCount; - while (variadicEndIndex > variadicIndex) { - if (!typeArgs[variadicEndIndex - 1].typeList) { - break; - } - variadicEndIndex--; - } - const variadicTypeResults = typeArgs.slice(variadicIndex, variadicEndIndex); - - // If the type args consist of a lone TypeVarTuple, don't wrap it in a tuple. - if (variadicTypeResults.length === 1 && isTypeVarTuple(variadicTypeResults[0].type)) { - validateTypeVarTupleIsUnpacked(variadicTypeResults[0].type, variadicTypeResults[0].node); - } else { - variadicTypeResults.forEach((arg, index) => { - validateTypeArg(arg, { - allowEmptyTuple: index === 0, - allowTypeVarTuple: true, - allowUnpackedTuples: true, - }); - }); - - const variadicTypes: TupleTypeArg[] = []; - if (variadicTypeResults.length !== 1 || !variadicTypeResults[0].isEmptyTupleShorthand) { - variadicTypeResults.forEach((typeResult) => { - if (isUnpackedClass(typeResult.type) && typeResult.type.priv.tupleTypeArgs) { - appendArray(variadicTypes, typeResult.type.priv.tupleTypeArgs); - } else { - variadicTypes.push({ - type: convertToInstance(typeResult.type), - isUnbounded: false, - }); - } - }); - } - - const tupleObject = makeTupleObject(evaluatorInterface, variadicTypes, /* isUnpacked */ true); - - typeArgs = [ - ...typeArgs.slice(0, variadicIndex), - { node: typeArgs[variadicIndex].node, type: tupleObject }, - ...typeArgs.slice(variadicEndIndex, typeArgs.length), - ]; - } - } else if (!variadicTypeVar.shared.isDefaultExplicit) { - // Add an empty tuple that maps to the TypeVarTuple type parameter. - typeArgs.push({ - node: errorNode, - type: makeTupleObject(evaluatorInterface, [], /* isUnpacked */ true), - }); - } - } - - return typeArgs; - } - - // If the variadic type variable is not unpacked, report an error. - function validateTypeVarTupleIsUnpacked(type: TypeVarTupleType, node: ParseNode) { - if (!type.priv.isUnpacked) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.unpackedTypeVarTupleExpected().format({ - name1: type.shared.name, - name2: type.shared.name, - }), - node - ); - return false; - } - - return true; - } - - // If the type is a generic type alias that is not specialized, provides - // default type arguments for the type alias. It optionally logs diagnostics - // for missing type arguments. - function specializeTypeAliasWithDefaults(type: Type, errorNode: ExpressionNode | undefined) { - // Is this a type alias? - const aliasInfo = type.props?.typeAliasInfo; - if (!aliasInfo) { - return type; - } - - // Is this a generic type alias that needs specializing? - if (!aliasInfo.shared.typeParams || aliasInfo.shared.typeParams.length === 0 || aliasInfo.typeArgs) { - return type; - } - - let reportDiag = false; - const defaultTypeArgs: Type[] = []; - const constraints = new ConstraintTracker(); - - aliasInfo.shared.typeParams.forEach((param) => { - if (!param.shared.isDefaultExplicit) { - reportDiag = true; - } - - let defaultType: Type; - if (param.shared.isDefaultExplicit || isParamSpec(param)) { - defaultType = solveAndApplyConstraints(param, constraints, { - replaceUnsolved: { - scopeIds: [aliasInfo.shared.typeVarScopeId], - tupleClassType: getTupleClassType(), - }, - }); - } else if (isTypeVarTuple(param) && prefetched?.tupleClass && isInstantiableClass(prefetched.tupleClass)) { - defaultType = makeTupleObject( - evaluatorInterface, - [{ type: UnknownType.create(), isUnbounded: true }], - /* isUnpacked */ true - ); - } else { - defaultType = UnknownType.create(); - } - - defaultTypeArgs.push(defaultType); - constraints.setBounds(param, defaultType); - }); - - if (reportDiag && errorNode) { - addDiagnostic( - DiagnosticRule.reportMissingTypeArgument, - LocMessage.typeArgsMissingForAlias().format({ - name: aliasInfo.shared.name, - }), - errorNode - ); - } - - type = TypeBase.cloneForTypeAlias( - solveAndApplyConstraints(type, constraints, { - replaceUnsolved: { - scopeIds: [aliasInfo.shared.typeVarScopeId], - tupleClassType: getTupleClassType(), - }, - }), - { ...aliasInfo, typeArgs: defaultTypeArgs } - ); - - return type; - } - - // Handles index expressions that are providing type arguments for a - // generic type alias. - function createSpecializedTypeAlias( - node: IndexNode, - baseType: Type, - flags: EvalFlags - ): TypeResultWithNode | undefined { - let aliasInfo = baseType.props?.typeAliasInfo; - let aliasBaseType = baseType; - - if (!aliasInfo && baseType.props?.typeForm) { - aliasInfo = baseType.props.typeForm?.props?.typeAliasInfo; - aliasBaseType = convertToInstantiable(baseType.props.typeForm); - } - - if (!aliasInfo?.shared.typeParams || (aliasInfo.shared.typeParams.length === 0 && aliasInfo.typeArgs)) { - return undefined; - } - - // If this is not instantiable, then the index expression isn't a specialization. - if (!TypeBase.isInstantiable(aliasBaseType)) { - return undefined; - } - - // If this is already specialized, the index expression isn't a specialization. - if (aliasInfo.typeArgs) { - return undefined; - } - - inferVarianceForTypeAlias(baseType); - - const typeParams = aliasInfo.shared.typeParams; - let typeArgs: TypeResultWithNode[] | undefined; - typeArgs = adjustTypeArgsForTypeVarTuple(getTypeArgs(node, flags), typeParams, node); - let reportedError = false; - - typeArgs = transformTypeArgsForParamSpec(typeParams, typeArgs, node); - if (!typeArgs) { - typeArgs = []; - reportedError = true; - } - - let minTypeArgCount = typeParams.length; - const firstDefaultParamIndex = typeParams.findIndex((param) => !!param.shared.isDefaultExplicit); - if (firstDefaultParamIndex >= 0) { - minTypeArgCount = firstDefaultParamIndex; - } - - if (typeArgs.length > typeParams.length) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.typeArgsTooMany().format({ - name: printType(aliasBaseType), - expected: typeParams.length, - received: typeArgs.length, - }), - typeArgs[typeParams.length].node - ); - reportedError = true; - } else if (typeArgs.length < minTypeArgCount) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.typeArgsTooFew().format({ - name: printType(aliasBaseType), - expected: typeParams.length, - received: typeArgs.length, - }), - node.d.items[node.d.items.length - 1] - ); - reportedError = true; - } - - // Handle the mypy_extensions.FlexibleAlias type specially. - if ( - isInstantiableClass(aliasBaseType) && - aliasBaseType.shared.fullName === 'mypy_extensions.FlexibleAlias' && - typeArgs.length >= 1 - ) { - return { node, type: typeArgs[0].type }; - } - - const constraints = new ConstraintTracker(); - const diag = new DiagnosticAddendum(); - - typeParams.forEach((param, index) => { - if (isParamSpec(param) && index < typeArgs.length) { - const typeArgType = typeArgs[index].type; - const typeList = typeArgs[index].typeList; - - if (typeList) { - const functionType = FunctionType.createSynthesizedInstance('', FunctionTypeFlags.ParamSpecValue); - typeList.forEach((paramTypeResult, paramIndex) => { - let paramType = paramTypeResult.type; - - if (!validateTypeArg(paramTypeResult)) { - paramType = UnknownType.create(); - } - - FunctionType.addParam( - functionType, - FunctionParam.create( - ParamCategory.Simple, - convertToInstance(paramType), - FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared, - `__p${paramIndex}` - ) - ); - }); - - if (typeList.length > 0) { - FunctionType.addPositionOnlyParamSeparator(functionType); - } - - assignTypeVar( - evaluatorInterface, - param, - functionType, - diag, - constraints, - AssignTypeFlags.RetainLiteralsForTypeVar - ); - } else if (isParamSpec(typeArgType)) { - assignTypeVar( - evaluatorInterface, - param, - convertToInstance(typeArgType), - diag, - constraints, - AssignTypeFlags.RetainLiteralsForTypeVar - ); - } else if (isInstantiableClass(typeArgType) && ClassType.isBuiltIn(typeArgType, 'Concatenate')) { - const concatTypeArgs = typeArgType.priv.typeArgs; - const functionType = FunctionType.createInstance('', '', '', FunctionTypeFlags.None); - - if (concatTypeArgs && concatTypeArgs.length > 0) { - concatTypeArgs.forEach((typeArg, index) => { - if (index === concatTypeArgs.length - 1) { - FunctionType.addPositionOnlyParamSeparator(functionType); - - if (isParamSpec(typeArg)) { - FunctionType.addParamSpecVariadics(functionType, typeArg); - } else if (isEllipsisType(typeArg)) { - FunctionType.addDefaultParams(functionType); - functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm; - } - } else { - FunctionType.addParam( - functionType, - FunctionParam.create( - ParamCategory.Simple, - typeArg, - FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared, - `__p${index}` - ) - ); - } - }); - } - - assignTypeVar( - evaluatorInterface, - param, - functionType, - diag, - constraints, - AssignTypeFlags.RetainLiteralsForTypeVar - ); - } else if (isEllipsisType(typeArgType)) { - const functionType = FunctionType.createSynthesizedInstance( - '', - FunctionTypeFlags.ParamSpecValue | FunctionTypeFlags.GradualCallableForm - ); - FunctionType.addDefaultParams(functionType); - assignTypeVar(evaluatorInterface, param, functionType, diag, constraints); - } else { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.typeArgListExpected(), - typeArgs[index].node - ); - reportedError = true; - } - } else { - if (index < typeArgs.length && typeArgs[index].typeList) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.typeArgListNotAllowed(), - typeArgs[index].node - ); - reportedError = true; - } - - let typeArgType: Type; - if (index < typeArgs.length) { - typeArgType = convertToInstance(typeArgs[index].type); - } else if (param.shared.isDefaultExplicit) { - typeArgType = solveAndApplyConstraints(param, constraints, { - replaceUnsolved: { - scopeIds: [aliasInfo.shared.typeVarScopeId], - tupleClassType: getTupleClassType(), - }, - }); - } else { - typeArgType = UnknownType.create(); - } - - if ((flags & EvalFlags.EnforceVarianceConsistency) !== 0) { - const usageVariances = inferVarianceForTypeAlias(aliasBaseType); - if (usageVariances && index < usageVariances.length) { - const usageVariance = usageVariances[index]; - - if (!isVarianceOfTypeArgCompatible(typeArgType, usageVariance)) { - const messageDiag = diag.createAddendum(); - messageDiag.addMessage( - LocAddendum.varianceMismatchForTypeAlias().format({ - typeVarName: printType(typeArgType), - typeAliasParam: printType(typeParams[index]), - }) - ); - messageDiag.addTextRange(typeArgs[index].node); - } - } - } - - if (isUnpacked(typeArgType) && !isTypeVarTuple(param)) { - const messageDiag = diag.createAddendum(); - messageDiag.addMessage(LocMessage.unpackedArgInTypeArgument()); - messageDiag.addTextRange(typeArgs[index].node); - typeArgType = UnknownType.create(); - } - - assignTypeVar( - evaluatorInterface, - param, - typeArgType, - diag, - constraints, - AssignTypeFlags.RetainLiteralsForTypeVar - ); - } - }); - - if (!diag.isEmpty()) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.typeNotSpecializable().format({ type: printType(aliasBaseType) }) + diag.getString(), - node, - diag.getEffectiveTextRange() ?? node - ); - reportedError = true; - } - - const solutionSet = solveConstraints(evaluatorInterface, constraints).getMainSolutionSet(); - const aliasTypeArgs: Type[] = []; - - aliasInfo.shared.typeParams?.forEach((typeParam) => { - let typeVarType = solutionSet.getType(typeParam); - - // Fill in any unsolved type arguments with unknown. - if (!typeVarType) { - typeVarType = getUnknownForTypeVar(typeParam, getTupleClassType()); - constraints.setBounds(typeParam, typeVarType); - } - - aliasTypeArgs.push(typeVarType); - }); - - let type = TypeBase.cloneForTypeAlias(solveAndApplyConstraints(aliasBaseType, constraints), { - ...aliasInfo, - typeArgs: aliasTypeArgs, - }); - - type = TypeBase.cloneWithTypeForm(type, reportedError ? undefined : convertToInstance(type)); - - if (baseType.props?.typeAliasInfo) { - return { type, node }; - } - - return { type: TypeBase.cloneWithTypeForm(baseType, convertToInstance(type)), node }; - } - - function getTypeOfIndexWithBaseType( - node: IndexNode, - baseTypeResult: TypeResult, - usage: EvaluatorUsage, - flags: EvalFlags - ): TypeResult { - // Handle the case where we're specializing a generic type alias. - const typeAliasResult = createSpecializedTypeAlias(node, baseTypeResult.type, flags); - if (typeAliasResult) { - return typeAliasResult; - } - - // Handle the case where Never or NoReturn are being specialized. - if (isNever(baseTypeResult.type) && baseTypeResult.type.props?.specialForm) { - // Swap in the special form type, which is the Never or NoReturn class. - baseTypeResult = { ...baseTypeResult, type: baseTypeResult.type.props.specialForm }; - } - - // Handle the case where a TypeAliasType symbol is being specialized - // in a value expression. - if ( - isClassInstance(baseTypeResult.type) && - ClassType.isBuiltIn(baseTypeResult.type, 'TypeAliasType') && - baseTypeResult.type.props?.typeForm - ) { - const typeAliasInfo = baseTypeResult.type.props.typeForm.props?.typeAliasInfo; - if (typeAliasInfo && typeAliasInfo.shared.typeParams) { - const origTypeAlias = TypeBase.cloneForTypeAlias( - convertToInstantiable(baseTypeResult.type.props.typeForm), - { ...typeAliasInfo, typeArgs: undefined } - ); - const typeFormType = createSpecializedTypeAlias(node, origTypeAlias, flags); - if (typeFormType) { - return { - type: TypeBase.cloneWithTypeForm(baseTypeResult.type, convertToInstance(typeFormType.type)), - }; - } - } - } - - if (isTypeVar(baseTypeResult.type) && isTypeAliasPlaceholder(baseTypeResult.type)) { - const typeArgTypes = getTypeArgs(node, flags).map((t) => convertToInstance(t.type)); - const type = TypeBase.cloneForTypeAlias(baseTypeResult.type, { - shared: baseTypeResult.type.shared.recursiveAlias!, - typeArgs: typeArgTypes, - }); - return { type }; - } - - let isIncomplete = baseTypeResult.isIncomplete; - let isRequired = false; - let isNotRequired = false; - let isReadOnly = false; - - // Viper 扩展:TypeSet 类型组合的下标访问语义。 - // Viper 中 `|` 表示类型组合(如 CConst | str = const char*), - // 对 TypeSet 中的修饰类型(CConst/CStatic/CPtr/CVoid/CDefine/CArray)跳过下标访问, - // 让 combineTypes 只合并非修饰类型的结果(如 str/bytes 的下标返回 int/CChar)。 - const viperIndexFileInfo = AnalyzerNodeInfo.getFileInfo(node); - const isViperTypeSetIndex = - IsViperDialect(viperIndexFileInfo.executionEnvironment.dialect) && - isUnion(baseTypeResult.type) && - baseTypeResult.type.priv.subtypes.some( - (subtype) => - isClassInstance(subtype) && - (isCConstType(subtype) || - isCStaticType(subtype) || - isCPtrType(subtype) || - isCVoidType(subtype) || - isCDefineType(subtype) || - isCArrayType(subtype)) - ); - - const type = mapSubtypesExpandTypeVars( - baseTypeResult.type, - /* options */ undefined, - (concreteSubtype, unexpandedSubtype) => { - const selfType = isTypeVar(unexpandedSubtype) ? unexpandedSubtype : undefined; - - // Viper TypeSet:修饰类型跳过下标访问,避免产生 Unknown 污染结果。 - if ( - isViperTypeSetIndex && - isClassInstance(concreteSubtype) && - (isCConstType(concreteSubtype) || - isCStaticType(concreteSubtype) || - isCPtrType(concreteSubtype) || - isCVoidType(concreteSubtype) || - isCDefineType(concreteSubtype) || - isCArrayType(concreteSubtype)) - ) { - return undefined; - } - - if (isAnyOrUnknown(concreteSubtype)) { - if ((flags & EvalFlags.TypeExpression) !== 0) { - // If we are expecting a type annotation here, assume that - // the subscripts are type arguments and evaluate them - // accordingly. - getTypeArgs(node, flags); - } - - return concreteSubtype; - } - - if (flags & EvalFlags.InstantiableType) { - if (isTypeVar(unexpandedSubtype)) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarNotSubscriptable().format({ - type: printType(unexpandedSubtype), - }), - node.d.leftExpr - ); - - // Evaluate the index expressions as though they are type arguments for error-reporting. - getTypeArgs(node, flags); - - return UnknownType.create(); - } - } - - if (isInstantiableClass(concreteSubtype)) { - // See if the class has a custom metaclass that supports __getitem__, etc. - if ( - concreteSubtype.shared.effectiveMetaclass && - isInstantiableClass(concreteSubtype.shared.effectiveMetaclass) && - !ClassType.isBuiltIn(concreteSubtype.shared.effectiveMetaclass, ['type', '_InitVarMeta']) && - (flags & EvalFlags.InstantiableType) === 0 - ) { - const itemMethodType = getBoundMagicMethod( - concreteSubtype, - getIndexAccessMagicMethodName(usage), - /* selfType */ undefined, - node.d.leftExpr - ); - - if ((flags & EvalFlags.TypeExpression) !== 0) { - // If the class doesn't derive from Generic, a type argument should not be allowed. - addDiagnostic( - DiagnosticRule.reportInvalidTypeArguments, - LocMessage.typeArgsExpectingNone().format({ - name: printType(ClassType.cloneAsInstance(concreteSubtype)), - }), - node - ); - } - - if (itemMethodType) { - return getTypeOfIndexedObjectOrClass(node, concreteSubtype, selfType, usage).type; - } - } - - // Setting the value of an indexed class will always result - // in an exception. - if (usage.method === 'set') { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.genericClassAssigned(), - node.d.leftExpr - ); - } else if (usage.method === 'del') { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.genericClassDeleted(), - node.d.leftExpr - ); - } - - if (ClassType.isSpecialBuiltIn(concreteSubtype, 'Literal')) { - // Special-case Literal types. - return createLiteralType(concreteSubtype, node, flags); - } - - if (ClassType.isBuiltIn(concreteSubtype, 'InitVar')) { - // Special-case InitVar, used in dataclasses. - const typeArgs = getTypeArgs(node, flags); - - if ((flags & EvalFlags.TypeExpression) !== 0) { - if ((flags & EvalFlags.VarTypeAnnotation) === 0) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.initVarNotAllowed(), - node.d.leftExpr - ); - } - } - - if (typeArgs.length === 1) { - return typeArgs[0].type; - } else { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.typeArgsMismatchOne().format({ received: typeArgs.length }), - node.d.leftExpr - ); - - return UnknownType.create(); - } - } - - if (ClassType.isEnumClass(concreteSubtype)) { - // Special-case Enum types. - // TODO - validate that there's only one index entry - // that is a str type. - // TODO - validate that literal strings are referencing - // a known enum member. - return ClassType.cloneAsInstance(concreteSubtype); - } - - const isAnnotatedClass = - isInstantiableClass(concreteSubtype) && ClassType.isBuiltIn(concreteSubtype, 'Annotated'); - const hasCustomClassGetItem = - isInstantiableClass(concreteSubtype) && ClassType.hasCustomClassGetItem(concreteSubtype); - const isGenericClass = - concreteSubtype.shared.typeParams?.length > 0 || - ClassType.isSpecialBuiltIn(concreteSubtype) || - ClassType.isBuiltIn(concreteSubtype, 'type') || - ClassType.isPartiallyEvaluated(concreteSubtype); - const isFinalAnnotation = - isInstantiableClass(concreteSubtype) && ClassType.isBuiltIn(concreteSubtype, 'Final'); - const isClassVarAnnotation = - isInstantiableClass(concreteSubtype) && ClassType.isBuiltIn(concreteSubtype, 'ClassVar'); - - // This feature is currently experimental. - const supportsTypedDictTypeArg = - AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.enableExperimentalFeatures && - ClassType.isBuiltIn(concreteSubtype, 'TypedDict'); - - let typeArgs = getTypeArgs(node, flags, { - isAnnotatedClass, - hasCustomClassGetItem: hasCustomClassGetItem || !isGenericClass, - isFinalAnnotation, - isClassVarAnnotation, - supportsTypedDictTypeArg, - }); - - if (!isAnnotatedClass) { - typeArgs = adjustTypeArgsForTypeVarTuple(typeArgs, concreteSubtype.shared.typeParams, node); - } - - // If this is a custom __class_getitem__, there's no need to specialize the class. - // Just return it as is. - if (hasCustomClassGetItem) { - return concreteSubtype; - } - - if (concreteSubtype.priv.typeArgs) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeArguments, - LocMessage.classAlreadySpecialized().format({ - type: printType(convertToInstance(concreteSubtype), { expandTypeAlias: true }), - }), - node.d.leftExpr - ); - return concreteSubtype; - } - - const result = createSpecializedClassType(concreteSubtype, typeArgs, flags, node); - if (result.isRequired) { - isRequired = true; - } else if (result.isNotRequired) { - isNotRequired = true; - } - - if (result.isReadOnly) { - isReadOnly = true; - } - - return result.type; - } - - if (isNoneInstance(concreteSubtype)) { - if (!isIncomplete) { - addDiagnostic( - DiagnosticRule.reportOptionalSubscript, - LocMessage.noneNotSubscriptable(), - node.d.leftExpr - ); - } - - return UnknownType.create(); - } - - if (isClassInstance(concreteSubtype)) { - // Viper TypeSet:str/bytes 在类型组合中表示 C 字符串指针(i8*), - // 下标访问 a[i] 应返回 t.CChar(int 别名),而非 str/bytes。 - if ( - isViperTypeSetIndex && - ClassType.isBuiltIn(concreteSubtype, ['str', 'bytes']) - ) { - // 构造带 CChar 别名信息的 int 类型(保留 t.CChar 显示信息) - const ccharType = getViperCCharType(node); - if (ccharType) { - return ccharType; - } - return concreteSubtype; - } - // Viper TypeSet:非修饰基本类型(int/float/bool 等)在类型组合中 - // 表示指针指向的目标类型(如 CConst | int | CPtr = const int*), - // 下标访问 ptr[i] 应返回该类型本身(解引用),不报 __getitem__ 错误。 - if ( - isViperTypeSetIndex && - ClassType.isBuiltIn(concreteSubtype, [ - 'int', 'float', 'bool', 'complex', 'object', - ]) - ) { - return concreteSubtype; - } + isPureCModifierType, + isCEnumType, + isCEnumSubclassType, + isCTypeSubclassType, + canImplicitlyConvertCType, + isViperIntegerType, + isViperCIntegerTypeName, + isViperCFloatTypeName, + isOverloaded, + isParamSpec, + isPositionOnlySeparator, + isTypeSame, + isTypeVar, + isTypeVarTuple, + isUnbound, + isUnion, + isUnknown, + isUnpacked, + isUnpackedClass, + isUnpackedTypeVarTuple, + LiteralValue, + maxTypeRecursionCount, + ModuleType, + NeverType, + OverloadedType, + ParamSpecType, + removeFromUnion, + removeUnbound, + SentinelLiteral, + TupleTypeArg, + Type, + TypeAliasInfo, + TypeBase, + TypeCategory, + TypeCondition, + TypedDictEntries, + TypeVarKind, + TypeVarScopeId, + TypeVarScopeType, + TypeVarTupleType, + TypeVarType, + UnboundType, + UnionType, + UnknownType, + Variance, +} from './types'; +import { + addConditionToType, + addTypeVarsToListIfUnique, + applySolvedTypeVars, + ApplyTypeVarOptions, + areTypesSame, + buildSolutionFromSpecializedClass, + ClassMember, + combineSameSizedTuples, + combineTupleTypeArgs, + combineVariances, + computeMroLinearization, + containsAnyOrUnknown, + containsLiteralType, + convertToInstance, + convertToInstantiable, + convertTypeToParamSpecValue, + derivesFromAnyOrUnknown, + derivesFromClassRecursive, + derivesFromStdlibClass, + doForEachSubtype, + ensureSignaturesAreUnique, + explodeGenericClass, + getContainerDepth, + getDeclaredGeneratorReturnType, + getGeneratorTypeArgs, + getGeneratorYieldType, + getSpecializedTupleType, + getTypeCondition, + getTypeVarArgsRecursive, + getTypeVarScopeId, + getTypeVarScopeIds, + getUnknownForTypeVar, + getUnknownTypeForCallable, + InferenceContext, + invertVariance, + isDescriptorInstance, + isEffectivelyInstantiable, + isEllipsisType, + isIncompleteUnknown, + isInstantiableMetaclass, + isLiteralLikeType, + isLiteralType, + isMaybeDescriptorInstance, + isMemberReadOnly, + isMetaclassInstance, + isNoneInstance, + isNoneTypeClass, + isOptionalType, + isPartlyUnknown, + isProperty, + isSentinelLiteral, + isTupleClass, + isTupleIndexUnambiguous, + isTypeAliasPlaceholder, + isTypeAliasRecursive, + isTypeVarSame, + isUnboundedTupleClass, + isVarianceOfTypeArgCompatible, + lookUpClassMember, + lookUpObjectMember, + makeFunctionTypeVarsBound, + makeInferenceContext, + makePacked, + makeTypeVarsBound, + makeTypeVarsFree, + mapSignatures, + mapSubtypes, + MemberAccessFlags, + partiallySpecializeType, + preserveUnknown, + removeNoneFromUnion, + requiresSpecialization, + requiresTypeArgs, + selfSpecializeClass, + simplifyFunctionToParamSpec, + someSubtypes, + sortTypes, + specializeForBaseClass, + specializeTupleClass, + specializeWithDefaultTypeArgs, + specializeWithUnknownTypeArgs, + stripTypeForm, + stripTypeFormRecursive, + synthesizeTypeVarForSelfCls, + transformExpectedType, + transformPossibleRecursiveTypeAlias, + UniqueSignatureTracker, + validateTypeVarDefault, +} from './typeUtils'; + +interface GetTypeArgsOptions { + isAnnotatedClass?: boolean; + hasCustomClassGetItem?: boolean; + isFinalAnnotation?: boolean; + isClassVarAnnotation?: boolean; + supportsTypedDictTypeArg?: boolean; +} + +interface MatchArgsToParamsResult { + overload: FunctionType; + overloadIndex: number; + + argumentErrors: boolean; + isTypeIncomplete: boolean; + argParams: ValidateArgTypeParams[]; + activeParam?: FunctionParam | undefined; + paramSpecTarget?: ParamSpecType | undefined; + paramSpecArgList?: Arg[] | undefined; + + // Was there an unpacked argument of unknown length? + unpackedArgOfUnknownLength?: boolean; + + // Did that unpacked argument map to a variadic parameter? + unpackedArgMapsToVariadic?: boolean; + + // A score that indicates how well the overload matches with + // supplied arguments. Used to pick the "best" for purposes + // of error reporting when no matches are found. The higher + // the score, the worse the match. + argumentMatchScore: number; +} + +export interface MemberAccessTypeResult { + type: Type; + isDescriptorApplied?: boolean; + isAsymmetricAccessor?: boolean; + memberAccessDeprecationInfo?: MemberAccessDeprecationInfo; + typeErrors?: boolean; +} + +interface ScopedTypeVarResult { + type: TypeVarType; + scopeNode: TypeParameterScopeNode | AssignmentNode | undefined; + foundInterveningClass: boolean; +} + +interface AliasMapEntry { + alias: string; + module: 'builtins' | 'collections' | 'internals'; + implicitBaseClass?: string; + isSpecialForm?: boolean; + isIllegalInIsinstance?: boolean; + typeParamVariance?: Variance; +} + +interface AssignClassToSelfInfo { + class: ClassType; + assumedVariance: Variance; +} + +interface MatchedOverloadInfo { + overload: FunctionType; + matchResults: MatchArgsToParamsResult; + constraints: ConstraintTracker; + argResults: ArgResult[]; + returnType: Type; +} + +interface ValidateArgTypeOptions { + skipUnknownArgCheck?: boolean; + isArgFirstPass?: boolean; + conditionFilter?: TypeCondition[]; + skipReportError?: boolean; +} + +interface EffectiveReturnTypeOptions { + callSiteInfo?: CallSiteEvaluationInfo; +} + +interface SignatureTrackerStackEntry { + tracker: UniqueSignatureTracker; + rootNode: ParseNode; +} + +// This table contains the names of several built-in types that +// are not subscriptable at runtime on older versions of Python. +// It lists the first version of Python where subscripting is +// allowed. +const nonSubscriptableBuiltinTypes: Map = new Map([ + ['asyncio.futures.Future', pythonVersion3_9], + ['asyncio.tasks.Task', pythonVersion3_9], + ['builtins.dict', pythonVersion3_9], + ['builtins.frozenset', pythonVersion3_9], + ['builtins.list', pythonVersion3_9], + ['builtins._PathLike', pythonVersion3_9], + ['builtins.set', pythonVersion3_9], + ['builtins.tuple', pythonVersion3_9], + ['collections.ChainMap', pythonVersion3_9], + ['collections.Counter', pythonVersion3_9], + ['collections.defaultdict', pythonVersion3_9], + ['collections.DefaultDict', pythonVersion3_9], + ['collections.deque', pythonVersion3_9], + ['collections.OrderedDict', pythonVersion3_9], + ['queue.Queue', pythonVersion3_9], +]); + +// Some types that do not inherit from others are still considered +// "compatible" based on the Python spec. These are sometimes referred +// to as "type promotions". +const typePromotions: Map = new Map([ + ['builtins.float', ['builtins.int']], + ['builtins.complex', ['builtins.float', 'builtins.int']], + ['builtins.bytes', ['builtins.bytearray', 'builtins.memoryview']], +]); + +interface SymbolResolutionStackEntry { + // The symbol ID and declaration being resolved. + symbolId: number; + declaration: Declaration; + + // Initially true, it's set to false if a recursion + // is detected. + isResultValid: boolean; + + // Some limited forms of recursion are allowed. In these + // cases, a partially-constructed type can be registered. + partialType?: Type | undefined; +} + +interface ReturnTypeInferenceContext { + functionNode: FunctionNode; + codeFlowAnalyzer: CodeFlowAnalyzer; +} + +interface ParamSpecArgResult { + argumentErrors: boolean; + constraintTrackers: (ConstraintTracker | undefined)[]; +} + +// How many levels deep should we attempt to infer return +// types based on call-site argument types? The deeper we go, +// the more types we may be able to infer, but the worse the +// performance. +const maxReturnTypeInferenceStackSize = 2; + +// What is the max number of input arguments we should allow +// for call-site return type inference? We've found that large, +// complex functions with many arguments can take too long to +// analyze. +const maxReturnTypeInferenceArgCount = 6; + +// What is the max complexity of the code flow graph that +// we will analyze to determine the return type of a function +// when its parameters are unannotated? We want to keep this +// pretty low because this can be very costly. +const maxReturnTypeInferenceCodeFlowComplexity = 32; + +// What is the max complexity of the code flow graph for +// call-site type inference? This is very expensive, so we +// want to keep this very low. +const maxReturnCallSiteTypeInferenceCodeFlowComplexity = 8; + +// What is the max number of return types cached per function +// when using call-site inference? +const maxCallSiteReturnTypeCacheSize = 8; + +// How many entries in a list, set, or dict should we examine +// when inferring the type? We need to cut it off at some point +// to avoid excessive computation. +const maxEntriesToUseForInference = 64; + +// How many times should attempt to infer a return type of a +// function before giving up and assuming that it won't converge +// due to recursion? +const maxReturnTypeInferenceAttempts = 8; + +// How many assignments to an unannotated variable should be used +// when inferring its type? We need to cut it off at some point +// to avoid excessive computation. +const maxDeclarationsToUseForInference = 64; + +// Maximum number of times to attempt effective type evaluation +// of a variable that has no type declaration. +const maxEffectiveTypeEvaluationAttempts = 16; + +// Maximum number of combinatoric argument type expansions allowed +// when resolving an overload. +const maxTotalOverloadArgTypeExpansionCount = 256; + +// Maximum size of an enum that will be expanded during overload +// argument type expansion. +const maxSingleOverloadArgTypeExpansionCount = 64; + +// Maximum number of recursive function return type inference attempts +// that can be concurrently pending before we give up. +const maxInferFunctionReturnRecursionCount = 12; + +// Maximum recursion amount when comparing two recursive type aliases. +// Increasing this can greatly increase the time required to evaluate +// two recursive type aliases that have the same definition. Decreasing +// it can increase the chance of false negatives for such recursive +// type aliases. +const maxRecursiveTypeAliasRecursionCount = 10; + +// Normally a symbol can have only one type declaration, but there are +// cases where multiple are possible (e.g. a property with a setter +// and a deleter). In extreme cases, we need to limit the number of +// type declarations we consider to avoid excessive computation. +const maxTypedDeclsPerSymbol = 16; + +// This switch enables a special debug mode that attempts to catch +// bugs due to inconsistent evaluation flags used when reading types +// from the type cache. +const verifyTypeCacheEvaluatorFlags = false; + +// This debugging option prints each expression and its evaluated type. +const printExpressionTypes = false; + +// The following number is chosen somewhat arbitrarily. We need to cut +// off code flow analysis at some point for code flow graphs that are too +// complex. Otherwise we risk overflowing the stack or incurring extremely +// long analysis times. This number has been tuned empirically. +export const maxCodeComplexity = 768; + +export interface EvaluatorOptions { + printTypeFlags: TypePrinter.PrintTypeFlags; + logCalls: boolean; + minimumLoggingThreshold: number; + evaluateUnknownImportsAsAny: boolean; + verifyTypeCacheEvaluatorFlags: boolean; +} + +// Describes a "deferred class completion" that is run when a class type is +// fully created and the "PartiallyEvaluated" flag has just been cleared. +// This allows us to properly compute information like the MRO which +// depends on a full understanding of base classes. +interface DeferredClassCompletion { + dependsUpon: ClassType; + classesToComplete: ClassNode[]; +} + +interface TypeCacheEntry { + typeResult: TypeResult; + incompleteGenCount: number; + flags: EvalFlags | undefined; +} + +interface CodeFlowAnalyzerCacheEntry { + typeAtStart: TypeResult | undefined; + codeFlowAnalyzer: CodeFlowAnalyzer; +} + +interface FunctionRecursionInfo { + callerNode: ExpressionNode | undefined; +} + +type LogWrapper = any>(func: T) => (...args: Parameters) => ReturnType; + +interface SuppressedNodeStackEntry { + node: ParseNode; + suppressedDiags: string[] | undefined; +} + +interface ExpectedTypeCacheEntry { + type: Type; + candidates: Type[]; +} + +export function createTypeEvaluator( + importLookup: ImportLookup, + evaluatorOptions: EvaluatorOptions, + wrapWithLogger: LogWrapper +): TypeEvaluator { + const symbolResolutionStack: SymbolResolutionStackEntry[] = []; + const speculativeTypeTracker = new SpeculativeTypeTracker(); + const suppressedNodeStack: SuppressedNodeStackEntry[] = []; + const assignClassToSelfStack: AssignClassToSelfInfo[] = []; + + let functionRecursionMap = new Map(); + let codeFlowAnalyzerCache = new Map(); + let typeCache = new Map(); + let effectiveTypeCache = new Map>(); + let expectedTypeCache = new Map(); + let asymmetricAccessorAssignmentCache = new Set(); + let deferredClassCompletions: DeferredClassCompletion[] = []; + let cancellationToken: CancellationToken | undefined; + let printExpressionSpaceCount = 0; + let incompleteGenCount = 0; + const returnTypeInferenceContextStack: ReturnTypeInferenceContext[] = []; + let returnTypeInferenceTypeCache: Map | undefined; + const signatureTrackerStack: SignatureTrackerStackEntry[] = []; + let prefetched: Partial | undefined; + + function runWithCancellationToken(token: CancellationToken, callback: () => T): T; + function runWithCancellationToken(token: CancellationToken, callback: () => Promise): Promise; + function runWithCancellationToken(token: CancellationToken, callback: () => T | Promise): T | Promise { + // Save the current token and restore it after the callback to support nested calls + const oldToken = cancellationToken; + let result: T | Promise | undefined = undefined; + try { + cancellationToken = token; + result = callback(); + + if (!isThenable(result)) { + return result; + } + + return result.finally(() => { + cancellationToken = oldToken; + }); + } finally { + if (!isThenable(result)) { + cancellationToken = oldToken; + } + } + } + + function checkForCancellation() { + if (cancellationToken) { + throwIfCancellationRequested(cancellationToken); + } + } + + function getTypeCacheEntryCount(): number { + return typeCache.size; + } + + // This function should be called immediately prior to discarding + // the type evaluator. It forcibly replaces existing cache maps + // with empty equivalents. This shouldn't be necessary, but there + // is apparently a bug in the v8 GC where it is unable to detect + // circular references in complex data structures, so it fails + // to clean up the objects if we don't help it out. + function disposeEvaluator() { + functionRecursionMap = new Map(); + codeFlowAnalyzerCache = new Map(); + typeCache = new Map(); + effectiveTypeCache = new Map>(); + expectedTypeCache = new Map(); + asymmetricAccessorAssignmentCache = new Set(); + } + + function readTypeCacheEntry(node: ParseNode) { + // Should we use a temporary cache associated with a contextual + // analysis of a function, contextualized based on call-site argument types? + if (returnTypeInferenceTypeCache && isNodeInReturnTypeInferenceContext(node)) { + return returnTypeInferenceTypeCache.get(node.id); + } else { + return typeCache.get(node.id); + } + } + + function isTypeCached(node: ParseNode) { + const cacheEntry = readTypeCacheEntry(node); + if (!cacheEntry) { + return false; + } + + return !cacheEntry.typeResult.isIncomplete || cacheEntry.incompleteGenCount === incompleteGenCount; + } + + function readTypeCache(node: ParseNode, flags: EvalFlags | undefined): Type | undefined { + const cacheEntry = readTypeCacheEntry(node); + if (!cacheEntry || cacheEntry.typeResult.isIncomplete) { + return undefined; + } + + if (evaluatorOptions.verifyTypeCacheEvaluatorFlags || verifyTypeCacheEvaluatorFlags) { + if (flags !== undefined) { + const expectedFlags = cacheEntry.flags; + + if (expectedFlags !== undefined && flags !== expectedFlags) { + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + const position = convertOffsetToPosition(node.start, fileInfo.lines); + + const message = + `Type cache flag mismatch for node type ${node.nodeType} ` + + `(parent ${node.parent?.nodeType ?? 'none'}): ` + + `cached flags = ${expectedFlags}, access flags = ${flags}, ` + + `file = {${fileInfo.fileUri} [${position.line + 1}:${position.character + 1}]}`; + if (evaluatorOptions.verifyTypeCacheEvaluatorFlags) { + fail(message); + } else { + console.log(message); + } + } + } + } + + return cacheEntry.typeResult.type; + } + + function writeTypeCache( + node: ParseNode, + typeResult: TypeResult, + flags: EvalFlags | undefined, + inferenceContext?: InferenceContext, + allowSpeculativeCaching = false + ) { + // Should we use a temporary cache associated with a contextual + // analysis of a function, contextualized based on call-site argument types? + const typeCacheToUse = + returnTypeInferenceTypeCache && isNodeInReturnTypeInferenceContext(node) + ? returnTypeInferenceTypeCache + : typeCache; + + if (!typeResult.isIncomplete) { + incompleteGenCount++; + } else { + const oldValue = typeCacheToUse.get(node.id); + if (oldValue !== undefined && !isTypeSame(typeResult.type, oldValue.typeResult.type)) { + incompleteGenCount++; + } + } + + typeCacheToUse.set(node.id, { typeResult, flags, incompleteGenCount }); + + // If the entry is located within a part of the parse tree that is currently being + // "speculatively" evaluated, track it so we delete the cached entry when we leave + // this speculative context. + if (isSpeculativeModeInUse(node)) { + speculativeTypeTracker.trackEntry(typeCacheToUse, node.id); + if (allowSpeculativeCaching) { + speculativeTypeTracker.addSpeculativeType( + node, + typeResult, + incompleteGenCount, + inferenceContext?.expectedType + ); + } + } + } + + function setTypeResultForNode(node: ParseNode, typeResult: TypeResult, flags = EvalFlags.None) { + writeTypeCache(node, typeResult, flags); + } + + function setAsymmetricDescriptorAssignment(node: ParseNode) { + if (isSpeculativeModeInUse(/* node */ undefined)) { + return; + } + + asymmetricAccessorAssignmentCache.add(node.id); + } + + function isAsymmetricAccessorAssignment(node: ParseNode) { + return asymmetricAccessorAssignmentCache.has(node.id); + } + + // Determines whether the specified node is contained within + // the function node corresponding to the function that we + // are currently analyzing in the context of parameter types + // defined by a call site. + function isNodeInReturnTypeInferenceContext(node: ParseNode) { + const stackSize = returnTypeInferenceContextStack.length; + if (stackSize === 0) { + return false; + } + + const contextNode = returnTypeInferenceContextStack[stackSize - 1]; + + let curNode: ParseNode | undefined = node; + while (curNode) { + if (curNode === contextNode.functionNode) { + return true; + } + curNode = curNode.parent; + } + + return false; + } + + function getCodeFlowAnalyzerForReturnTypeInferenceContext() { + const stackSize = returnTypeInferenceContextStack.length; + assert(stackSize > 0); + const contextNode = returnTypeInferenceContextStack[stackSize - 1]; + return contextNode.codeFlowAnalyzer; + } + + function getIndexOfSymbolResolution(symbol: Symbol, declaration: Declaration) { + return symbolResolutionStack.findIndex( + (entry) => entry.symbolId === symbol.id && entry.declaration === declaration + ); + } + + function pushSymbolResolution(symbol: Symbol, declaration: Declaration) { + const index = getIndexOfSymbolResolution(symbol, declaration); + if (index >= 0) { + // Mark all of the entries between these two as invalid. + for (let i = index + 1; i < symbolResolutionStack.length; i++) { + symbolResolutionStack[i].isResultValid = false; + } + return false; + } + + symbolResolutionStack.push({ + symbolId: symbol.id, + declaration, + isResultValid: true, + }); + return true; + } + + function popSymbolResolution(symbol: Symbol) { + const poppedEntry = symbolResolutionStack.pop(); + assert( + poppedEntry !== undefined && poppedEntry.symbolId === symbol.id, + `Symbol resolution stack mismatch: expected symbol ${symbol.id}, got ${ + poppedEntry?.symbolId ?? 'empty stack' + }` + ); + return poppedEntry.isResultValid; + } + + function setSymbolResolutionPartialType(symbol: Symbol, declaration: Declaration, type: Type) { + const index = getIndexOfSymbolResolution(symbol, declaration); + if (index >= 0) { + symbolResolutionStack[index].partialType = type; + } + } + + function getSymbolResolutionPartialType(symbol: Symbol, declaration: Declaration): Type | undefined { + const index = getIndexOfSymbolResolution(symbol, declaration); + if (index >= 0) { + return symbolResolutionStack[index].partialType; + } + + return undefined; + } + + // Determines the type of the specified node by evaluating it in + // context, logging any errors in the process. This may require the + // type of surrounding statements to be evaluated. + function getType(node: ExpressionNode): Type | undefined { + initializePrefetchedTypes(node); + + let type = evaluateTypeForSubnode(node, () => { + evaluateTypesForExpressionInContext(node); + })?.type; + + // If this is a type parameter with a calculated variance, see if we + // can swap it out for a version that has a computed variance. + if (type && isTypeVar(type) && type.shared.declaredVariance === Variance.Auto) { + const typeVarType = type; + const typeParamListNode = ParseTreeUtils.getParentNodeOfType( + node, + ParseNodeType.TypeParameterList + ); + + if (typeParamListNode?.parent?.nodeType === ParseNodeType.Class) { + const classTypeResult = getTypeOfClass(typeParamListNode.parent); + + if (classTypeResult) { + inferVarianceForClass(classTypeResult.classType); + + const typeParam = classTypeResult.classType.shared.typeParams.find((param) => + isTypeSame(param, typeVarType, { ignoreTypeFlags: true }) + ); + + if (typeParam?.priv.computedVariance !== undefined) { + type = TypeVarType.cloneWithComputedVariance(type, typeParam.priv.computedVariance); + } + } + } else if (typeParamListNode?.parent?.nodeType === ParseNodeType.TypeAlias) { + const typeAliasType = getTypeOfTypeAlias(typeParamListNode.parent); + const typeParamIndex = typeParamListNode.d.params.findIndex((param) => param.d.name === node); + + if (typeParamIndex >= 0) { + inferVarianceForTypeAlias(typeAliasType); + + const typeAliasInfo = typeAliasType.props?.typeAliasInfo; + if (typeAliasInfo?.shared.computedVariance) { + const computedVariance = typeAliasInfo.shared.computedVariance[typeParamIndex]; + + type = TypeVarType.cloneWithComputedVariance(type, computedVariance); + } + } + } + } + + if (type) { + type = transformPossibleRecursiveTypeAlias(type); + } + + return type; + } + + function getTypeResult(node: ExpressionNode): TypeResult | undefined { + return evaluateTypeForSubnode(node, () => { + evaluateTypesForExpressionInContext(node); + }); + } + + function getTypeResultForDecorator(node: DecoratorNode): TypeResult | undefined { + return evaluateTypeForSubnode(node, () => { + evaluateTypesForExpressionInContext(node.d.expr); + }); + } + + // Reads the type of the node from the cache. + function getCachedType(node: ExpressionNode | DecoratorNode): Type | undefined { + return readTypeCache(node, EvalFlags.None); + } + + // Determines the expected type of a specified node based on surrounding + // context. For example, if it's a subexpression of an argument expression, + // the associated parameter type might inform the expected type. + function getExpectedType(node: ExpressionNode): ExpectedTypeResult | undefined { + // This is a primary entry point called by language server providers, + // and it might be called before any other type evaluation has occurred. + // Use this opportunity to do some initialization. + initializePrefetchedTypes(node); + + // Scan up the parse tree to find the top-most expression node + // so we can evaluate the entire expression. + let topExpression = node; + let curNode: ParseNode | undefined = node; + while (curNode) { + if (isExpressionNode(curNode)) { + topExpression = curNode; + } + + curNode = curNode.parent; + } + + // Evaluate the expression. This will have the side effect of + // storing an expected type in the expected type cache. + evaluateTypesForExpressionInContext(topExpression); + + // Look for the resulting expected type by scanning up the parse tree. + curNode = node; + while (curNode) { + const expectedType = expectedTypeCache.get(curNode.id); + if (expectedType) { + return { + type: expectedType.type, + node: curNode, + candidates: ensureExpectedTypeCandidates(expectedType.type, expectedType.candidates), + }; + } + + if (curNode === topExpression) { + break; + } + + curNode = curNode.parent; + } + + return undefined; + } + + function addExpectedTypeCacheEntry(node: ParseNode, expectedType: Type) { + const cached = expectedTypeCache.get(node.id); + if (!cached) { + expectedTypeCache.set(node.id, { + type: expectedType, + candidates: [expectedType], + }); + return; + } + + cached.type = expectedType; + if (!cached.candidates.some((candidate) => isTypeSame(candidate, expectedType))) { + cached.candidates.push(expectedType); + } + } + + function initializePrefetchedTypes(node: ParseNode) { + if (!prefetched) { + // Some of these types have cyclical dependencies on each other, + // so don't re-enter this block once we start executing it. + prefetched = {}; + + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + + prefetched.objectClass = getBuiltInType(node, 'object'); + prefetched.typeClass = getBuiltInType(node, 'type'); + prefetched.functionClass = getTypesType(node, 'FunctionType') ?? getBuiltInType(node, 'function'); + prefetched.methodClass = getTypesType(node, 'MethodType'); + + prefetched.unionTypeClass = getTypesType(node, 'UnionType'); + if (prefetched.unionTypeClass && isClass(prefetched.unionTypeClass)) { + prefetched.unionTypeClass.shared.flags |= ClassTypeFlags.SpecialFormClass; + } + + // Initialize and cache "Collection" to break a cyclical dependency + // that occurs when resolving tuple below. + getTypingType(node, 'Collection'); + + prefetched.noneTypeClass = getTypeshedType(node, 'NoneType') ?? UnknownType.create(); + prefetched.tupleClass = getBuiltInType(node, 'tuple'); + prefetched.boolClass = getBuiltInType(node, 'bool'); + prefetched.intClass = getBuiltInType(node, 'int'); + prefetched.strClass = getBuiltInType(node, 'str'); + prefetched.dictClass = getBuiltInType(node, 'dict'); + prefetched.moduleTypeClass = getTypingType(node, 'ModuleType'); + prefetched.typedDictPrivateClass = + getTypeCheckerInternalsType(node, 'TypedDictFallback') ?? getTypingType(node, '_TypedDict'); + prefetched.typedDictClass = getTypingType(node, 'TypedDict'); + prefetched.awaitableClass = getTypingType(node, 'Awaitable'); + prefetched.mappingClass = getTypingType(node, 'Mapping'); + + // Don't attempt to resolve the string.templatelib if viper is configured for + // Python 3.13 or older. Doing so will either fail to resolve (if running on Python 3.13 + // or older) or resolve to the templatelib.py source file (if running on Python 3.14). + if (PythonVersion.isGreaterOrEqualTo(fileInfo.executionEnvironment.pythonVersion, pythonVersion3_14)) { + prefetched.templateClass = getTypeOfModule(node, 'Template', ['string', 'templatelib']); + } else { + prefetched.templateClass = UnknownType.create(); + } + + prefetched.supportsKeysAndGetItemClass = getTypeshedType(node, 'SupportsKeysAndGetItem'); + if (!prefetched.supportsKeysAndGetItemClass) { + // Fall back on 'Mapping' if 'SupportsKeysAndGetItem' is not available. + prefetched.supportsKeysAndGetItemClass = prefetched.mappingClass; + } + + // Wire up the `Any` class to the special-form version of our internal AnyType. + if ( + prefetched.objectClass && + isInstantiableClass(prefetched.objectClass) && + prefetched.typeClass && + isInstantiableClass(prefetched.typeClass) + ) { + const anyClass = ClassType.createInstantiable( + 'Any', + 'typing.Any', + 'typing', + Uri.empty(), + ClassTypeFlags.BuiltIn | ClassTypeFlags.SpecialFormClass | ClassTypeFlags.IllegalIsinstanceClass, + /* typeSourceId */ -1, + /* declaredMetaclass */ undefined, + /* effectiveMetaclass */ prefetched.typeClass + ); + anyClass.shared.baseClasses.push(prefetched.objectClass); + computeMroLinearization(anyClass); + const anySpecialForm = AnyType.createSpecialForm(); + + if (isAny(anySpecialForm)) { + TypeBase.setSpecialForm(anySpecialForm, anyClass); + TypeBase.setTypeForm(anySpecialForm, convertToInstance(anySpecialForm)); + } + } + } + } + + function getTypeOfExpression( + node: ExpressionNode, + flags = EvalFlags.None, + inferenceContext?: InferenceContext + ): TypeResult { + // Is this type already cached? + const cacheEntry = readTypeCacheEntry(node); + if (cacheEntry) { + if (!cacheEntry.typeResult.isIncomplete || cacheEntry.incompleteGenCount === incompleteGenCount) { + if (printExpressionTypes) { + console.log( + `${getPrintExpressionTypesSpaces()}${ParseTreeUtils.printExpression(node)} (${getLineNum( + node + )}): Cached ${printType(cacheEntry.typeResult.type)} ${ + cacheEntry.typeResult.typeErrors ? ' Errors' : '' + }` + ); + } + + return cacheEntry.typeResult; + } + } + + // Is it cached in the speculative type cache? + const specCacheEntry = speculativeTypeTracker.getSpeculativeType(node, inferenceContext?.expectedType); + if (specCacheEntry) { + if ( + !specCacheEntry.typeResult.isIncomplete || + specCacheEntry.incompleteGenerationCount === incompleteGenCount + ) { + if (printExpressionTypes) { + console.log( + `${getPrintExpressionTypesSpaces()}${ParseTreeUtils.printExpression(node)} (${getLineNum( + node + )}): Speculative ${printType(specCacheEntry.typeResult.type)}` + ); + } + + return specCacheEntry.typeResult; + } + } + + if (printExpressionTypes) { + console.log( + `${getPrintExpressionTypesSpaces()}${ParseTreeUtils.printExpression(node)} (${getLineNum(node)}): Pre` + ); + printExpressionSpaceCount++; + } + + // This is a frequently-called routine, so it's a good place to call + // the cancellation check. If the operation is canceled, an exception + // will be thrown at this point. + checkForCancellation(); + + if (inferenceContext) { + inferenceContext.expectedType = transformPossibleRecursiveTypeAlias(inferenceContext.expectedType); + } + + // If we haven't already fetched some core type definitions from the + // typeshed stubs, do so here. It would be better to fetch this when it's + // needed in assignType, but we don't have access to the parse tree + // at that point. + initializePrefetchedTypes(node); + + let typeResult = getTypeOfExpressionCore(node, flags, inferenceContext); + + // Should we disable type promotions for bytes? + if ( + isInstantiableClass(typeResult.type) && + typeResult.type.priv.includePromotions && + !typeResult.type.priv.includeSubclasses && + ClassType.isBuiltIn(typeResult.type, 'bytes') + ) { + if (AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.disableBytesTypePromotions) { + typeResult = { + ...typeResult, + type: ClassType.cloneRemoveTypePromotions(typeResult.type), + }; + } + } + + if (inferenceContext) { + // Handle TypeForm assignments. + typeResult.type = convertToTypeFormType(inferenceContext.expectedType, typeResult.type); + } + + // Don't allow speculative caching for assignment expressions because + // the target name node won't have a corresponding type cached speculatively. + const allowSpeculativeCaching = node.nodeType !== ParseNodeType.AssignmentExpression; + + writeTypeCache(node, typeResult, flags, inferenceContext, allowSpeculativeCaching); + + if (node.nodeType === ParseNodeType.Name || node.nodeType === ParseNodeType.MemberAccess) { + // If this is a generic function and there is a signature tracker, + // make sure the signature is unique. + typeResult.type = ensureSignatureIsUnique(typeResult.type, node); + } + + // If there was an expected type, make sure that the result type is compatible. + if ( + inferenceContext && + !isAnyOrUnknown(inferenceContext.expectedType) && + !isNever(inferenceContext.expectedType) + ) { + addExpectedTypeCacheEntry(node, inferenceContext.expectedType); + + if (!typeResult.isIncomplete && !typeResult.expectedTypeDiagAddendum) { + const diag = new DiagnosticAddendum(); + + // Make sure the resulting type is assignable to the expected type. + if ( + !assignType( + inferenceContext.expectedType, + typeResult.type, + diag, + /* constraints */ undefined, + AssignTypeFlags.Default + ) + ) { + // Set the typeErrors to true, but first make a copy of the + // type result because the (non-error) version may already + // be cached. + typeResult = { ...typeResult, typeErrors: true }; + typeResult.expectedTypeDiagAddendum = diag; + diag.addTextRange(node); + } + } + } + + if (printExpressionTypes) { + printExpressionSpaceCount--; + console.log( + `${getPrintExpressionTypesSpaces()}${ParseTreeUtils.printExpression(node)} (${getLineNum( + node + )}): Post ${printType(typeResult.type)}${typeResult.isIncomplete ? ' Incomplete' : ''}` + ); + } + + return typeResult; + } + + // This is a helper function that implements the core of getTypeOfExpression. + function getTypeOfExpressionCore( + node: ExpressionNode, + flags = EvalFlags.None, + inferenceContext?: InferenceContext + ): TypeResult { + let typeResult: TypeResult | undefined; + let expectingInstantiable = (flags & EvalFlags.InstantiableType) !== 0; + + switch (node.nodeType) { + case ParseNodeType.Name: { + typeResult = getTypeOfName(node, flags); + break; + } + + case ParseNodeType.MemberAccess: { + typeResult = getTypeOfMemberAccess(node, flags); + break; + } + + case ParseNodeType.Index: { + typeResult = getTypeOfIndex(node, flags); + break; + } + + case ParseNodeType.Call: { + typeResult = useSignatureTracker(node, () => getTypeOfCall(node, flags, inferenceContext)); + break; + } + + case ParseNodeType.Tuple: { + typeResult = getTypeOfTuple(evaluatorInterface, node, flags, inferenceContext); + break; + } + + case ParseNodeType.Constant: { + typeResult = getTypeOfConstant(node, flags); + break; + } + + case ParseNodeType.StringList: { + if ((flags & EvalFlags.StrLiteralAsType) !== 0) { + // Don't report expecting type errors again. We will have already + // reported them when analyzing the contents of the string. + expectingInstantiable = false; + } + + typeResult = getTypeOfStringList(node, flags, inferenceContext); + break; + } + + case ParseNodeType.Number: { + typeResult = getTypeOfNumber(node, typeResult); + break; + } + + case ParseNodeType.Ellipsis: { + typeResult = getTypeOfEllipsis(flags, typeResult, node); + break; + } + + case ParseNodeType.UnaryOperation: { + typeResult = getTypeOfUnaryOperation(evaluatorInterface, node, flags, inferenceContext); + break; + } + + case ParseNodeType.BinaryOperation: { + let effectiveFlags = flags; + + // If we're expecting an instantiable type and this isn't a union operator, + // don't require that the two operands are also instantiable types. + if (expectingInstantiable && node.d.operator !== OperatorType.BitwiseOr) { + effectiveFlags &= ~EvalFlags.InstantiableType; + } + + typeResult = getTypeOfBinaryOperation(evaluatorInterface, node, effectiveFlags, inferenceContext); + break; + } + + case ParseNodeType.AugmentedAssignment: { + typeResult = getTypeOfAugmentedAssignment(evaluatorInterface, node, inferenceContext); + break; + } + + case ParseNodeType.List: + case ParseNodeType.Set: { + typeResult = getTypeOfListOrSet(node, flags, inferenceContext); + break; + } + + case ParseNodeType.Slice: { + typeResult = getTypeOfSlice(node); + break; + } + + case ParseNodeType.Await: { + typeResult = getTypeOfAwaitOperator(node, flags, inferenceContext); + break; + } + + case ParseNodeType.Ternary: { + typeResult = getTypeOfTernaryOperation(evaluatorInterface, node, flags, inferenceContext); + break; + } + + case ParseNodeType.Comprehension: { + typeResult = getTypeOfComprehension(node, flags, inferenceContext); + break; + } + + case ParseNodeType.Dictionary: { + typeResult = getTypeOfDictionary(node, flags, inferenceContext); + break; + } + + case ParseNodeType.Lambda: { + typeResult = getTypeOfLambda(node, inferenceContext); + break; + } + + case ParseNodeType.Assignment: { + typeResult = getTypeOfExpression(node.d.rightExpr, flags, inferenceContext); + assignTypeToExpression( + node.d.leftExpr, + typeResult, + node.d.rightExpr, + /* ignoreEmptyContainers */ true, + /* allowAssignmentToFinalVar */ true + ); + break; + } + + case ParseNodeType.AssignmentExpression: { + if ((flags & EvalFlags.TypeExpression) !== 0) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.walrusNotAllowed(), node); + } + + typeResult = getTypeOfExpression(node.d.rightExpr, flags, inferenceContext); + assignTypeToExpression(node.d.name, typeResult, node.d.rightExpr, /* ignoreEmptyContainers */ true); + break; + } + + case ParseNodeType.Yield: { + typeResult = getTypeOfYield(node); + break; + } + + case ParseNodeType.YieldFrom: { + typeResult = getTypeOfYieldFrom(node); + break; + } + + case ParseNodeType.Unpack: { + typeResult = getTypeOfUnpackOperator(node, flags, inferenceContext); + break; + } + + case ParseNodeType.TypeAnnotation: { + typeResult = getTypeOfExpression( + node.d.annotation, + EvalFlags.InstantiableType | + EvalFlags.TypeExpression | + EvalFlags.StrLiteralAsType | + EvalFlags.NoParamSpec | + EvalFlags.NoTypeVarTuple | + EvalFlags.VarTypeAnnotation + ); + break; + } + + case ParseNodeType.String: + case ParseNodeType.FormatString: { + typeResult = getTypeOfString(node); + break; + } + + case ParseNodeType.Error: { + // Evaluate the child expression as best we can so the + // type information is cached for the completion handler. + suppressDiagnostics(node, () => { + if (node.d.child) { + getTypeOfExpression(node.d.child); + } + }); + typeResult = { type: UnknownType.create() }; + break; + } + + default: + assertNever(node, `Illegal node type: ${(node as any).nodeType}`); + } + + if (!typeResult) { + // We shouldn't get here. If we do, report an error. + fail(`Unhandled expression type '${ParseTreeUtils.printExpression(node)}'`); + } + + // Do we need to validate that the type is instantiable? + if (expectingInstantiable) { + validateTypeIsInstantiable(typeResult, flags, node); + } + + // If this is a PEP 695 type alias, remove the special form so the type + // printer prints it as its aliased type rather than TypeAliasType. + if ((flags & EvalFlags.TypeExpression) !== 0 && typeResult.type.props?.typeForm === undefined) { + const specialForm = typeResult.type.props?.specialForm; + if (specialForm && ClassType.isBuiltIn(specialForm, 'TypeAliasType')) { + typeResult.type = TypeBase.cloneAsSpecialForm(typeResult.type, undefined); + } + } + + return typeResult; + } + + // Reports the case where a function or class has been decorated with + // @type_check_only and is used in a value expression. + function reportUseOfTypeCheckOnly(type: Type, node: ExpressionNode) { + let isTypeCheckingOnly = false; + let name = ''; + + if (isInstantiableClass(type) && !type.priv.includeSubclasses) { + isTypeCheckingOnly = ClassType.isTypeCheckOnly(type); + name = type.shared.name; + } else if (isFunction(type)) { + isTypeCheckingOnly = FunctionType.isTypeCheckOnly(type); + name = type.shared.name; + } + + if (isTypeCheckingOnly) { + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + + if (!fileInfo.isStubFile) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeCheckOnly().format({ name }), + node + ); + } + } + } + + function validateTypeIsInstantiable(typeResult: TypeResult, flags: EvalFlags, node: ExpressionNode) { + // If the type is incomplete, don't log any diagnostics yet. + if (typeResult.isIncomplete) { + return; + } + + if ((flags & EvalFlags.NoTypeVarTuple) !== 0) { + if (isTypeVarTuple(typeResult.type) && !typeResult.type.priv.isInUnion) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeVarTupleContext(), node); + typeResult.type = UnknownType.create(); + } + } + + if (isEffectivelyInstantiable(typeResult.type, { honorTypeVarBounds: true })) { + return; + } + + // Exempt ellipses. + if (isClassInstance(typeResult.type) && ClassType.isBuiltIn(typeResult.type, ['EllipsisType', 'ellipsis'])) { + return; + } + + // Viper 扩展:t.CDefine 常量豁免可实例化检查。 + // CDefine 是编译时常量,在类型下标中作为参数使用时 + // (如 CArray[int_slot, UHCI_INT_SLOTS]),不应要求其可实例化。 + if (isCDefineType(typeResult.type)) { + return; + } + + // Viper 扩展:整数字面量豁免可实例化检查。 + // Viper 中 CArray[T, N] 的 N 是数组长度(整数字面量), + // 如 CArray[t.CUInt32T, 6],不应要求整数可实例化。 + if ( + isClassInstance(typeResult.type) && + isLiteralType(typeResult.type) && + IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect) + ) { + return; + } + + // Viper 扩展:Viper 是 AOT 静态语言,typedef/TypeSet 等类型别名 + // 在跨模块 import 后可能被求值为类实例(如 int)而非类本身。 + // 在 Viper 模式下,豁免所有类型的可实例化检查。 + if (IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect)) { + return; + } + + // Emit these errors only if we know we're evaluating a type expression. + if ((flags & EvalFlags.TypeExpression) !== 0) { + const diag = new DiagnosticAddendum(); + if (isUnion(typeResult.type)) { + doForEachSubtype(typeResult.type, (subtype) => { + if (!isEffectivelyInstantiable(subtype, { honorTypeVarBounds: true })) { + diag.addMessage(LocAddendum.typeNotClass().format({ type: printType(subtype) })); + } + }); + } + + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeExpectedClass().format({ type: printType(typeResult.type) }) + diag.getString(), + node + ); + + typeResult.type = UnknownType.create(); + } + + typeResult.typeErrors = true; + } + + function getTypeOfAwaitOperator(node: AwaitNode, flags: EvalFlags, inferenceContext?: InferenceContext) { + if ((flags & EvalFlags.TypeExpression) !== 0) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.awaitNotAllowed(), node); + return { type: UnknownType.create() }; + } + + const expectedType = inferenceContext + ? createAwaitableReturnType( + node, + inferenceContext.expectedType, + /* isGenerator */ false, + /* useCoroutine */ false + ) + : undefined; + + const exprTypeResult = getTypeOfExpression(node.d.expr, flags, makeInferenceContext(expectedType)); + const awaitableResult = getTypeOfAwaitable(exprTypeResult, node.d.expr); + const typeResult: TypeResult = { + type: awaitableResult.type, + isIncomplete: exprTypeResult.isIncomplete || awaitableResult.isIncomplete, + typeErrors: exprTypeResult.typeErrors, + }; + + if (exprTypeResult.isIncomplete) { + typeResult.isIncomplete = true; + } + return typeResult; + } + + function getTypeOfEllipsis(flags: EvalFlags, typeResult: TypeResult | undefined, node: ExpressionNode) { + if ((flags & EvalFlags.ConvertEllipsisToAny) !== 0) { + typeResult = { type: AnyType.create(/* isEllipsis */ true) }; + } else { + if ((flags & EvalFlags.TypeExpression) !== 0 && (flags & EvalFlags.AllowEllipsis) === 0) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.ellipsisContext(), node); + typeResult = { type: UnknownType.create() }; + } else { + const ellipsisType = + getBuiltInObject(node, 'EllipsisType') ?? getBuiltInObject(node, 'ellipsis') ?? AnyType.create(); + typeResult = { type: ellipsisType }; + } + } + return typeResult; + } + + function getTypeOfNumber(node: NumberNode, typeResult: TypeResult | undefined) { + if (node.d.isImaginary) { + typeResult = { type: getBuiltInObject(node, 'complex') }; + } else if (node.d.isInteger) { + typeResult = { type: cloneBuiltinObjectWithLiteral(node, 'int', node.d.value) }; + } else { + typeResult = { type: getBuiltInObject(node, 'float') }; + } + return typeResult; + } + + function getTypeOfUnpackOperator(node: UnpackNode, flags: EvalFlags, inferenceContext?: InferenceContext) { + let typeResult: TypeResult | undefined; + let iterExpectedType: Type | undefined; + + if (inferenceContext) { + const iterableType = getBuiltInType(node, 'Iterable'); + if (iterableType && isInstantiableClass(iterableType)) { + iterExpectedType = ClassType.cloneAsInstance( + ClassType.specialize(iterableType, [inferenceContext.expectedType]) + ); + } + } + + const iterTypeResult = getTypeOfExpression(node.d.expr, flags, makeInferenceContext(iterExpectedType)); + const iterType = iterTypeResult.type; + if ((flags & EvalFlags.NoTypeVarTuple) === 0 && isTypeVarTuple(iterType) && !iterType.priv.isUnpacked) { + typeResult = { type: TypeVarType.cloneForUnpacked(iterType) }; + } else if ( + (flags & EvalFlags.AllowUnpackedTuple) !== 0 && + isInstantiableClass(iterType) && + ClassType.isBuiltIn(iterType, 'tuple') + ) { + typeResult = { type: ClassType.cloneForUnpacked(iterType) }; + } else if ((flags & EvalFlags.TypeExpression) !== 0) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.unpackInAnnotation(), + node, + node.d.starToken + ); + typeResult = { type: UnknownType.create() }; + } else { + const iteratorTypeResult = getTypeOfIterator(iterTypeResult, /* isAsync */ false, node) ?? { + type: UnknownType.create(!!iterTypeResult.isIncomplete), + isIncomplete: iterTypeResult.isIncomplete, + }; + typeResult = { + type: iteratorTypeResult.type, + typeErrors: iterTypeResult.typeErrors, + unpackedType: iterType, + isIncomplete: iteratorTypeResult.isIncomplete, + }; + } + + return typeResult; + } + + function getTypeOfStringList( + node: StringListNode, + flags: EvalFlags, + inferenceContext?: InferenceContext + ): TypeResult { + let typeResult: TypeResult | undefined; + + if ((flags & EvalFlags.StrLiteralAsType) !== 0 && (flags & EvalFlags.TypeFormArg) === 0) { + return getTypeOfStringListAsType(node, flags); + } + + const isBytesNode = (node: StringNode | FormatStringNode) => + (node.d.token.flags & StringTokenFlags.Bytes) !== 0; + + // Check for mixing of bytes and str, which is not allowed. + const firstStrIndex = node.d.strings.findIndex((str) => !isBytesNode(str)); + const firstBytesIndex = node.d.strings.findIndex((str) => isBytesNode(str)); + if (firstStrIndex >= 0 && firstBytesIndex >= 0) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.mixingBytesAndStr(), + node.d.strings[Math.max(firstBytesIndex, firstStrIndex)] + ); + + return { type: UnknownType.create() }; + } + + const isBytes = firstBytesIndex >= 0; + let isLiteralString = true; + let isIncomplete = false; + let isTemplate = false; + + node.d.strings.forEach((expr) => { + // Handle implicit concatenation. + const typeResult = getTypeOfString(expr); + + if (typeResult.isIncomplete) { + isIncomplete = true; + } + + let isExprLiteralString = false; + + if (isClassInstance(typeResult.type)) { + if (ClassType.isBuiltIn(typeResult.type, 'str') && typeResult.type.priv.literalValue !== undefined) { + isExprLiteralString = true; + } else if (ClassType.isBuiltIn(typeResult?.type, 'LiteralString')) { + isExprLiteralString = true; + } + + if (typeResult.type.shared.name === 'Template') { + isTemplate = true; + } + } + + if (!isExprLiteralString) { + isLiteralString = false; + } + }); + + if (isTemplate) { + const templateType = + prefetched?.templateClass && isInstantiableClass(prefetched?.templateClass) + ? ClassType.cloneAsInstance(prefetched.templateClass) + : UnknownType.create(); + + typeResult = { type: templateType, isIncomplete }; + } else if (node.d.strings.some((str) => str.nodeType === ParseNodeType.FormatString)) { + if (isLiteralString) { + const literalStringType = getTypingType(node, 'LiteralString'); + if (literalStringType && isInstantiableClass(literalStringType)) { + typeResult = { type: ClassType.cloneAsInstance(literalStringType) }; + } + } + + if (!typeResult) { + typeResult = { + type: getBuiltInObject(node, isBytes ? 'bytes' : 'str'), + isIncomplete, + }; + } + } else { + typeResult = { + type: cloneBuiltinObjectWithLiteral( + node, + isBytes ? 'bytes' : 'str', + node.d.strings.map((s) => s.d.value).join('') + ), + isIncomplete, + }; + } + + if (node.d.strings.length !== 1 || node.d.strings[0].nodeType !== ParseNodeType.String) { + return typeResult; + } + + // Only attempt to interpret the string as a TypeForm forward reference when + // there's a signal that a TypeForm value is wanted in this context. Doing it + // unconditionally can trigger expensive (and recursion-prone) type lookups + // for plain string literals in non-type contexts. + const wantsTypeForm = + (flags & EvalFlags.TypeFormArg) !== 0 || + (inferenceContext !== undefined && expectedTypeWantsTypeForm(inferenceContext.expectedType)); + if (!wantsTypeForm) { + return typeResult; + } + + // For performance reasons, do not attempt to treat the string literal + // as a TypeForm if it's going to fail anyway or is unlikely to be a + // TypeForm (really long, triple-quoted, etc.). + const stringNode = node.d.strings[0]; + const tokenFlags = stringNode.d.token.flags; + const disallowedTokenFlags = + StringTokenFlags.Bytes | + StringTokenFlags.Raw | + StringTokenFlags.Format | + StringTokenFlags.Template | + StringTokenFlags.Triplicate; + const maxTypeFormStringLength = 256; + + if ( + (tokenFlags & disallowedTokenFlags) !== 0 || + stringNode.d.token.escapedValue.length >= maxTypeFormStringLength + ) { + return typeResult; + } + + const typeFormResult = getTypeOfStringListAsType(node, flags); + if (typeFormResult.type.props?.typeForm) { + typeResult.type = TypeBase.cloneWithTypeForm(typeResult.type, typeFormResult.type.props.typeForm); + } + + return typeResult; + } + + function getTypeOfStringListAsType(node: StringListNode, flags: EvalFlags): TypeResult { + const reportTypeErrors = (flags & EvalFlags.StrLiteralAsType) !== 0; + let updatedFlags = flags | EvalFlags.ForwardRefs | EvalFlags.InstantiableType; + let typeResult: TypeResult | undefined; + + // In most cases, annotations within a string are not parsed by the interpreter. + // There are a few exceptions (e.g. the "bound" value for a TypeVar constructor). + if ((flags & EvalFlags.ParsesStringLiteral) === 0) { + updatedFlags |= EvalFlags.NotParsed; + } + + updatedFlags &= ~EvalFlags.TypeFormArg; + + if (node.d.annotation && (flags & EvalFlags.TypeExpression) !== 0) { + return getTypeOfExpression(node.d.annotation, updatedFlags); + } + + if (node.d.strings.length === 1) { + const tokenFlags = node.d.strings[0].d.token.flags; + + if (tokenFlags & StringTokenFlags.Bytes) { + if (reportTypeErrors) { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.annotationBytesString(), node); + } + return { type: UnknownType.create() }; + } + + if (tokenFlags & StringTokenFlags.Raw) { + if (reportTypeErrors) { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.annotationRawString(), node); + } + return { type: UnknownType.create() }; + } + + if (tokenFlags & StringTokenFlags.Format) { + if (reportTypeErrors) { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.annotationFormatString(), node); + } + return { type: UnknownType.create() }; + } + + if (tokenFlags & StringTokenFlags.Template) { + if (reportTypeErrors) { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.annotationTemplateString(), node); + } + return { type: UnknownType.create() }; + } + + // We didn't know at parse time that this string node was going + // to be evaluated as a forward-referenced type. We need + // to re-invoke the parser at this stage. + const expr = parseStringAsTypeAnnotation(node, reportTypeErrors); + if (expr) { + typeResult = useSpeculativeMode(reportTypeErrors ? undefined : node, () => { + return getTypeOfExpression(expr, updatedFlags); + }); + } + } + + if (!typeResult) { + if (reportTypeErrors) { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.expectedTypeNotString(), node); + } + typeResult = { type: UnknownType.create() }; + } + + return typeResult; + } + + function getTypeOfString(node: StringNode | FormatStringNode): TypeResult { + const isBytes = (node.d.token.flags & StringTokenFlags.Bytes) !== 0; + let typeResult: TypeResult | undefined; + let isIncomplete = false; + + if (node.nodeType === ParseNodeType.String) { + typeResult = { + type: cloneBuiltinObjectWithLiteral(node, isBytes ? 'bytes' : 'str', node.d.value), + isIncomplete, + }; + } else { + const isTemplateString = (node.d.token.flags & StringTokenFlags.Template) !== 0; + let isLiteralString = true; + + // If all of the format expressions are of type LiteralString, then + // the resulting formatted string is also LiteralString. + node.d.fieldExprs.forEach((expr) => { + const exprTypeResult = getTypeOfExpression(expr); + const exprType = exprTypeResult.type; + + if (exprTypeResult.isIncomplete) { + isIncomplete = true; + } + + doForEachSubtype(exprType, (exprSubtype) => { + if (!isClassInstance(exprSubtype)) { + isLiteralString = false; + return; + } + + if (ClassType.isBuiltIn(exprSubtype, 'LiteralString')) { + return; + } + + if (ClassType.isBuiltIn(exprSubtype, 'str') && exprSubtype.priv.literalValue !== undefined) { + return; + } + + isLiteralString = false; + }); + }); + + if (isTemplateString) { + const templateType = + prefetched?.templateClass && isInstantiableClass(prefetched?.templateClass) + ? ClassType.cloneAsInstance(prefetched.templateClass) + : UnknownType.create(); + + typeResult = { type: templateType, isIncomplete }; + } else if (!isBytes && isLiteralString) { + const literalStringType = getTypingType(node, 'LiteralString'); + if (literalStringType && isInstantiableClass(literalStringType)) { + typeResult = { type: ClassType.cloneAsInstance(literalStringType), isIncomplete }; + } + } + + if (!typeResult) { + typeResult = { + type: getBuiltInObject(node, isBytes ? 'bytes' : 'str'), + isIncomplete, + }; + + if (isClass(typeResult.type) && typeResult.type.priv.includePromotions) { + typeResult.type = ClassType.cloneRemoveTypePromotions(typeResult.type); + } + } + } + + return typeResult; + } + + function stripLiteralValue(type: Type): Type { + // Handle the not-uncommon case where the type is a union that consists + // only of literal values. + if (isUnion(type) && type.priv.subtypes.length > 0) { + if ( + type.priv.literalInstances.literalStrMap?.size === type.priv.subtypes.length || + type.priv.literalInstances.literalIntMap?.size === type.priv.subtypes.length || + type.priv.literalInstances.literalEnumMap?.size === type.priv.subtypes.length + ) { + return stripLiteralValue(type.priv.subtypes[0]); + } + } + + return mapSubtypes(type, (subtype) => { + if (isClass(subtype)) { + if (subtype.priv.literalValue !== undefined) { + subtype = ClassType.cloneWithLiteral(subtype, /* value */ undefined); + } + + if (ClassType.isBuiltIn(subtype, 'LiteralString')) { + // Handle "LiteralString" specially. + if (prefetched?.strClass && isInstantiableClass(prefetched.strClass)) { + let strInstance = ClassType.cloneAsInstance(prefetched.strClass); + strInstance = TypeBase.cloneForCondition(strInstance, getTypeCondition(subtype)); + return strInstance; + } + } + } + + return subtype; + }); + } + + function getTypeOfParamAnnotation(paramTypeNode: ExpressionNode, paramCategory: ParamCategory) { + return getTypeOfAnnotation(paramTypeNode, { + typeVarGetsCurScope: true, + allowUnpackedTuple: paramCategory === ParamCategory.ArgsList, + allowUnpackedTypedDict: paramCategory === ParamCategory.KwargsDict, + }); + } + + function getTypeOfAnnotation(node: ExpressionNode, options?: ExpectedTypeOptions): Type { + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + + // Special-case the typing.pyi file, which contains some special + // types that the type analyzer needs to interpret differently. + if (fileInfo.isTypingStubFile || fileInfo.isTypingExtensionsStubFile) { + const specialType = handleTypingStubTypeAnnotation(node); + if (specialType) { + return specialType; + } + } + + const adjustedOptions: ExpectedTypeOptions = options ? { ...options } : {}; + + adjustedOptions.typeExpression = true; + adjustedOptions.convertEllipsisToAny = true; + + // If the annotation is part of a comment, allow forward references + // even if it's not enclosed in quotes. + if (node?.parent?.nodeType === ParseNodeType.Assignment && node.parent.d.annotationComment === node) { + adjustedOptions.forwardRefs = true; + adjustedOptions.notParsed = true; + } else if (node?.parent?.nodeType === ParseNodeType.FunctionAnnotation) { + if (node.parent.d.returnAnnotation === node || node.parent.d.paramAnnotations.some((n) => n === node)) { + adjustedOptions.forwardRefs = true; + adjustedOptions.notParsed = true; + } + } else if (node?.parent?.nodeType === ParseNodeType.Parameter) { + if (node.parent.d.annotationComment === node) { + adjustedOptions.forwardRefs = true; + adjustedOptions.notParsed = true; + } + } + + const annotationType = getTypeOfExpressionExpectingType(node, adjustedOptions).type; + + if (isModule(annotationType)) { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.moduleAsType(), node); + } + + return convertToInstance(annotationType); + } + + function canBeFalsy(type: Type, recursionCount = 0): boolean { + type = makeTopLevelTypeVarsConcrete(type); + + if (recursionCount > maxTypeRecursionCount) { + return true; + } + recursionCount++; + + switch (type.category) { + case TypeCategory.Unbound: + case TypeCategory.Unknown: + case TypeCategory.Any: + case TypeCategory.Never: { + return true; + } + + case TypeCategory.Union: { + return findSubtype(type, (subtype) => canBeFalsy(subtype, recursionCount)) !== undefined; + } + + case TypeCategory.Function: + case TypeCategory.Overloaded: + case TypeCategory.Module: + case TypeCategory.TypeVar: { + return false; + } + + case TypeCategory.Class: { + if (TypeBase.isInstantiable(type)) { + return false; + } + + // Viper 扩展:CPtr 指针类型可为 NULL(falsy)。 + // 允许 if sp: / if ptr: 的假分支可达,避免 slab_page | CPtr 这类联合的 + // 假分支被误判为 Never(如 free() 中 sp 为 NULL 指针的路径)。 + if (isCPtrType(type)) { + return true; + } + + // Viper 扩展:CType 整数/浮点子类可为 falsy(值为 0)。 + // CInt/CUInt32T/CFloat 等在底层等价于 int/float,值 0 为 falsy。 + // 这些类型是独立 class(非 built-in int/float),不匹配上方 2128 的 + // isBuiltIn(type, ['int']) 检查,也不匹配末尾 2178 的 isBuiltIn(object), + // 导致 canBeFalsy 默认返回 false(认为总是 truthy)。 + // 这使得 in_corner_zone: t.CInt = 0 后 if not in_corner_zone: 的真分支 + // 被误判为不可达(CInt 被认为恒 truthy)。此处让整数/浮点 CType 子类 + // 返回 true(可为 0/falsy),使条件分支的真假分支均可达。 + if ( + isCTypeSubclassType(type) && + (isViperCIntegerTypeName(type.shared.name) || + isViperCFloatTypeName(type.shared.name)) + ) { + return true; + } + + // Sentinels are always truthy. + if (isSentinelLiteral(type)) { + return false; + } + + // Handle tuples specially. + if (isTupleClass(type) && type.priv.tupleTypeArgs) { + return isUnboundedTupleClass(type) || type.priv.tupleTypeArgs.length === 0; + } + + // Handle subclasses of tuple, such as NamedTuple. + const tupleBaseClass = type.shared.mro.find((mroClass) => !isClass(mroClass) || isTupleClass(mroClass)); + if (tupleBaseClass && isClass(tupleBaseClass) && tupleBaseClass.priv.tupleTypeArgs) { + return isUnboundedTupleClass(tupleBaseClass) || tupleBaseClass.priv.tupleTypeArgs.length === 0; + } + + // Handle TypedDicts specially. If one or more entries are required + // or known to exist, we can say for sure that the type is not falsy. + if (ClassType.isTypedDictClass(type)) { + const tdEntries = getTypedDictMembersForClass(evaluatorInterface, type, /* allowNarrowed */ true); + if (tdEntries) { + for (const tdEntry of tdEntries.knownItems.values()) { + if (tdEntry.isRequired || tdEntry.isProvided) { + return false; + } + } + } + } + + // Check for bool, int, str and bytes literals that are never falsy. + if (type.priv.literalValue !== undefined) { + if (ClassType.isBuiltIn(type, ['bool', 'int', 'str', 'bytes'])) { + return !type.priv.literalValue || type.priv.literalValue === BigInt(0); + } + + if (type.priv.literalValue instanceof EnumLiteral) { + // Does the Enum class forward the truthiness check to the + // underlying member type? + if (type.priv.literalValue.isReprEnum) { + return canBeFalsy(type.priv.literalValue.itemType, recursionCount); + } + } + } + + // If this is a protocol class, don't make any assumptions about the absence + // of specific methods. These could be provided by a class that conforms + // to the protocol. + if (ClassType.isProtocolClass(type)) { + return true; + } + + const lenMethod = lookUpObjectMember(type, '__len__'); + if (lenMethod) { + return true; + } + + const boolMethod = lookUpObjectMember(type, '__bool__'); + if (boolMethod) { + const boolMethodType = getTypeOfMember(boolMethod); + + // If the __bool__ function unconditionally returns True, it can never be falsy. + if (isFunction(boolMethodType) && boolMethodType.shared.declaredReturnType) { + const returnType = boolMethodType.shared.declaredReturnType; + if ( + isClassInstance(returnType) && + ClassType.isBuiltIn(returnType, 'bool') && + returnType.priv.literalValue === true + ) { + return false; + } + } + + return true; + } + + // If the class is not final, it's possible that it could be overridden + // such that it is falsy. To be fully correct, we'd need to do the + // following: + // return !ClassType.isFinal(type); + // However, pragmatically if the class is not an `object`, it's typically + // OK to assume that it will not be overridden in this manner. + return ClassType.isBuiltIn(type, 'object'); + } + } + } + + function canBeTruthy(type: Type, recursionCount = 0): boolean { + type = makeTopLevelTypeVarsConcrete(type); + + if (recursionCount > maxTypeRecursionCount) { + return true; + } + recursionCount++; + + switch (type.category) { + case TypeCategory.Unknown: + case TypeCategory.Function: + case TypeCategory.Overloaded: + case TypeCategory.Module: + case TypeCategory.TypeVar: + case TypeCategory.Never: + case TypeCategory.Any: { + return true; + } + + case TypeCategory.Union: { + return findSubtype(type, (subtype) => canBeTruthy(subtype, recursionCount)) !== undefined; + } + + case TypeCategory.Unbound: { + return false; + } + + case TypeCategory.Class: { + if (TypeBase.isInstantiable(type)) { + return true; + } + + if (isNoneInstance(type)) { + return false; + } + + // // Check for tuple[()] (an empty tuple). + if (type.priv.tupleTypeArgs && type.priv.tupleTypeArgs.length === 0) { + return false; + } + + // Check for bool, int, str and bytes literals that are never falsy. + if (type.priv.literalValue !== undefined) { + if (ClassType.isBuiltIn(type, ['bool', 'int', 'str', 'bytes'])) { + return !!type.priv.literalValue && type.priv.literalValue !== BigInt(0); + } + + if (type.priv.literalValue instanceof EnumLiteral) { + // Does the Enum class forward the truthiness check to the + // underlying member type? + if (type.priv.literalValue.isReprEnum) { + return canBeTruthy(type.priv.literalValue.itemType, recursionCount); + } + } + } + + // If this is a protocol class, don't make any assumptions about the absence + // of specific methods. These could be provided by a class that conforms + // to the protocol. + if (ClassType.isProtocolClass(type)) { + return true; + } + + const boolMethod = lookUpObjectMember(type, '__bool__'); + if (boolMethod) { + const boolMethodType = getTypeOfMember(boolMethod); + + // If the __bool__ function unconditionally returns False, it can never be truthy. + if (isFunction(boolMethodType) && boolMethodType.shared.declaredReturnType) { + const returnType = boolMethodType.shared.declaredReturnType; + if ( + isClassInstance(returnType) && + ClassType.isBuiltIn(returnType, 'bool') && + returnType.priv.literalValue === false + ) { + return false; + } + } + } + + return true; + } + } + } + + // Filters a type such that that no part of it is definitely + // truthy. For example, if a type is a union of None + // and a custom class "Foo" that has no __len__ or __nonzero__ + // method, this method would strip off the "Foo" + // and return only the "None". + function removeTruthinessFromType(type: Type): Type { + return mapSubtypes(type, (subtype) => { + const concreteSubtype = makeTopLevelTypeVarsConcrete(subtype); + + if (isClassInstance(concreteSubtype)) { + if (concreteSubtype.priv.literalValue !== undefined) { + let isLiteralFalsy: boolean; + + if (concreteSubtype.priv.literalValue instanceof EnumLiteral) { + isLiteralFalsy = !canBeTruthy(concreteSubtype); + } else { + isLiteralFalsy = !concreteSubtype.priv.literalValue; + } + + // If the object is already definitely falsy, it's fine to + // include, otherwise it should be removed. + return isLiteralFalsy ? subtype : undefined; + } + + // If the object is a sentinel, we can eliminate it. + if (isSentinelLiteral(concreteSubtype)) { + return undefined; + } + + // If the object is a bool, make it "false", since + // "true" is a truthy value. + if (ClassType.isBuiltIn(concreteSubtype, 'bool')) { + return ClassType.cloneWithLiteral(concreteSubtype, /* value */ false); + } + + // If the object is an int, str or bytes, narrow to a literal type. + // This is slightly unsafe in that someone could subclass `int`, `str` + // or `bytes` and override the `__bool__` method to change its behavior, + // but this is extremely unlikely (and ill advised). + if (ClassType.isBuiltIn(concreteSubtype, 'int')) { + return ClassType.cloneWithLiteral(concreteSubtype, /* value */ 0); + } else if (ClassType.isBuiltIn(concreteSubtype, ['str', 'bytes'])) { + return ClassType.cloneWithLiteral(concreteSubtype, /* value */ ''); + } + } + + // If it's possible for the type to be falsy, include it. + if (canBeFalsy(subtype)) { + return subtype; + } + + return undefined; + }); + } + + // Filters a type such that that no part of it is definitely + // falsy. For example, if a type is a union of None + // and an "int", this method would strip off the "None" + // and return only the "int". + function removeFalsinessFromType(type: Type): Type { + return mapSubtypes(type, (subtype) => { + const concreteSubtype = makeTopLevelTypeVarsConcrete(subtype); + + if (isClassInstance(concreteSubtype)) { + if (concreteSubtype.priv.literalValue !== undefined) { + let isLiteralTruthy: boolean; + + if (concreteSubtype.priv.literalValue instanceof EnumLiteral) { + isLiteralTruthy = !canBeFalsy(concreteSubtype); + } else if (concreteSubtype.priv.literalValue instanceof SentinelLiteral) { + isLiteralTruthy = true; + } else { + isLiteralTruthy = !!concreteSubtype.priv.literalValue; + } + + // If the object is already definitely truthy, it's fine to + // include, otherwise it should be removed. + return isLiteralTruthy ? subtype : undefined; + } + + // If the object is a bool, make it "true", since + // "false" is a falsy value. + if (ClassType.isBuiltIn(concreteSubtype, 'bool')) { + return ClassType.cloneWithLiteral(concreteSubtype, /* value */ true); + } + + // If the object is a "None" instance, we can eliminate it. + if (isNoneInstance(concreteSubtype)) { + return undefined; + } + + // If this is an instance of a class that cannot be subclassed, + // we cannot say definitively that it's not falsy because a subclass + // could override `__bool__`. For this reason, the code should not + // remove any classes that are not final. + // if (!ClassType.isFinal(concreteSubtype)) { + // return subtype; + // } + // However, we're going to pragmatically assume that any classes + // other than `object` will not be overridden in this manner. + if (ClassType.isBuiltIn(concreteSubtype, 'object')) { + return subtype; + } + } + + // If it's possible for the type to be truthy, include it. + if (canBeTruthy(subtype)) { + return subtype; + } + + return undefined; + }); + } + + // If a type contains a TypeGuard or TypeIs, convert it to a bool. + function stripTypeGuard(type: Type): Type { + return mapSubtypes(type, (subtype) => { + if (isClassInstance(subtype) && ClassType.isBuiltIn(subtype, ['TypeGuard', 'TypeIs'])) { + return prefetched?.boolClass ? convertToInstance(prefetched.boolClass) : UnknownType.create(); + } + + return subtype; + }); + } + + function solveAndApplyConstraints( + type: Type, + constraints: ConstraintTracker, + applyOptions?: ApplyTypeVarOptions, + solveOptions?: SolveConstraintsOptions + ): Type { + const solution = solveConstraints(evaluatorInterface, constraints, solveOptions); + return applySolvedTypeVars(type, solution, applyOptions); + } + + // Gets a member type from an object or class. If it's a function, binds + // it to the object or class. If selfType is undefined, the binding is done + // using the objectType parameter. Callers can specify these separately + // to handle the case where we're fetching the object member from a + // metaclass but binding to the class. + function getTypeOfBoundMember( + errorNode: ExpressionNode | undefined, + objectType: ClassType, + memberName: string, + usage: EvaluatorUsage = { method: 'get' }, + diag: DiagnosticAddendum | undefined = undefined, + flags = MemberAccessFlags.Default, + selfType?: ClassType | TypeVarType, + recursionCount = 0 + ): TypeResult | undefined { + if (ClassType.isPartiallyEvaluated(objectType)) { + if (errorNode) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.classDefinitionCycle().format({ name: objectType.shared.name }), + errorNode + ); + } + return { type: UnknownType.create() }; + } + + // If this is an unspecialized generic class, specialize it using the default + // values for its type parameters. + if ( + isInstantiableClass(objectType) && + !objectType.priv.includeSubclasses && + objectType.shared.typeParams.length > 0 + ) { + // Skip this if we're suppressing the use of attribute access override, + // such as with dundered methods (like __call__). + if ((flags & MemberAccessFlags.SkipAttributeAccessOverride) === 0) { + objectType = specializeWithDefaultTypeArgs(objectType); + } + } + + // Determine the class that was used to instantiate the objectType. + // If the objectType is a class itself, then the class used to instantiate + // it is the metaclass. + const objectTypeIsInstantiable = TypeBase.isInstantiable(objectType); + const metaclass = objectType.shared.effectiveMetaclass; + + let memberInfo: ClassMemberLookup | undefined; + + // If the object type is an instantiable (i.e. it derives from "type") and + // we've been asked not to consider instance members, don't look in the class. + // Consider only the metaclass class variables in this case. + let skipObjectTypeLookup = objectTypeIsInstantiable && (flags & MemberAccessFlags.SkipInstanceMembers) !== 0; + + // Look up the attribute in the metaclass first. If the member is a descriptor + // (an object with a __get__ and __set__ method) and the access is a 'get', + // the Python runtime uses this descriptor to satisfy the lookup. Skip this + // costly lookup in the common case where the metaclass is 'type' since we know + // that `type` doesn't have any attributes that are descriptors. + if ( + usage.method === 'get' && + objectTypeIsInstantiable && + metaclass && + isInstantiableClass(metaclass) && + !ClassType.isBuiltIn(metaclass, 'type') && + !ClassType.isSameGenericClass(metaclass, objectType) + ) { + const descMemberInfo = getTypeOfClassMemberName( + errorNode, + metaclass, + memberName, + usage, + /* diag */ undefined, + flags | MemberAccessFlags.SkipAttributeAccessOverride | MemberAccessFlags.SkipTypedDictEntries, + objectType, + recursionCount + ); + + if (descMemberInfo) { + const isProperty = + isClassInstance(descMemberInfo.type) && ClassType.isPropertyClass(descMemberInfo.type); + if (isDescriptorInstance(descMemberInfo.type, /* requireSetter */ true) || isProperty) { + skipObjectTypeLookup = true; + } + } + } + + let subDiag: DiagnosticAddendum | undefined; + + if (!skipObjectTypeLookup) { + let effectiveFlags = flags | MemberAccessFlags.SkipTypedDictEntries; + + if (objectTypeIsInstantiable) { + effectiveFlags |= + MemberAccessFlags.SkipInstanceMembers | + MemberAccessFlags.SkipAttributeAccessOverride | + MemberAccessFlags.DisallowGenericInstanceVariableAccess; + effectiveFlags &= ~MemberAccessFlags.SkipClassMembers; + } else { + effectiveFlags |= MemberAccessFlags.DisallowClassVarWrites; + } + + subDiag = diag ? new DiagnosticAddendum() : undefined; + + // See if the member is present in the object itself. + memberInfo = getTypeOfClassMemberName( + errorNode, + objectType, + memberName, + usage, + subDiag, + effectiveFlags, + selfType, + recursionCount + ); + } + + // If it wasn't found on the object, see if it's part of the metaclass. + if (!memberInfo && metaclass && isInstantiableClass(metaclass)) { + let effectiveFlags = flags; + + // Class members cannot be accessed on a class's metaclass through + // an instance of a class. Limit access to metaclass instance members + // in this case. + if (!objectTypeIsInstantiable) { + effectiveFlags |= + MemberAccessFlags.SkipClassMembers | + MemberAccessFlags.SkipAttributeAccessOverride | + MemberAccessFlags.SkipTypeBaseClass; + effectiveFlags &= ~MemberAccessFlags.SkipInstanceMembers; + } + + const metaclassDiag = diag ? new DiagnosticAddendum() : undefined; + memberInfo = getTypeOfClassMemberName( + errorNode, + ClassType.cloneAsInstance(metaclass), + memberName, + usage, + metaclassDiag, + effectiveFlags, + objectTypeIsInstantiable ? objectType : ClassType.cloneAsInstantiable(objectType), + recursionCount + ); + + // If there was a descriptor error (as opposed to an error where the members + // was simply not found), use this diagnostic message. + if (memberInfo?.isDescriptorError) { + subDiag = metaclassDiag; + } + } + + if (memberInfo) { + if (memberInfo.isDescriptorError && diag && subDiag) { + diag.addAddendum(subDiag); + } + + return { + type: memberInfo.type, + classType: memberInfo.classType, + isIncomplete: !!memberInfo.isTypeIncomplete, + isAsymmetricAccessor: memberInfo.isAsymmetricAccessor, + narrowedTypeForSet: memberInfo.narrowedTypeForSet, + memberAccessDeprecationInfo: memberInfo.memberAccessDeprecationInfo, + typeErrors: memberInfo.isDescriptorError, + }; + } + + // If this is a type[Any] or type[Unknown], allow any other members. + if ( + isClassInstance(objectType) && + ClassType.isBuiltIn(objectType, 'type') && + objectType.priv.includeSubclasses + ) { + if ((flags & (MemberAccessFlags.SkipTypeBaseClass | MemberAccessFlags.SkipAttributeAccessOverride)) === 0) { + const typeArg = + objectType.priv.typeArgs && objectType.priv.typeArgs.length >= 1 + ? objectType.priv.typeArgs[0] + : UnknownType.create(); + + if (isAnyOrUnknown(typeArg)) { + return { type: typeArg, classType: UnknownType.create() }; + } + } + } + + if (diag && subDiag) { + diag.addAddendum(subDiag); + } + + // Viper 扩展:`__before_init__` 是编译器自动生成的默认成员方法 + // (在 __init__ 之前自动填充成员默认值,见 TransPyC wiki/06-oop.md), + // 所有类都隐式存在,无需在源码中定义。因此当成员查找失败且目标 + // 成员名是 `__before_init__` 时,合成一个 `() -> None` 的方法, + // 避免报 "attribute is unknown"。同时该成员不允许手动定义,手动 + // 定义会在 checker 中报错。 + if ( + memberName === '__before_init__' && + isClass(objectType) && + errorNode && + IsViperDialect(AnalyzerNodeInfo.getFileInfo(errorNode).executionEnvironment.dialect) && + (flags & MemberAccessFlags.SkipAttributeAccessOverride) === 0 + ) { + // 合成 `() -> None` 方法。注意:成员访问 `obj.__before_init__()` + // 已绑定实例,因此签名不包含 self 参数(与 __getattr__ 等 + // 动态成员的绑定行为一致),避免调用时误报 "missing self"。 + const beforeInitType = FunctionType.createSynthesizedInstance('__before_init__'); + beforeInitType.shared.declaredReturnType = getNoneType(); + + return { + type: beforeInitType, + isIncomplete: false, + isAsymmetricAccessor: false, + }; + } + + return undefined; + } + + function getBoundMagicMethod( + classType: ClassType, + memberName: string, + selfType?: ClassType | TypeVarType | undefined, + errorNode?: ExpressionNode | undefined, + diag?: DiagnosticAddendum, + recursionCount = 0 + ): FunctionType | OverloadedType | undefined { + const boundMethodResult = getTypeOfBoundMember( + errorNode, + classType, + memberName, + /* usage */ undefined, + diag, + MemberAccessFlags.SkipInstanceMembers | MemberAccessFlags.SkipAttributeAccessOverride, + selfType, + recursionCount + ); + + if (!boundMethodResult || boundMethodResult.typeErrors) { + return undefined; + } + + if (isFunctionOrOverloaded(boundMethodResult.type)) { + return boundMethodResult.type; + } + + if (isClassInstance(boundMethodResult.type)) { + if (recursionCount > maxTypeRecursionCount) { + return undefined; + } + recursionCount++; + + return getBoundMagicMethod( + boundMethodResult.type, + '__call__', + /* selfType */ undefined, + errorNode, + diag, + recursionCount + ); + } + + if (isAnyOrUnknown(boundMethodResult.type)) { + return getUnknownTypeForCallable(); + } + + return undefined; + } + + // Returns the signature(s) associated with a call node that contains + // the specified node. It also returns the index of the argument + // that contains the node. + function getCallSignatureInfo( + callNode: CallNode, + activeIndex: number, + activeOrFake: boolean + ): CallSignatureInfo | undefined { + const exprNode = callNode.d.leftExpr; + const callType = getType(exprNode); + if (!callType) { + return undefined; + } + + const argList: Arg[] = []; + let previousCategory = ArgCategory.Simple; + + // Empty arguments do not enter the AST as nodes, but instead are left blank. + // Instead, we detect when we appear to be between two known arguments or at the + // end of the argument list and insert a fake argument of an unknown type to have + // something to match later. + function addFakeArg() { + argList.push({ + argCategory: previousCategory, + typeResult: { type: UnknownType.create() }, + active: true, + }); + } + + callNode.d.args.forEach((arg, index) => { + let active = false; + if (index === activeIndex) { + if (activeOrFake) { + active = true; + } else { + addFakeArg(); + } + } + + previousCategory = arg.d.argCategory; + + argList.push({ + valueExpression: arg.d.valueExpr, + argCategory: arg.d.argCategory, + name: arg.d.name, + active: active, + }); + }); + + if (callNode.d.args.length < activeIndex) { + addFakeArg(); + } + + let signatures: CallSignature[] = []; + + function addOneFunctionToSignature(type: FunctionType) { + let callResult: CallResult | undefined; + const constraints = new ConstraintTracker(); + + useSpeculativeMode(callNode, () => { + callResult = validateArgs( + exprNode, + argList, + { type }, + constraints, + /* skipUnknownArgCheck */ true, + /* inferenceContext */ undefined + ); + }); + + const specializedType = solveAndApplyConstraints(type, constraints); + const finalType = isFunction(specializedType) ? specializedType : type; + const hasActiveArg = argList.some((arg) => arg.active); + + // If the type was specialized (e.g. ParamSpec expansion), the activeParam + // from the original validateArgs refers to parameters in the unspecialized + // type and won't match the specialized type's parameters. Re-run validateArgs + // against the specialized type to get the correct activeParam mapping. + let activeParam = callResult?.activeParam; + if (hasActiveArg && finalType !== type) { + let specializedActiveParam: typeof activeParam | undefined; + useSpeculativeMode(callNode, () => { + const specializedResult = validateArgs( + exprNode, + argList, + { type: finalType }, + new ConstraintTracker(), + /* skipUnknownArgCheck */ true, + /* inferenceContext */ undefined + ); + specializedActiveParam = specializedResult?.activeParam; + }); + if (specializedActiveParam) { + activeParam = specializedActiveParam; + } + } + + signatures.push({ + type: expandTypedKwargs(finalType), + activeParam, + }); + } + + function addFunctionToSignature(type: FunctionType | OverloadedType) { + if (isFunction(type)) { + addOneFunctionToSignature(type); + } else { + OverloadedType.getOverloads(type).forEach((func) => { + addOneFunctionToSignature(func); + }); + } + } + + doForEachSubtype(callType, (subtype) => { + switch (subtype.category) { + case TypeCategory.Function: + case TypeCategory.Overloaded: { + addFunctionToSignature(subtype); + break; + } + + case TypeCategory.Class: { + if (TypeBase.isInstantiable(subtype)) { + const constructorType = createFunctionFromConstructor(evaluatorInterface, subtype); + + if (constructorType) { + doForEachSubtype(constructorType, (subtype) => { + if (isFunctionOrOverloaded(subtype)) { + addFunctionToSignature(subtype); + } + }); + + // It's common for either the `__new__` or `__init__` methods to be + // simple (*args: Any, **kwargs: Any) signatures. If so, we'll try + // to filter out these signatures if they add nothing of value. + const filteredSignatures = signatures.filter( + (sig) => + !FunctionType.isGradualCallableForm(sig.type) || + sig.type.shared.parameters.length > 2 || + sig.type.shared.docString || + sig.type.shared.deprecatedMessage + ); + + if (filteredSignatures.length > 0) { + signatures = filteredSignatures; + } + } + } else { + const methodType = getBoundMagicMethod(subtype, '__call__'); + if (methodType) { + addFunctionToSignature(methodType); + } + } + break; + } + } + }); + + if (signatures.length === 0) { + return undefined; + } + + return { callNode, signatures }; + } + + // If the function includes a `**kwargs: Unpack[TypedDict]` parameter, the + // parameter is expanded to include individual keyword args. + function expandTypedKwargs(functionType: FunctionType): FunctionType { + const kwargsIndex = functionType.shared.parameters.findIndex( + (param) => param.category === ParamCategory.KwargsDict + ); + if (kwargsIndex < 0) { + return functionType; + } + assert(kwargsIndex === functionType.shared.parameters.length - 1); + + const kwargsType = FunctionType.getParamType(functionType, kwargsIndex); + if (!isClassInstance(kwargsType) || !ClassType.isTypedDictClass(kwargsType) || !kwargsType.priv.isUnpacked) { + return functionType; + } + + const tdEntries = kwargsType.priv.typedDictNarrowedEntries ?? kwargsType.shared.typedDictEntries?.knownItems; + if (!tdEntries) { + return functionType; + } + + const newFunction = FunctionType.clone(functionType); + newFunction.shared.parameters.splice(kwargsIndex); + if (newFunction.priv.specializedTypes) { + newFunction.priv.specializedTypes.parameterTypes.splice(kwargsIndex); + } + + const kwSeparatorIndex = functionType.shared.parameters.findIndex( + (param) => param.category === ParamCategory.ArgsList + ); + + // Add a keyword separator if necessary. + if (kwSeparatorIndex < 0 && tdEntries.size > 0) { + FunctionType.addKeywordOnlyParamSeparator(newFunction); + } + + tdEntries.forEach((tdEntry, name) => { + // A TypedDict entry type may carry the TypedDict's own type parameters (e.g. an unpacked + // generic TypedDict `**kwargs: Unpack[TD[int]]`), so specialize each entry against the + // (possibly generic) TypedDict instance. Specializing an already-concrete type is a + // no-op, so this is safe for non-generic TypedDicts. + const specializedValueType = partiallySpecializeType( + tdEntry.valueType, + kwargsType, + /* typeClassType */ undefined + ); + + FunctionType.addParam( + newFunction, + FunctionParam.create( + ParamCategory.Simple, + specializedValueType, + FunctionParamFlags.TypeDeclared, + name, + tdEntry.isRequired ? undefined : specializedValueType + ) + ); + }); + + const extraItemsType = kwargsType.shared.typedDictEntries?.extraItems?.valueType; + + if (extraItemsType && !isNever(extraItemsType)) { + // Specialized for the same reason as the per-entry types above. This PEP 728 + // `extra_items` branch uses the identical mechanism and is intentionally not + // separately tested. + const specializedExtraItemsType = partiallySpecializeType( + extraItemsType, + kwargsType, + /* typeClassType */ undefined + ); + + FunctionType.addParam( + newFunction, + FunctionParam.create( + ParamCategory.KwargsDict, + specializedExtraItemsType, + FunctionParamFlags.TypeDeclared, + 'kwargs' + ) + ); + } + + return newFunction; + } + + // Determines whether the specified expression is an explicit TypeAlias declaration. + function isDeclaredTypeAlias(expression: ExpressionNode): boolean { + if (expression.nodeType === ParseNodeType.TypeAnnotation) { + if (expression.d.valueExpr.nodeType === ParseNodeType.Name) { + const symbolWithScope = lookUpSymbolRecursive( + expression, + expression.d.valueExpr.d.value, + /* honorCodeFlow */ false + ); + if (symbolWithScope) { + const symbol = symbolWithScope.symbol; + return symbol.getDeclarations().find((decl) => isExplicitTypeAliasDeclaration(decl)) !== undefined; + } + } + } + + return false; + } + + // Determines whether the specified expression is a symbol with a declared type. + function getDeclaredTypeForExpression(expression: ExpressionNode, usage?: EvaluatorUsage): Type | undefined { + let symbol: Symbol | undefined; + let selfType: ClassType | TypeVarType | undefined; + let classOrObjectBase: ClassType | undefined; + let memberAccessClass: Type | undefined; + let bindFunction = true; + let useDescriptorSetterType = false; + + switch (expression.nodeType) { + case ParseNodeType.Name: { + const symbolWithScope = lookUpSymbolRecursive(expression, expression.d.value, /* honorCodeFlow */ true); + if (symbolWithScope) { + symbol = symbolWithScope.symbol; + + // Handle the case where the symbol is a class-level variable + // where the type isn't declared in this class but is in + // a parent class. + if ( + !getDeclaredTypeOfSymbol(symbol, expression)?.type && + symbolWithScope.scope.type === ScopeType.Class + ) { + const enclosingClass = ParseTreeUtils.getEnclosingClassOrFunction(expression); + if (enclosingClass && enclosingClass.nodeType === ParseNodeType.Class) { + const classTypeInfo = getTypeOfClass(enclosingClass); + if (classTypeInfo) { + const classMemberInfo = lookUpClassMember( + classTypeInfo.classType, + expression.d.value, + MemberAccessFlags.SkipInstanceMembers | MemberAccessFlags.DeclaredTypesOnly + ); + if (classMemberInfo) { + symbol = classMemberInfo.symbol; + } + } + } + } + } + break; + } + + case ParseNodeType.TypeAnnotation: { + return getDeclaredTypeForExpression(expression.d.valueExpr, usage); + } + + case ParseNodeType.MemberAccess: { + const baseType = getTypeOfExpression(expression.d.leftExpr, EvalFlags.MemberAccessBaseDefaults).type; + const baseTypeConcrete = makeTopLevelTypeVarsConcrete(baseType); + const memberName = expression.d.member.d.value; + + // Normally, baseTypeConcrete will not be a composite type (a union), + // but this can occur. In this case, it's not clear how to handle this + // correctly. For now, we'll just loop through the subtypes and + // use one of them. We'll sort the subtypes for determinism. + doForEachSubtype( + baseTypeConcrete, + (baseSubtype) => { + if (isClassInstance(baseSubtype)) { + const classMemberInfo = lookUpObjectMember( + baseSubtype, + memberName, + MemberAccessFlags.DeclaredTypesOnly + ); + + classOrObjectBase = baseSubtype; + memberAccessClass = classMemberInfo?.classType; + symbol = classMemberInfo?.symbol; + useDescriptorSetterType = true; + + // If this is an instance member (e.g. a dataclass field), don't + // bind it to the object if it's a function. + bindFunction = !classMemberInfo?.isInstanceMember; + } else if (isInstantiableClass(baseSubtype)) { + const classMemberInfo = lookUpClassMember( + baseSubtype, + memberName, + MemberAccessFlags.SkipInstanceMembers | MemberAccessFlags.DeclaredTypesOnly + ); + + classOrObjectBase = baseSubtype; + memberAccessClass = classMemberInfo?.classType; + symbol = classMemberInfo?.symbol; + useDescriptorSetterType = false; + bindFunction = true; + } else if (isModule(baseSubtype)) { + classOrObjectBase = undefined; + memberAccessClass = undefined; + symbol = ModuleType.getField(baseSubtype, memberName); + if (symbol && !symbol.hasTypedDeclarations()) { + // Do not use inferred types for the declared type. + symbol = undefined; + } + useDescriptorSetterType = false; + bindFunction = false; + } + }, + /* sortSubtypes */ true + ); + + if (isTypeVar(baseType)) { + selfType = baseType; + } + break; + } + + case ParseNodeType.Index: { + const baseType = makeTopLevelTypeVarsConcrete( + getTypeOfExpression(expression.d.leftExpr, EvalFlags.IndexBaseDefaults).type + ); + + if (baseType && isClassInstance(baseType)) { + if (ClassType.isTypedDictClass(baseType)) { + const typeFromTypedDict = getTypeOfIndexedTypedDict( + evaluatorInterface, + expression, + baseType, + usage || { method: 'get' } + ); + if (typeFromTypedDict) { + return typeFromTypedDict.type; + } + } + + let setItemType = getBoundMagicMethod(baseType, '__setitem__'); + if (!setItemType) { + break; + } + + if (isOverloaded(setItemType)) { + // Determine whether we need to use the slice overload. + const expectsSlice = + expression.d.items.length === 1 && + expression.d.items[0].d.valueExpr.nodeType === ParseNodeType.Slice; + const overloads = OverloadedType.getOverloads(setItemType); + setItemType = overloads.find((overload) => { + if (overload.shared.parameters.length < 2) { + return false; + } + + const keyType = FunctionType.getParamType(overload, 0); + const isSlice = isClassInstance(keyType) && ClassType.isBuiltIn(keyType, 'slice'); + return expectsSlice === isSlice; + }); + + if (!setItemType) { + break; + } + } + + if (isFunction(setItemType) && setItemType.shared.parameters.length >= 2) { + const paramType = FunctionType.getParamType(setItemType, 1); + if (!isAnyOrUnknown(paramType)) { + return paramType; + } + } + } + break; + } + + case ParseNodeType.Tuple: { + // If this is a tuple expression with at least one item and no + // unpacked items, and all of the items have declared types, + // we can assume a declared type for the resulting tuple. This + // is needed to enable bidirectional type inference when assigning + // to an unpacked tuple. + if ( + expression.d.items.length > 0 && + !expression.d.items.some((item) => item.nodeType === ParseNodeType.Unpack) + ) { + const itemTypes: Type[] = []; + expression.d.items.forEach((expr) => { + const itemType = getDeclaredTypeForExpression(expr, usage); + if (itemType) { + itemTypes.push(itemType); + } + }); + + if (itemTypes.length === expression.d.items.length) { + // If all items have a declared type, return a tuple of those types. + return makeTupleObject( + evaluatorInterface, + itemTypes.map((t) => { + return { type: t, isUnbounded: false }; + }) + ); + } + } + break; + } + } + + if (symbol) { + let declaredType = getDeclaredTypeOfSymbol(symbol)?.type; + if (declaredType) { + // If it's a descriptor, we need to get the setter type. + if (useDescriptorSetterType && isClassInstance(declaredType)) { + const setter = getBoundMagicMethod(declaredType, '__set__'); + if (setter && isFunction(setter) && setter.shared.parameters.length >= 2) { + declaredType = FunctionType.getParamType(setter, 1); + + if (isAnyOrUnknown(declaredType)) { + return undefined; + } + } + } + + if (classOrObjectBase) { + if (memberAccessClass && isInstantiableClass(memberAccessClass)) { + declaredType = partiallySpecializeType( + declaredType, + memberAccessClass, + getTypeClassType(), + selfType + ); + } + + if (isFunctionOrOverloaded(declaredType)) { + if (bindFunction) { + declaredType = bindFunctionToClassOrObject( + classOrObjectBase, + declaredType, + /* memberClass */ undefined, + /* treatConstructorAsClassMethod */ undefined, + selfType + ); + } + } + } + + return declaredType; + } + } + + return undefined; + } + + // Applies an "await" operation to the specified type and returns + // the result. According to PEP 492, await operates on an Awaitable + // (object that provides an __await__ that returns a generator object). + // If errorNode is undefined, no errors are reported. + function getTypeOfAwaitable(typeResult: TypeResult, errorNode?: ExpressionNode): TypeResult { + if ( + !prefetched?.awaitableClass || + !isInstantiableClass(prefetched.awaitableClass) || + prefetched.awaitableClass.shared.typeParams.length !== 1 + ) { + return { type: UnknownType.create(), isIncomplete: typeResult.isIncomplete }; + } + + const awaitableProtocolObj = ClassType.cloneAsInstance(prefetched.awaitableClass); + const isIncomplete = !!typeResult.isIncomplete; + + const type = mapSubtypes(typeResult.type, (subtype) => { + subtype = makeTopLevelTypeVarsConcrete(subtype); + + if (isAnyOrUnknown(subtype)) { + return subtype; + } + + const diag = errorNode ? new DiagnosticAddendum() : undefined; + + if (isClassInstance(subtype)) { + const constraints = new ConstraintTracker(); + + if (assignType(awaitableProtocolObj, subtype, diag, constraints)) { + const specializedType = solveAndApplyConstraints(awaitableProtocolObj, constraints); + + if ( + isClass(specializedType) && + specializedType.priv.typeArgs && + specializedType.priv.typeArgs.length > 0 + ) { + return specializedType.priv.typeArgs[0]; + } + + return UnknownType.create(); + } + } + + if (errorNode && !typeResult.isIncomplete) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeNotAwaitable().format({ type: printType(subtype) }) + diag?.getString(), + errorNode + ); + } + + return UnknownType.create(); + }); + + return { type, isIncomplete }; + } + + // Validates that the type is an iterator and returns the iterated type + // (i.e. the type returned from the '__next__' or '__anext__' method). + function getTypeOfIterator( + typeResult: TypeResult, + isAsync: boolean, + errorNode: ExpressionNode, + emitNotIterableError = true + ): TypeResult | undefined { + const iterMethodName = isAsync ? '__aiter__' : '__iter__'; + const nextMethodName = isAsync ? '__anext__' : '__next__'; + let isValidIterator = true; + let isIncomplete = typeResult.isIncomplete; + + let type = transformPossibleRecursiveTypeAlias(typeResult.type); + type = makeTopLevelTypeVarsConcrete(type); + type = removeUnbound(type); + + if (isOptionalType(type) && emitNotIterableError) { + if (!typeResult.isIncomplete) { + addDiagnostic(DiagnosticRule.reportOptionalIterable, LocMessage.noneNotIterable(), errorNode); + } + type = removeNoneFromUnion(type); + } + + const iterableType = mapSubtypes(type, (subtype) => { + subtype = makeTopLevelTypeVarsConcrete(subtype); + + if (isAnyOrUnknown(subtype)) { + return subtype; + } + + const diag = new DiagnosticAddendum(); + if (isClass(subtype)) { + // Viper 扩展:CPtr 类型支持迭代(指针遍历)。 + // C 语言中指针可以通过递增来遍历内存,CPtr[T] 迭代返回 T 类型。 + // 包含 CPtr 的联合类型中,修饰类型(CConst, CStatic 等)和 + // 整数类型(CChar = int)也跳过迭代检查,因为它们是类型组合的一部分 + // (如 CConst | CChar | CPtr 表示 const char*)。 + const isViperDialectIter = IsViperDialect(AnalyzerNodeInfo.getFileInfo(errorNode).executionEnvironment.dialect); + if (isViperDialectIter && TypeBase.isInstance(subtype)) { + if (isCPtrType(subtype)) { + const typeArgs = subtype.priv.typeArgs; + if (typeArgs && typeArgs.length > 0) { + return typeArgs[0]; + } + return UnknownType.create(isIncomplete); + } + // Viper 扩展:str/bytes 类型迭代返回 t.CChar(字符)。 + // Viper 语义:str/bytes = i8* 指针(const char*),迭代返回单个字符 CChar。 + // 使用 getViperCCharType 而非裸 int,保持与 s[i] 下标访问一致(t.CChar 显示)。 + if (ClassType.isBuiltIn(subtype, ['str', 'bytes'])) { + const ccharType = getViperCCharType(errorNode); + if (ccharType) { + return ccharType; + } + return getBuiltInObject(errorNode, 'int'); + } + // 检查联合类型中是否包含 CPtr + const typeContainsCPtr = findSubtype(type, (s) => + isClassInstance(s) && isCPtrType(s)) !== undefined; + if (typeContainsCPtr && ( + isCConstType(subtype) || isCStaticType(subtype) || isCVoidType(subtype) || + ClassType.isBuiltIn(subtype, 'int') + )) { + return UnknownType.create(isIncomplete); + } + } + + // Handle an empty tuple specially. + if ( + TypeBase.isInstance(subtype) && + isTupleClass(subtype) && + subtype.priv.tupleTypeArgs && + subtype.priv.tupleTypeArgs.length === 0 + ) { + return NeverType.createNever(); + } + + const iterReturnType = getTypeOfMagicMethodCall(subtype, iterMethodName, [], errorNode)?.type; + + if (!iterReturnType) { + // There was no __iter__. See if we can fall back to + // the __getitem__ method instead. + if (!isAsync && isClassInstance(subtype)) { + const getItemReturnType = getTypeOfMagicMethodCall( + subtype, + '__getitem__', + [ + { + type: + prefetched?.intClass && isInstantiableClass(prefetched.intClass) + ? ClassType.cloneAsInstance(prefetched.intClass) + : UnknownType.create(), + }, + ], + errorNode + )?.type; + if (getItemReturnType) { + return getItemReturnType; + } + } + + diag.addMessage(LocMessage.methodNotDefined().format({ name: iterMethodName })); + } else { + const iterReturnTypeDiag = new DiagnosticAddendum(); + + const returnType = mapSubtypesExpandTypeVars(iterReturnType, /* options */ undefined, (subtype) => { + if (isAnyOrUnknown(subtype)) { + return subtype; + } + + let nextReturnType = getTypeOfMagicMethodCall(subtype, nextMethodName, [], errorNode)?.type; + + if (!nextReturnType) { + iterReturnTypeDiag.addMessage( + LocMessage.methodNotDefinedOnType().format({ + name: nextMethodName, + type: printType(subtype), + }) + ); + } else { + // Convert any unpacked TypeVarTuples into object instances. We don't + // know anything more about them. + nextReturnType = mapSubtypes(nextReturnType, (returnSubtype) => { + if (isTypeVar(returnSubtype) && isUnpackedTypeVarTuple(returnSubtype)) { + return getObjectType(); + } + + return returnSubtype; + }); + + if (!isAsync) { + return nextReturnType; + } + + // If it's an async iteration, there's an implicit + // 'await' operator applied. + const awaitableResult = getTypeOfAwaitable( + { type: nextReturnType, isIncomplete: typeResult.isIncomplete }, + errorNode + ); + if (awaitableResult.isIncomplete) { + isIncomplete = true; + } + return awaitableResult.type; + } + + return undefined; + }); + + if (iterReturnTypeDiag.isEmpty()) { + return returnType; + } + + diag.addAddendum(iterReturnTypeDiag); + } + } + + if (!isIncomplete && emitNotIterableError) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeNotIterable().format({ type: printType(subtype) }) + diag.getString(), + errorNode + ); + } + + isValidIterator = false; + return undefined; + }); + + return isValidIterator ? { type: iterableType, isIncomplete } : undefined; + } + + // Validates that the type is an iterable and returns the iterable type argument. + function getTypeOfIterable( + typeResult: TypeResult, + isAsync: boolean, + errorNode: ExpressionNode, + emitNotIterableError = true + ): TypeResult | undefined { + const iterMethodName = isAsync ? '__aiter__' : '__iter__'; + let isValidIterable = true; + + let type = makeTopLevelTypeVarsConcrete(typeResult.type); + + if (isOptionalType(type)) { + if (!typeResult.isIncomplete && emitNotIterableError) { + addDiagnostic(DiagnosticRule.reportOptionalIterable, LocMessage.noneNotIterable(), errorNode); + } + type = removeNoneFromUnion(type); + } + + const iterableType = mapSubtypes(type, (subtype) => { + if (isAnyOrUnknown(subtype)) { + return subtype; + } + + if (isClass(subtype)) { + // Viper 扩展:CPtr 类型支持迭代(指针遍历)。 + // 与 getTypeOfIterator 中的逻辑保持一致。 + const isViperDialectIter = IsViperDialect(AnalyzerNodeInfo.getFileInfo(errorNode).executionEnvironment.dialect); + if (isViperDialectIter && TypeBase.isInstance(subtype)) { + if (isCPtrType(subtype)) { + const typeArgs = subtype.priv.typeArgs; + if (typeArgs && typeArgs.length > 0) { + return makeTopLevelTypeVarsConcrete(typeArgs[0]); + } + return UnknownType.create(); + } + const typeContainsCPtr = findSubtype(type, (s) => + isClassInstance(s) && isCPtrType(s)) !== undefined; + if (typeContainsCPtr && ( + isCConstType(subtype) || isCStaticType(subtype) || isCVoidType(subtype) || + ClassType.isBuiltIn(subtype, 'int') + )) { + return UnknownType.create(); + } + } + + const iterReturnType = getTypeOfMagicMethodCall(subtype, iterMethodName, [], errorNode)?.type; + + if (iterReturnType) { + return makeTopLevelTypeVarsConcrete(iterReturnType); + } + } + + if (emitNotIterableError) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeNotIterable().format({ type: printType(subtype) }), + errorNode + ); + } + + isValidIterable = false; + return undefined; + }); + + return isValidIterable ? { type: iterableType, isIncomplete: typeResult.isIncomplete } : undefined; + } + + function isTypeHashable(type: Type): boolean { + let isTypeHashable = true; + + doForEachSubtype(makeTopLevelTypeVarsConcrete(type), (subtype) => { + if (isClassInstance(subtype)) { + // Assume the class is hashable. + let isObjectHashable = true; + + // Have we already computed and cached the hashability? + if (subtype.shared.isInstanceHashable !== undefined) { + isObjectHashable = subtype.shared.isInstanceHashable; + } else { + const hashMember = lookUpObjectMember(subtype, '__hash__', MemberAccessFlags.SkipObjectBaseClass); + + if (hashMember && hashMember.isTypeDeclared) { + const decls = hashMember.symbol.getTypedDeclarations(); + const synthesizedType = hashMember.symbol.getSynthesizedType(); + + // Handle the case where the type is synthesized (used for + // dataclasses). + if (synthesizedType) { + isObjectHashable = !isNoneInstance(synthesizedType.type); + } else { + // Assume that if '__hash__' is declared as a variable, it is + // not hashable. If it's declared as a function, it is. We'll + // skip evaluating its full type because that's not needed in + // this case. + if (decls.every((decl) => decl.type === DeclarationType.Variable)) { + isObjectHashable = false; + } + } + } + + // Cache the hashability for next time. + subtype.shared.isInstanceHashable = isObjectHashable; + } + + if (!isObjectHashable) { + isTypeHashable = false; + } + } + }); + + return isTypeHashable; + } + + function getTypedDictClassType(): ClassType | undefined { + return prefetched?.typedDictPrivateClass && isInstantiableClass(prefetched.typedDictPrivateClass) + ? prefetched.typedDictPrivateClass + : undefined; + } + + function getTupleClassType(): ClassType | undefined { + return prefetched?.tupleClass && isInstantiableClass(prefetched.tupleClass) ? prefetched.tupleClass : undefined; + } + + function getDictClassType(): ClassType | undefined { + return prefetched?.dictClass && isInstantiableClass(prefetched.dictClass) ? prefetched.dictClass : undefined; + } + + function getStrClassType(): ClassType | undefined { + return prefetched?.strClass && isInstantiableClass(prefetched.strClass) ? prefetched.strClass : undefined; + } + + function getObjectType(): Type { + return prefetched?.objectClass ? convertToInstance(prefetched.objectClass) : UnknownType.create(); + } + + function getNoneType(): Type { + return prefetched?.noneTypeClass ? convertToInstance(prefetched.noneTypeClass) : UnknownType.create(); + } + + function getUnionClassType(): Type { + return prefetched?.unionTypeClass ?? UnknownType.create(); + } + + function getTypeClassType(): ClassType | undefined { + if (prefetched?.typeClass && isInstantiableClass(prefetched.typeClass)) { + return prefetched.typeClass; + } + return undefined; + } + + function getTypingType(node: ParseNode, symbolName: string): Type | undefined { + return ( + getTypeOfModule(node, symbolName, ['typing']) ?? getTypeOfModule(node, symbolName, ['typing_extensions']) + ); + } + + function getTypeCheckerInternalsType(node: ParseNode, symbolName: string): Type | undefined { + return getTypeOfModule(node, symbolName, ['_typeshed', '_type_checker_internals']); + } + + function getTypesType(node: ParseNode, symbolName: string): Type | undefined { + return getTypeOfModule(node, symbolName, ['types']); + } + + function getTypeshedType(node: ParseNode, symbolName: string): Type | undefined { + return getTypeOfModule(node, symbolName, ['_typeshed']); + } + + function getTypeOfModule(node: ParseNode, symbolName: string, nameParts: string[]) { + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + const lookupResult = importLookup({ nameParts, importingFileUri: fileInfo.fileUri }); + + if (!lookupResult) { + return undefined; + } + + const symbol = lookupResult.symbolTable.get(symbolName); + if (!symbol) { + return undefined; + } + + return getEffectiveTypeOfSymbol(symbol); + } + + function checkCodeFlowTooComplex(node: ParseNode): boolean { + const scopeNode = node.nodeType === ParseNodeType.Function ? node : ParseTreeUtils.getExecutionScopeNode(node); + const codeComplexity = AnalyzerNodeInfo.getCodeFlowComplexity(scopeNode); + + if (codeComplexity > maxCodeComplexity) { + let errorRange: TextRange = scopeNode; + if (scopeNode.nodeType === ParseNodeType.Function) { + errorRange = scopeNode.d.name; + } else if (scopeNode.nodeType === ParseNodeType.Module) { + errorRange = { start: 0, length: 0 }; + } + + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + addDiagnosticForTextRange( + fileInfo, + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.codeTooComplexToAnalyze(), + errorRange + ); + + return true; + } + + return false; + } + + function isNodeReachable(node: ParseNode, sourceNode?: ParseNode): boolean { + return getNodeReachability(node, sourceNode) === Reachability.Reachable; + } + + function isAfterNodeReachable(node: ParseNode): boolean { + return getAfterNodeReachability(node) === Reachability.Reachable; + } + + function getNodeReachability(node: ParseNode, sourceNode?: ParseNode): Reachability { + if (checkCodeFlowTooComplex(node)) { + return Reachability.Reachable; + } + + const flowNode = AnalyzerNodeInfo.getFlowNode(node); + if (!flowNode) { + if (node.parent) { + return getNodeReachability(node.parent, sourceNode); + } + return Reachability.UnreachableStructural; + } + + const sourceFlowNode = sourceNode ? AnalyzerNodeInfo.getFlowNode(sourceNode) : undefined; + + return codeFlowEngine.getFlowNodeReachability(flowNode, sourceFlowNode); + } + + function getAfterNodeReachability(node: ParseNode): Reachability { + const returnFlowNode = AnalyzerNodeInfo.getAfterFlowNode(node); + if (!returnFlowNode) { + return Reachability.UnreachableStructural; + } + + if (checkCodeFlowTooComplex(node)) { + return Reachability.Reachable; + } + + const reachability = codeFlowEngine.getFlowNodeReachability(returnFlowNode); + if (reachability !== Reachability.Reachable) { + return reachability; + } + + const executionScopeNode = ParseTreeUtils.getExecutionScopeNode(node); + if (!isFlowNodeReachableUsingNeverNarrowing(executionScopeNode, returnFlowNode)) { + return Reachability.UnreachableByAnalysis; + } + + return Reachability.Reachable; + } + + // Although isFlowNodeReachable indicates that the node is reachable, it + // may not be reachable if we apply "never narrowing". + function isFlowNodeReachableUsingNeverNarrowing(node: ExecutionScopeNode, flowNode: FlowNode) { + const analyzer = getCodeFlowAnalyzerForNode(node, /* typeAtStart */ undefined); + + if (checkCodeFlowTooComplex(node)) { + return true; + } + + const codeFlowResult = analyzer.getTypeFromCodeFlow(flowNode, /* reference */ undefined, { + typeAtStart: { type: UnboundType.create() }, + }); + + return codeFlowResult.type !== undefined && !isNever(codeFlowResult.type); + } + + // Determines whether there is a code flow path from sourceNode to sinkNode. + function isFlowPathBetweenNodes(sourceNode: ParseNode, sinkNode: ParseNode, allowSelf = true) { + if (checkCodeFlowTooComplex(sourceNode)) { + return true; + } + + const sourceFlowNode = AnalyzerNodeInfo.getFlowNode(sourceNode); + const sinkFlowNode = AnalyzerNodeInfo.getFlowNode(sinkNode); + if (!sourceFlowNode || !sinkFlowNode) { + return false; + } + if (sourceFlowNode === sinkFlowNode) { + return allowSelf; + } + + return ( + codeFlowEngine.getFlowNodeReachability(sinkFlowNode, sourceFlowNode, /* ignoreNoReturn */ true) === + Reachability.Reachable + ); + } + + function addInformation(message: string, node: ParseNode, range?: TextRange) { + return addDiagnosticWithSuppressionCheck('information', message, node, range); + } + + function addUnreachableCode(node: ParseNode, reachability: Reachability, textRange: TextRange) { + if (reachability === Reachability.Reachable) { + return; + } + + if (!isDiagnosticSuppressedForNode(node)) { + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + const reportTypeReachability = fileInfo.diagnosticRuleSet.enableReachabilityAnalysis; + + if ( + reachability === Reachability.UnreachableStructural || + reachability === Reachability.UnreachableStaticCondition || + reportTypeReachability + ) { + fileInfo.diagnosticSink.addUnreachableCodeWithTextRange( + reachability === Reachability.UnreachableStructural + ? LocMessage.unreachableCodeStructure() + : reachability === Reachability.UnreachableStaticCondition + ? LocMessage.unreachableCodeCondition() + : LocMessage.unreachableCodeType(), + textRange + ); + } + } + } + + function addDeprecated(message: string, node: ParseNode) { + if (!isDiagnosticSuppressedForNode(node)) { + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + fileInfo.diagnosticSink.addDeprecatedWithTextRange(message, node); + } + } + + function addDiagnosticWithSuppressionCheck( + diagLevel: DiagnosticLevel, + message: string, + node: ParseNode, + range?: TextRange + ) { + if (isDiagnosticSuppressedForNode(node)) { + // See if this node is suppressed but the diagnostic should be generated + // anyway so it can be used by the caller that requested the suppression. + const suppressionEntry = suppressedNodeStack.find( + (suppressedNode) => + ParseTreeUtils.isNodeContainedWithin(node, suppressedNode.node) && suppressedNode.suppressedDiags + ); + suppressionEntry?.suppressedDiags?.push(message); + + return undefined; + } + + if (isNodeReachable(node)) { + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + return fileInfo.diagnosticSink.addDiagnosticWithTextRange(diagLevel, message, range ?? node); + } + + return undefined; + } + + function isDiagnosticSuppressedForNode(node: ParseNode) { + if (speculativeTypeTracker.isSpeculative(node, /* ignoreIfDiagnosticsAllowed */ true)) { + return true; + } + + return suppressedNodeStack.some((suppressedNode) => + ParseTreeUtils.isNodeContainedWithin(node, suppressedNode.node) + ); + } + + // This function is similar to isDiagnosticSuppressedForNode except that it + // returns false if diagnostics are suppressed for the node but the caller + // has requested that diagnostics be generated anyway. + function canSkipDiagnosticForNode(node: ParseNode) { + if (speculativeTypeTracker.isSpeculative(node, /* ignoreIfDiagnosticsAllowed */ true)) { + return true; + } + + const suppressedEntries = suppressedNodeStack.filter((suppressedNode) => + ParseTreeUtils.isNodeContainedWithin(node, suppressedNode.node) + ); + + if (suppressedEntries.length === 0) { + return false; + } + + return suppressedEntries.every((entry) => !entry.suppressedDiags); + } + + function addDiagnostic(rule: DiagnosticRule, message: string, node: ParseNode, range?: TextRange) { + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + const diagLevel = fileInfo.diagnosticRuleSet[rule] as DiagnosticLevel; + + if (diagLevel === 'none') { + return undefined; + } + + const containingFunction = ParseTreeUtils.getEnclosingFunction(node); + + if (containingFunction) { + // Should we suppress this diagnostic because it's within an unannotated function? + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + if (!fileInfo.diagnosticRuleSet.analyzeUnannotatedFunctions) { + // Is the target node within the body of the function? If so, suppress the diagnostic. + if ( + ParseTreeUtils.isUnannotatedFunction(containingFunction) && + ParseTreeUtils.isNodeContainedWithin(node, containingFunction.d.suite) + ) { + return undefined; + } + } + + // Should we suppress this diagnostic because it's within a no_type_check function? + const containingClassNode = ParseTreeUtils.getEnclosingClass(containingFunction, /* stopAtFunction */ true); + const functionInfo = getFunctionInfoFromDecorators( + evaluatorInterface, + containingFunction, + !!containingClassNode + ); + + if ((functionInfo.flags & FunctionTypeFlags.NoTypeCheck) !== 0) { + return undefined; + } + } + + const diagnostic = addDiagnosticWithSuppressionCheck(diagLevel, message, node, range); + if (diagnostic) { + diagnostic.setRule(rule); + } + + return diagnostic; + } + + function addDiagnosticForTextRange( + fileInfo: AnalyzerFileInfo, + rule: DiagnosticRule, + message: string, + range: TextRange + ) { + const diagLevel = fileInfo.diagnosticRuleSet[rule] as DiagnosticLevel; + + if (diagLevel === 'none') { + return undefined; + } + + const diagnostic = fileInfo.diagnosticSink.addDiagnosticWithTextRange(diagLevel, message, range); + if (rule) { + diagnostic.setRule(rule); + } + + return diagnostic; + } + + function assignTypeToNameNode( + nameNode: NameNode, + typeResult: TypeResult, + ignoreEmptyContainers: boolean, + srcExpression?: ParseNode, + allowAssignmentToFinalVar = false, + expectedTypeDiagAddendum?: DiagnosticAddendum + ) { + const nameValue = nameNode.d.value; + + const symbolWithScope = lookUpSymbolRecursive(nameNode, nameValue, /* honorCodeFlow */ false); + if (!symbolWithScope) { + // This can happen when we are evaluating a piece of code that was + // determined to be unreachable by the binder. + return; + } + + const declarations = symbolWithScope.symbol.getDeclarations(); + let declaredType = getDeclaredTypeOfSymbol(symbolWithScope.symbol)?.type; + const fileInfo = AnalyzerNodeInfo.getFileInfo(nameNode); + + // Viper 扩展:禁止函数内部隐式写入全局变量(无 global 声明且无局部声明)。 + // 规则: + // 1. 全局变量存在 + 局部再次声明赋值(无 global)→ 不报错(局部优先) + // 2. 全局变量存在 + 局部无声明 + 有 global + 赋值 → 不报错 + // 3. 全局变量存在 + 局部无声明 + 无 global + 仅读取 → 不报错(读取处不检查) + // 4. 全局变量存在 + 局部无声明 + 无 global + 赋值 → 报错 + // 判断关键:symbolWithScope.scope.type === Module 说明局部没有声明, + // 查找到的是全局符号;此时若无 global 声明且在函数内赋值,则报错。 + // 跳过非变量类型(模块导入、函数、类、类型别名)和 CDefine 常量。 + if (IsViperDialect(fileInfo.executionEnvironment.dialect)) { + const containingFunction = ParseTreeUtils.getEnclosingFunction(nameNode); + if ( + containingFunction && + !symbolWithScope.usesGlobalBinding && + !symbolWithScope.usesNonlocalBinding + ) { + // 查找模块作用域中的同名全局变量。 + // 情况 1:符号直接在 Module 作用域(无局部声明)→ 直接使用。 + // 情况 2:符号在 Function 作用域(绑定器因赋值创建局部符号), + // 但无类型注解声明(不是合法局部变量)→ 查找模块作用域全局。 + let globalSymbol: typeof symbolWithScope.symbol | undefined; + if (symbolWithScope.scope.type === ScopeType.Module) { + globalSymbol = symbolWithScope.symbol; + } else if (!symbolWithScope.symbol.hasTypedDeclarations()) { + const globalScopeResult = symbolWithScope.scope.getGlobalScope(); + if (globalScopeResult) { + globalSymbol = globalScopeResult.scope.lookUpSymbol(nameValue); + } + } + + if (globalSymbol) { + // 只对变量声明(Variable)报错,跳过函数、类、模块导入、类型别名 + const globalDecls = globalSymbol.getDeclarations(); + const isGlobalVariable = globalDecls.some( + (decl) => decl.type === DeclarationType.Variable + ); + if (isGlobalVariable) { + // 跳过 CDefine 常量(编译期宏定义,可在任意作用域使用) + const globalEffectiveType = getEffectiveTypeOfSymbol(globalSymbol); + if (!isCDefineType(globalEffectiveType)) { + addDiagnostic( + DiagnosticRule.reportUndefinedVariable, + LocMessage.viperGlobalAssignmentWithoutGlobal().format({ + name: nameValue, + }), + nameNode + ); + } + } + } + } + } + + // If this is a class scope and there is no type declared for this class variable, + // see if a parent class has a type declared. + if (declaredType === undefined && symbolWithScope.scope.type === ScopeType.Class) { + const containingClass = ParseTreeUtils.getEnclosingClass(nameNode); + if (containingClass) { + const classType = getTypeOfClass(containingClass); + if (classType) { + const memberInfo = lookUpClassMember( + classType.classType, + nameNode.d.value, + MemberAccessFlags.SkipOriginalClass + ); + if (memberInfo?.isTypeDeclared) { + declaredType = getTypeOfMember(memberInfo); + } + } + } + } + + // We found an existing declared type. Make sure the type is assignable. + let destType = typeResult.type; + const isTypeAlias = + !!declaredType && + isClassInstance(declaredType) && + (ClassType.isBuiltIn(declaredType, 'TypeAlias') || + // Viper 扩展:t.CTypedef 是 TypeAlias 的别名(CTypedef: TypeAlias = TypeAlias), + // 但来自 vipershed-fallback 没有 BuiltIn 标志,需要额外检查类型名称。 + (IsViperDialect(fileInfo.executionEnvironment.dialect) && + (declaredType.shared.name === 'CTypedef' || + declaredType.shared.fullName === 't.CTypedef'))); + + if (declaredType && !isTypeAlias) { + let diagAddendum = new DiagnosticAddendum(); + + const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(nameNode); + const boundDeclaredType = makeTypeVarsBound(declaredType, liveScopeIds); + const srcType = makeTypeVarsBound(typeResult.type, liveScopeIds); + + if (!assignType(boundDeclaredType, srcType, diagAddendum)) { + // If there was an expected type mismatch, use that diagnostic + // addendum because it will be more informative. + if (expectedTypeDiagAddendum) { + diagAddendum = expectedTypeDiagAddendum; + } + + if (!typeResult.isIncomplete) { + addDiagnostic( + DiagnosticRule.reportAssignmentType, + LocMessage.typeAssignmentMismatch().format(printSrcDestTypes(typeResult.type, declaredType)) + + diagAddendum.getString(), + srcExpression ?? nameNode, + diagAddendum.getEffectiveTextRange() ?? srcExpression ?? nameNode + ); + } + + // Replace the assigned type with the (unnarrowed) declared type. + destType = declaredType; + } else { + // Constrain the resulting type to match the declared type. + destType = narrowTypeBasedOnAssignment(declaredType, typeResult).type; + } + } else { + // If this is a member name (within a class scope) and the member name + // appears to be a constant, use the strict source type. If it's a member + // variable that can be overridden by a child class, use the more general + // version by stripping off the literal and TypeForm. + const scope = ScopeUtils.getScopeForNode(nameNode); + if (scope?.type === ScopeType.Class) { + if ( + TypeBase.isInstance(destType) && + !isConstantName(nameValue) && + !isFinalVariable(symbolWithScope.symbol) + ) { + destType = stripTypeForm(stripLiteralValue(destType)); + } + } + } + + const varDeclIndex = declarations.findIndex((decl) => decl.type === DeclarationType.Variable); + const varDecl = varDeclIndex >= 0 ? declarations[varDeclIndex] : undefined; + + // Are there any non-var decls before the var decl? + const nonVarDecl = declarations.find( + (decl, index) => varDeclIndex < index && decl.type !== DeclarationType.Variable + ); + + if (varDecl && varDecl.type === DeclarationType.Variable) { + if (varDecl.isConstant) { + // A constant variable can be assigned only once. If this + // isn't the first assignment, generate an error. + if (nameNode !== getNameNodeForDeclaration(declarations[0]) || !!nonVarDecl) { + addDiagnostic( + DiagnosticRule.reportConstantRedefinition, + LocMessage.constantRedefinition().format({ name: nameValue }), + nameNode + ); + } + } else if (isFinalVariableDeclaration(varDecl) && !allowAssignmentToFinalVar) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.finalReassigned().format({ name: nameValue }), + nameNode + ); + } + } + + if (!typeResult.isIncomplete) { + reportPossibleUnknownAssignment( + fileInfo.diagnosticRuleSet.reportUnknownVariableType, + DiagnosticRule.reportUnknownVariableType, + nameNode, + typeResult.type, + nameNode, + ignoreEmptyContainers + ); + } + + writeTypeCache(nameNode, { type: destType, isIncomplete: typeResult.isIncomplete }, EvalFlags.None); + } + + function assignTypeToMemberAccessNode( + target: MemberAccessNode, + typeResult: TypeResult, + srcExpr?: ExpressionNode, + expectedTypeDiagAddendum?: DiagnosticAddendum + ) { + const baseTypeResult = getTypeOfExpression(target.d.leftExpr, EvalFlags.MemberAccessBaseDefaults); + const baseType = makeTopLevelTypeVarsConcrete(baseTypeResult.type); + let enclosingClass: ClassType | undefined; + + // Handle member accesses (e.g. self.x or cls.y). + if (target.d.leftExpr.nodeType === ParseNodeType.Name) { + // Determine whether we're writing to a class or instance member. + const enclosingClassNode = ParseTreeUtils.getEnclosingClass(target); + + if (enclosingClassNode) { + const classTypeResults = getTypeOfClass(enclosingClassNode); + + if (classTypeResults && isInstantiableClass(classTypeResults.classType)) { + enclosingClass = classTypeResults.classType; + + if (isClassInstance(baseType)) { + if ( + ClassType.isSameGenericClass( + ClassType.cloneAsInstantiable(baseType), + classTypeResults.classType + ) + ) { + assignTypeToMemberVariable(target, typeResult, /* isInstanceMember */ true, srcExpr); + } + } else if (isInstantiableClass(baseType)) { + if (ClassType.isSameGenericClass(baseType, classTypeResults.classType)) { + assignTypeToMemberVariable(target, typeResult, /* isInstanceMember */ false, srcExpr); + } + } + + // Assignments to instance or class variables through "self" or "cls" is not + // allowed for protocol classes unless it is also declared within the class. + if (ClassType.isProtocolClass(classTypeResults.classType)) { + const memberSymbol = ClassType.getSymbolTable(classTypeResults.classType).get( + target.d.member.d.value + ); + if (memberSymbol) { + const classLevelDecls = memberSymbol.getDeclarations().filter((decl) => { + return !ParseTreeUtils.getEnclosingFunction(decl.node); + }); + if (classLevelDecls.length === 0) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.assignmentInProtocol(), + target.d.member + ); + } + } + } + } + } + } + + const setTypeResult = getTypeOfMemberAccessWithBaseType( + target, + baseTypeResult, + { + method: 'set', + setType: typeResult, + setErrorNode: srcExpr, + setExpectedTypeDiag: expectedTypeDiagAddendum, + }, + EvalFlags.None + ); + + if (setTypeResult.isAsymmetricAccessor) { + setAsymmetricDescriptorAssignment(target); + } + + const resultToCache: TypeResult = { + type: setTypeResult.narrowedTypeForSet ?? typeResult.type, + isIncomplete: typeResult.isIncomplete, + memberAccessDeprecationInfo: setTypeResult.memberAccessDeprecationInfo, + }; + writeTypeCache(target, resultToCache, EvalFlags.None); + + // If the target is an instance or class variable, update any class-scoped + // type variables so the inferred type of the variable uses "external" + // type variables. + let memberResultToCache = resultToCache; + if (enclosingClass?.shared.typeVarScopeId) { + memberResultToCache = { + ...resultToCache, + type: makeTypeVarsFree(resultToCache.type, [enclosingClass.shared.typeVarScopeId]), + memberAccessDeprecationInfo: setTypeResult.memberAccessDeprecationInfo, + }; + } + writeTypeCache(target.d.member, memberResultToCache, EvalFlags.None); + } + + function assignTypeToMemberVariable( + node: MemberAccessNode, + typeResult: TypeResult, + isInstanceMember: boolean, + srcExprNode?: ExpressionNode + ) { + const memberName = node.d.member.d.value; + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + + const classDef = ParseTreeUtils.getEnclosingClass(node); + if (!classDef) { + return; + } + + const classTypeInfo = getTypeOfClass(classDef); + if (classTypeInfo && isInstantiableClass(classTypeInfo.classType)) { + let memberInfo = lookUpClassMember( + classTypeInfo.classType, + memberName, + isInstanceMember ? MemberAccessFlags.Default : MemberAccessFlags.SkipInstanceMembers + ); + + const memberFields = ClassType.getSymbolTable(classTypeInfo.classType); + if (memberInfo) { + // Are we accessing an existing member on this class, or is + // it a member on a parent class? + const memberClass = isInstantiableClass(memberInfo.classType) ? memberInfo.classType : undefined; + const isThisClass = memberClass && ClassType.isSameGenericClass(classTypeInfo.classType, memberClass); + + // Check for an attempt to write to an instance variable that is + // not defined by __slots__. + if (isThisClass && isInstanceMember && memberClass) { + const inheritedSlotsNames = ClassType.getInheritedSlotsNames(memberClass); + + if (inheritedSlotsNames && memberClass.shared.localSlotsNames) { + // Skip this check if the local slots is specified but empty + // and the class isn't final. This pattern is used in a + // legitimate manner for mix-in classes. + if ( + (memberClass.shared.localSlotsNames.length > 0 || ClassType.isFinal(memberClass)) && + !inheritedSlotsNames.some((name) => name === memberName) + ) { + // Determine whether the assignment corresponds to a descriptor + // that was assigned as a class variable. If so, then slots will not + // apply in this case. + const classMemberDetails = lookUpClassMember( + memberClass, + memberName, + MemberAccessFlags.SkipInstanceMembers + ); + let isPotentiallyDescriptor = false; + + if (classMemberDetails) { + const classMemberSymbolType = getEffectiveTypeOfSymbol(classMemberDetails.symbol); + if ( + isAnyOrUnknown(classMemberSymbolType) || + isUnbound(classMemberSymbolType) || + isMaybeDescriptorInstance(classMemberSymbolType) + ) { + isPotentiallyDescriptor = true; + } + } + + if (!isPotentiallyDescriptor) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.slotsAttributeError().format({ name: memberName }), + node.d.member + ); + } + } + } + } + + if (isThisClass && memberInfo.isInstanceMember === isInstanceMember) { + const symbol = memberFields.get(memberName)!; + assert(symbol !== undefined); + + const typedDecls = symbol.getDeclarations(); + + // Check for an attempt to overwrite a constant member variable. + if ( + typedDecls.length > 0 && + typedDecls[0].type === DeclarationType.Variable && + srcExprNode && + node.d.member !== typedDecls[0].node + ) { + if (typedDecls[0].isConstant) { + addDiagnostic( + DiagnosticRule.reportConstantRedefinition, + LocMessage.constantRedefinition().format({ name: node.d.member.d.value }), + node.d.member + ); + } + } + } else { + // Is the target a property? + const declaredType = getDeclaredTypeOfSymbol(memberInfo.symbol)?.type; + if (declaredType && !isProperty(declaredType)) { + // Handle the case where there is a class variable defined with the same + // name, but there's also now an instance variable introduced. Combine the + // type of the class variable with that of the new instance variable. + if (!memberInfo.isInstanceMember && isInstanceMember) { + // The class variable is accessed in this case. + setSymbolAccessed(fileInfo, memberInfo.symbol, node.d.member); + const memberType = getTypeOfMember(memberInfo); + typeResult = { ...typeResult, type: combineTypes([typeResult.type, memberType]) }; + } + } + } + } + + // Look up the member info again, now that we've potentially updated it. + memberInfo = lookUpClassMember(classTypeInfo.classType, memberName, MemberAccessFlags.DeclaredTypesOnly); + + if (!memberInfo && srcExprNode && !typeResult.isIncomplete) { + reportPossibleUnknownAssignment( + fileInfo.diagnosticRuleSet.reportUnknownMemberType, + DiagnosticRule.reportUnknownMemberType, + node.d.member, + typeResult.type, + node, + /* ignoreEmptyContainers */ true + ); + } + } + } + + function assignTypeToTupleOrListNode( + target: TupleNode | ListNode, + typeResult: TypeResult, + srcExpr: ExpressionNode + ) { + const targetExpressions = target.nodeType === ParseNodeType.List ? target.d.items : target.d.items; + + // Initialize the array of target types, one for each target. + const targetTypes: Type[][] = new Array(targetExpressions.length); + for (let i = 0; i < targetExpressions.length; i++) { + targetTypes[i] = []; + } + const targetUnpackIndex = targetExpressions.findIndex((expr) => expr.nodeType === ParseNodeType.Unpack); + + // Do any of the targets use an unpack operator? If so, it will consume all of the + // entries at that location. + const unpackIndex = targetExpressions.findIndex((expr) => expr.nodeType === ParseNodeType.Unpack); + + typeResult = { ...typeResult, type: makeTopLevelTypeVarsConcrete(typeResult.type) }; + + const diagAddendum = new DiagnosticAddendum(); + + doForEachSubtype(typeResult.type, (subtype) => { + // Is this subtype a tuple? + const tupleType = getSpecializedTupleType(subtype); + if (tupleType && tupleType.priv.tupleTypeArgs) { + const sourceEntryTypes = tupleType.priv.tupleTypeArgs.map((t) => + addConditionToType(t.type, getTypeCondition(subtype), { skipSelfCondition: true }) + ); + + const unboundedIndex = tupleType.priv.tupleTypeArgs.findIndex((t) => t.isUnbounded); + + if (unboundedIndex >= 0) { + if (sourceEntryTypes.length < targetTypes.length) { + const typeToReplicate = + sourceEntryTypes.length > 0 ? sourceEntryTypes[unboundedIndex] : AnyType.create(); + + // Add elements to make the count match the target count. + while (sourceEntryTypes.length < targetTypes.length) { + sourceEntryTypes.splice(unboundedIndex, 0, typeToReplicate); + } + } + + if (sourceEntryTypes.length > targetTypes.length) { + // Remove elements to make the count match the target count. + sourceEntryTypes.splice(unboundedIndex, 1); + } + } + + // If there's an unpack operator in the target and we have too many source elements, + // combine them to assign to the unpacked target. + if (targetUnpackIndex >= 0) { + if (sourceEntryTypes.length > targetTypes.length) { + const removedEntries = sourceEntryTypes.splice( + targetUnpackIndex, + sourceEntryTypes.length - targetTypes.length + 1 + ); + let combinedTypes = combineTypes(removedEntries); + if (target.nodeType === ParseNodeType.List) { + combinedTypes = stripLiteralValue(combinedTypes); + } + sourceEntryTypes.splice(targetUnpackIndex, 0, combinedTypes); + } else if (sourceEntryTypes.length === targetTypes.length - 1) { + sourceEntryTypes.splice(targetUnpackIndex, 0, NeverType.createNever()); + } + } + + sourceEntryTypes.forEach((type, targetIndex) => { + if (targetIndex < targetTypes.length) { + targetTypes[targetIndex].push(type); + } + }); + + // Have we accounted for all of the targets and sources? If not, we have a size mismatch. + if (sourceEntryTypes.length !== targetExpressions.length) { + const subDiag = diagAddendum.createAddendum(); + subDiag.addMessage( + (target.nodeType === ParseNodeType.List + ? LocAddendum.listAssignmentMismatch() + : LocAddendum.tupleAssignmentMismatch() + ).format({ + type: printType(subtype), + }) + ); + + subDiag.createAddendum().addMessage( + (unpackIndex >= 0 + ? LocAddendum.tupleSizeMismatchIndeterminateDest() + : LocAddendum.tupleSizeMismatch() + ).format({ + expected: unpackIndex >= 0 ? targetExpressions.length - 1 : targetExpressions.length, + received: sourceEntryTypes.length, + }) + ); + } + } else { + // The assigned expression isn't a tuple, so it had better + // be some iterable type. + const iterableType = + getTypeOfIterator( + { type: subtype, isIncomplete: typeResult.isIncomplete }, + /* isAsync */ false, + srcExpr + )?.type ?? UnknownType.create(); + for (let index = 0; index < targetExpressions.length; index++) { + targetTypes[index].push(addConditionToType(iterableType, getTypeCondition(subtype))); + } + } + }); + + if (!diagAddendum.isEmpty()) { + addDiagnostic( + DiagnosticRule.reportAssignmentType, + (target.nodeType === ParseNodeType.List + ? LocMessage.listAssignmentMismatch() + : LocMessage.tupleAssignmentMismatch() + ).format({ + type: printType(typeResult.type), + }) + diagAddendum.getString(), + target + ); + } + + // Assign the resulting types to the individual names in the tuple + // or list target expression. + targetExpressions.forEach((expr, index) => { + const typeList = targetTypes[index]; + const targetType = typeList.length === 0 ? UnknownType.create() : combineTypes(typeList); + + assignTypeToExpression( + expr, + { type: targetType, isIncomplete: typeResult.isIncomplete }, + srcExpr, + /* ignoreEmptyContainers */ true + ); + }); + + writeTypeCache(target, typeResult, EvalFlags.None); + } + + // If the type includes promotion types, expand these to their constituent types. + function expandPromotionTypes(node: ParseNode, type: Type, excludeBytes = false): Type { + return mapSubtypes(type, (subtype) => { + if (!isClass(subtype) || !subtype.priv.includePromotions || subtype.priv.literalValue !== undefined) { + return subtype; + } + + if (excludeBytes && ClassType.isBuiltIn(subtype, 'bytes')) { + return subtype; + } + + const typesToCombine: Type[] = [ClassType.cloneRemoveTypePromotions(subtype)]; + + const promotionTypeNames = typePromotions.get(subtype.shared.fullName); + if (promotionTypeNames) { + for (const promotionTypeName of promotionTypeNames) { + const nameSplit = promotionTypeName.split('.'); + let promotionSubtype = getBuiltInType(node, nameSplit[nameSplit.length - 1]); + + if (promotionSubtype && isInstantiableClass(promotionSubtype)) { + promotionSubtype = ClassType.cloneRemoveTypePromotions(promotionSubtype); + + if (isClassInstance(subtype)) { + promotionSubtype = ClassType.cloneAsInstance(promotionSubtype); + } + + promotionSubtype = addConditionToType(promotionSubtype, subtype.props?.condition); + typesToCombine.push(promotionSubtype); + } + } + } + + return combineTypes(typesToCombine); + }); + } + + // Replaces all of the top-level TypeVars (as opposed to TypeVars + // used as type arguments in other types) with their concrete form. + // If conditionFilter is specified and the TypeVar is a constrained + // TypeVar, only the conditions that match the filter will be included. + function makeTopLevelTypeVarsConcrete( + type: Type, + makeParamSpecsConcrete = false, + conditionFilter?: TypeCondition[] + ): Type { + type = transformPossibleRecursiveTypeAlias(type); + + return mapSubtypes(type, (subtype) => { + if (isParamSpec(subtype)) { + if (subtype.priv.paramSpecAccess === 'args') { + return makeTupleObject(evaluatorInterface, [{ type: getObjectType(), isUnbounded: true }]); + } else if (subtype.priv.paramSpecAccess === 'kwargs') { + if ( + prefetched?.dictClass && + isInstantiableClass(prefetched.dictClass) && + prefetched?.strClass && + isInstantiableClass(prefetched.strClass) + ) { + return ClassType.cloneAsInstance( + ClassType.specialize(prefetched.dictClass, [ + convertToInstance(prefetched.strClass), + getObjectType(), + ]) + ); + } + + return UnknownType.create(); + } + } + + // If this is a function that contains only a ParamSpec (no additional + // parameters), convert it to a concrete type of (*args: Unknown, **kwargs: Unknown). + if (makeParamSpecsConcrete && isFunction(subtype)) { + const convertedType = simplifyFunctionToParamSpec(subtype); + if (isParamSpec(convertedType)) { + return ParamSpecType.getUnknown(); + } + } + + if (isTypeVarTuple(subtype)) { + // If it's in a union, convert to type or object. + if (subtype.priv.isInUnion) { + if (TypeBase.isInstantiable(subtype)) { + if (prefetched?.typeClass && isInstantiableClass(prefetched.typeClass)) { + return prefetched.typeClass; + } + } else { + return getObjectType(); + } + + return AnyType.create(); + } + + // Fall back to "*tuple[object, ...]". + return makeTupleObject( + evaluatorInterface, + [{ type: getObjectType(), isUnbounded: true }], + /* isUnpacked */ true + ); + } + + if (isTypeVar(subtype)) { + // If this is a recursive type alias placeholder + // that hasn't yet been resolved, return it as is. + if (subtype.shared.recursiveAlias) { + return subtype; + } + + if (TypeVarType.hasConstraints(subtype)) { + const typesToCombine: Type[] = []; + + // Expand the list of constrained subtypes, filtering out any that are + // disallowed by the conditionFilter. + subtype.shared.constraints.forEach((constraintType, constraintIndex) => { + if (conditionFilter) { + const typeVarName = TypeVarType.getNameWithScope(subtype); + const applicableConstraint = conditionFilter.find( + (filter) => filter.typeVar.priv.nameWithScope === typeVarName + ); + + // If this type variable is being constrained to a single index, + // don't include the other indices. + if (applicableConstraint && applicableConstraint.constraintIndex !== constraintIndex) { + return; + } + } + + if (TypeBase.isInstantiable(subtype)) { + constraintType = convertToInstantiable(constraintType); + } + + typesToCombine.push( + addConditionToType(constraintType, [{ typeVar: subtype, constraintIndex }]) + ); + }); + + return combineTypes(typesToCombine); + } + + if (subtype.shared.isExemptFromBoundCheck) { + return AnyType.create(); + } + + // Fall back to a bound of "object" if no bound is provided. + let boundType = subtype.shared.boundType ?? getObjectType(); + + // If this is a synthesized self/cls type var, self-specialize its type arguments. + if (TypeVarType.isSelf(subtype) && isClass(boundType) && !ClassType.isPseudoGenericClass(boundType)) { + boundType = selfSpecializeClass(boundType, { + useBoundTypeVars: TypeVarType.isBound(subtype), + }); + } + + if (subtype.priv.isUnpacked && isClass(boundType)) { + boundType = ClassType.cloneForUnpacked(boundType); + } + + boundType = TypeBase.isInstantiable(subtype) ? convertToInstantiable(boundType) : boundType; + + return addConditionToType(boundType, [{ typeVar: subtype, constraintIndex: 0 }]); + } + + return subtype; + }); + } + + // Creates a new type by mapping an existing type (which could be a union) + // to another type or types. The callback is called for each subtype. + // Top-level TypeVars are expanded (e.g. a bound TypeVar is expanded to + // its bound type and a constrained TypeVar is expanded to its individual + // constrained types). If conditionFilter is specified, conditions that + // do not match will be ignored. + function mapSubtypesExpandTypeVars( + type: Type, + options: MapSubtypesOptions | undefined, + callback: (expandedSubtype: Type, unexpandedSubtype: Type, isLastIteration: boolean) => Type | undefined, + recursionCount = 0 + ): Type { + const newSubtypes: Type[] = []; + let typeChanged = false; + + function expandSubtype(unexpandedType: Type, isLastSubtype: boolean) { + let expandedType = isUnion(unexpandedType) ? unexpandedType : makeTopLevelTypeVarsConcrete(unexpandedType); + + expandedType = transformPossibleRecursiveTypeAlias(expandedType); + if (options?.expandCallback) { + expandedType = options.expandCallback(expandedType); + } + + doForEachSubtype( + expandedType, + (subtype, index, allSubtypes) => { + if (options?.conditionFilter) { + const filteredType = applyConditionFilterToType( + subtype, + options.conditionFilter, + recursionCount + ); + if (!filteredType) { + return undefined; + } + + subtype = filteredType; + } + + let transformedType = callback( + subtype, + unexpandedType, + isLastSubtype && index === allSubtypes.length - 1 + ); + + if (transformedType !== unexpandedType) { + typeChanged = true; + } + + if (transformedType) { + // Apply the type condition if it's associated with a constrained TypeVar. + const typeCondition = getTypeCondition(subtype)?.filter((condition) => + TypeVarType.hasConstraints(condition.typeVar) + ); + + if (typeCondition && typeCondition.length > 0) { + transformedType = addConditionToType(transformedType, typeCondition); + } + + // This code path can often produce many duplicate subtypes. We can + // reduce the cost of the combineTypes call below by filtering out these + // duplicates proactively. + if ( + newSubtypes.length === 0 || + !isTypeSame(transformedType, newSubtypes[newSubtypes.length - 1]) + ) { + newSubtypes.push(transformedType); + } + } + return undefined; + }, + options?.sortSubtypes + ); + } + + if (isUnion(type)) { + const subtypes = options?.sortSubtypes ? sortTypes(type.priv.subtypes) : type.priv.subtypes; + subtypes.forEach((subtype, index) => { + expandSubtype(subtype, index === type.priv.subtypes.length - 1); + }); + } else { + expandSubtype(type, /* isLastSubtype */ true); + } + + if (!typeChanged) { + return type; + } + + const newType = combineTypes(newSubtypes); + + // Do our best to retain type aliases. + if (newType.category === TypeCategory.Union) { + UnionType.addTypeAliasSource(newType, type); + } + return newType; + } + + function applyConditionFilterToType( + type: Type, + conditionFilter: TypeCondition[], + recursionCount: number + ): Type | undefined { + if (recursionCount > maxTypeRecursionCount) { + return type; + } + recursionCount++; + + // If the type has a condition associated with it, make sure it's compatible. + if (!TypeCondition.isCompatible(getTypeCondition(type), conditionFilter)) { + return undefined; + } + + // If the type is generic, see if any of its type arguments should be filtered. + // This is possible only in cases where the type parameter is covariant. + + // TODO - handle functions and tuples + if (isClass(type) && type.priv.typeArgs && !type.priv.tupleTypeArgs) { + inferVarianceForClass(type); + + let typeWasTransformed = false; + + const filteredTypeArgs = type.priv.typeArgs.map((typeArg, index) => { + if (index >= type.shared.typeParams.length) { + return typeArg; + } + + const variance = TypeVarType.getVariance(type.shared.typeParams[index]); + if (variance !== Variance.Covariant) { + return typeArg; + } + + // Don't expand recursive type aliases because they can + // cause infinite recursion. + if (isTypeVar(typeArg) && typeArg.shared.recursiveAlias) { + return typeArg; + } + + const filteredTypeArg = mapSubtypesExpandTypeVars( + typeArg, + { conditionFilter }, + (expandedSubtype) => { + return expandedSubtype; + }, + recursionCount + ); + + if (filteredTypeArg !== typeArg) { + typeWasTransformed = true; + } + + return filteredTypeArg; + }); + + if (typeWasTransformed) { + return ClassType.specialize(type, filteredTypeArgs); + } + } + + return type; + } + + function markNamesAccessed(node: ParseNode, names: string[]) { + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + const scope = ScopeUtils.getScopeForNode(node); + + if (scope) { + names.forEach((symbolName) => { + const symbolInScope = scope.lookUpSymbolRecursive(symbolName); + if (symbolInScope) { + setSymbolAccessed(fileInfo, symbolInScope.symbol, node); + } + }); + } + } + + function assignTypeToExpression( + target: ExpressionNode, + typeResult: TypeResult, + srcExpr: ExpressionNode, + ignoreEmptyContainers = false, + allowAssignmentToFinalVar = false, + expectedTypeDiagAddendum?: DiagnosticAddendum + ) { + // Is the source expression a TypeVar() call? + if (isTypeVar(typeResult.type)) { + if (srcExpr && srcExpr.nodeType === ParseNodeType.Call) { + const callType = getTypeOfExpression(srcExpr.d.leftExpr, EvalFlags.CallBaseDefaults).type; + if ( + isInstantiableClass(callType) && + (ClassType.isBuiltIn(callType, 'TypeVar') || + ClassType.isBuiltIn(callType, 'TypeVarTuple') || + ClassType.isBuiltIn(callType, 'ParamSpec')) + ) { + const typeVarTarget = + target.nodeType === ParseNodeType.TypeAnnotation ? target.d.valueExpr : target; + if ( + typeVarTarget.nodeType !== ParseNodeType.Name || + typeVarTarget.d.value !== typeResult.type.shared.name + ) { + const name = TypeVarType.getReadableName(typeResult.type); + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + isParamSpec(typeResult.type) + ? LocMessage.paramSpecAssignedName().format({ name }) + : LocMessage.typeVarAssignedName().format({ name }), + typeVarTarget + ); + } + } + } + } + + // If the type was partially unbound, an error will have already been logged. + // Remove the unbound before assigning to the target expression so the unbound + // error doesn't propagate. + if (findSubtype(typeResult.type, (subtype) => isUnbound(subtype))) { + typeResult = { ...typeResult, type: removeUnbound(typeResult.type) }; + } + + switch (target.nodeType) { + case ParseNodeType.Name: { + assignTypeToNameNode( + target, + typeResult, + ignoreEmptyContainers, + srcExpr, + allowAssignmentToFinalVar, + expectedTypeDiagAddendum + ); + break; + } + + case ParseNodeType.MemberAccess: { + assignTypeToMemberAccessNode(target, typeResult, srcExpr, expectedTypeDiagAddendum); + break; + } + + case ParseNodeType.Index: { + const baseTypeResult = getTypeOfExpression(target.d.leftExpr, EvalFlags.IndexBaseDefaults); + + getTypeOfIndexWithBaseType( + target, + baseTypeResult, + { + method: 'set', + setType: typeResult, + setErrorNode: srcExpr, + setExpectedTypeDiag: expectedTypeDiagAddendum, + }, + EvalFlags.None + ); + + writeTypeCache(target, typeResult, EvalFlags.None); + break; + } + + case ParseNodeType.List: + case ParseNodeType.Tuple: { + assignTypeToTupleOrListNode(target, typeResult, srcExpr); + break; + } + + case ParseNodeType.TypeAnnotation: { + getTypeOfAnnotation(target.d.annotation, { + varTypeAnnotation: true, + allowFinal: isFinalAllowedForAssignmentTarget(target.d.valueExpr), + allowClassVar: isClassVarAllowedForAssignmentTarget(target.d.valueExpr), + }); + + assignTypeToExpression( + target.d.valueExpr, + typeResult, + srcExpr, + ignoreEmptyContainers, + allowAssignmentToFinalVar, + expectedTypeDiagAddendum + ); + break; + } + + case ParseNodeType.Unpack: { + assignTypeToExpression( + target.d.expr, + { + type: getBuiltInObject(target.d.expr, 'list', [typeResult.type]), + isIncomplete: typeResult.isIncomplete, + }, + srcExpr, + ignoreEmptyContainers, + allowAssignmentToFinalVar, + expectedTypeDiagAddendum + ); + break; + } + + case ParseNodeType.Error: { + // Evaluate the child expression as best we can so the + // type information is cached for the completion handler. + if (target.d.child) { + suppressDiagnostics(target.d.child, () => { + getTypeOfExpression(target.d.child!); + }); + } + break; + } + + default: { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.assignmentTargetExpr(), target); + break; + } + } + } + + function isClassVarAllowedForAssignmentTarget(targetNode: ExpressionNode): boolean { + // ClassVar is allowed only in a class body. + const classNode = ParseTreeUtils.getEnclosingClass(targetNode, /* stopAtFunction */ true); + if (!classNode) { + return false; + } + + // ClassVar is not allowed in a TypedDict or a NamedTuple class. + return !isInTypedDictOrNamedTuple(classNode); + } + + function isFinalAllowedForAssignmentTarget(targetNode: ExpressionNode): boolean { + const classNode = ParseTreeUtils.getEnclosingClass(targetNode, /* stopAtFunction */ true); + + // Final is not allowed in the body of a TypedDict or NamedTuple class. + if (classNode && isInTypedDictOrNamedTuple(classNode)) { + return false; + } + + return ParseTreeUtils.isFinalAllowedForAssignmentTarget(targetNode); + } + + function isInTypedDictOrNamedTuple(classNode: ClassNode): boolean { + const classType = getTypeOfClass(classNode)?.classType; + if (!classType) { + return false; + } + + return ClassType.isTypedDictClass(classType) || !!classType.shared.namedTupleEntries; + } + + function verifyRaiseExceptionType(node: ExpressionNode, allowNone: boolean) { + const baseExceptionType = getBuiltInType(node, 'BaseException'); + const exceptionType = getTypeOfExpression(node).type; + + // Validate that the argument of "raise" is an exception object or class. + // If it is a class, validate that the class's constructor accepts zero + // arguments. + if (exceptionType && baseExceptionType && isInstantiableClass(baseExceptionType)) { + const diag = new DiagnosticAddendum(); + + doForEachSubtype(exceptionType, (subtype) => { + const concreteSubtype = makeTopLevelTypeVarsConcrete(subtype); + + if (isAnyOrUnknown(concreteSubtype) || isNever(concreteSubtype)) { + return; + } + + if (allowNone && isNoneInstance(concreteSubtype)) { + return; + } + + if (isInstantiableClass(concreteSubtype) && concreteSubtype.priv.literalValue === undefined) { + if (!derivesFromClassRecursive(concreteSubtype, baseExceptionType, /* ignoreUnknown */ false)) { + diag.addMessage( + LocMessage.exceptionTypeIncorrect().format({ + type: printType(subtype), + }) + ); + } else { + let callResult: CallResult | undefined; + suppressDiagnostics(node, () => { + callResult = validateConstructorArgs( + evaluatorInterface, + node, + [], + concreteSubtype, + /* skipUnknownArgCheck */ false, + /* inferenceContext */ undefined + ); + }); + + if (callResult && callResult.argumentErrors) { + diag.addMessage( + LocMessage.exceptionTypeNotInstantiable().format({ + type: printType(subtype), + }) + ); + } + } + } else if (isClassInstance(concreteSubtype)) { + if ( + !derivesFromClassRecursive( + ClassType.cloneAsInstantiable(concreteSubtype), + baseExceptionType, + /* ignoreUnknown */ false + ) + ) { + diag.addMessage( + LocMessage.exceptionTypeIncorrect().format({ + type: printType(subtype), + }) + ); + } + } else { + diag.addMessage( + LocMessage.exceptionTypeIncorrect().format({ + type: printType(subtype), + }) + ); + } + }); + + if (!diag.isEmpty()) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.expectedExceptionClass() + diag.getString(), + node + ); + } + } + } + + function verifyDeleteExpression(node: ExpressionNode) { + switch (node.nodeType) { + case ParseNodeType.Name: { + // Get the type to evaluate whether it's bound + // and to mark it accessed. + getTypeOfExpression(node); + break; + } + + case ParseNodeType.MemberAccess: { + const baseTypeResult = getTypeOfExpression(node.d.leftExpr, EvalFlags.MemberAccessBaseDefaults); + const delAccessResult = getTypeOfMemberAccessWithBaseType( + node, + baseTypeResult, + { method: 'del' }, + EvalFlags.None + ); + const resultToCache: TypeResult = { + type: delAccessResult.type, + memberAccessDeprecationInfo: delAccessResult.memberAccessDeprecationInfo, + }; + writeTypeCache(node.d.member, resultToCache, EvalFlags.None); + writeTypeCache(node, resultToCache, EvalFlags.None); + break; + } + + case ParseNodeType.Index: { + const baseTypeResult = getTypeOfExpression(node.d.leftExpr, EvalFlags.IndexBaseDefaults); + getTypeOfIndexWithBaseType(node, baseTypeResult, { method: 'del' }, EvalFlags.None); + writeTypeCache(node, { type: UnboundType.create() }, EvalFlags.None); + break; + } + + case ParseNodeType.Tuple: { + node.d.items.forEach((expr) => { + verifyDeleteExpression(expr); + }); + break; + } + + case ParseNodeType.Error: { + // Evaluate the child expression as best we can so the + // type information is cached for the completion handler. + if (node.d.child) { + suppressDiagnostics(node.d.child, () => { + getTypeOfExpression(node.d.child!); + }); + } + break; + } + + default: { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.delTargetExpr(), node); + break; + } + } + } + + function setSymbolAccessed(fileInfo: AnalyzerFileInfo, symbol: Symbol, node: ParseNode) { + if (!isSpeculativeModeInUse(node)) { + fileInfo.accessedSymbolSet.add(symbol.id); + } + } + + function getTypeOfName(node: NameNode, flags: EvalFlags): TypeResult { + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + const name = node.d.value; + let symbol: Symbol | undefined; + let type: Type | undefined; + let isIncomplete = false; + const allowForwardReferences = (flags & EvalFlags.ForwardRefs) !== 0 || fileInfo.isStubFile; + + // Look for the scope that contains the value definition and + // see if it has a declared type. + let symbolWithScope = lookUpSymbolRecursive( + node, + name, + !allowForwardReferences, + allowForwardReferences && (flags & EvalFlags.TypeExpression) !== 0 + ); + + if (!symbolWithScope) { + // If the node is part of a "from X import Y as Z" statement and the node + // is the "Y" (non-aliased) name, we need to look up the alias symbol + // since the non-aliased name is not in the symbol table. + const alias = getAliasFromImport(node); + if (alias) { + symbolWithScope = lookUpSymbolRecursive( + alias, + alias.d.value, + !allowForwardReferences, + allowForwardReferences && (flags & EvalFlags.TypeExpression) !== 0 + ); + } + } + + if (symbolWithScope) { + let useCodeFlowAnalysis = !allowForwardReferences; + + // If the symbol is implicitly imported from the builtin + // scope, there's no need to use code flow analysis. + if (symbolWithScope.scope.type === ScopeType.Builtin) { + useCodeFlowAnalysis = false; + + // Viper 扩展:模拟编译器的自动 import 机制。 + // Viper 编译器的 Phase2 会自动检测 list/dict 容器的使用并注入 + // `import _list` / `import _dict`(见 TransPyC Phase2Translator._inject_auto_imports), + // 因此用户源码无需手写 import。 + // LSP 若不模拟此机制,裸名 list/dict 会解析到 Python builtins 的 + // `list.__init__(iterable: Iterable[_T])`,把内存分配器实参(如 + // memhub.MemBuddy)当作可迭代对象检查而误报。 + // 这里在解析到 builtin 的 list/dict 时,尝试用 includes 中的 + // _list.vp / _dict.vp 真实定义替换(容器构造签名是 + // `__init__(self, pool: memhub.MemManager | t.CPtr, ...)`)。 + const viperBuiltinModule = + IsViperDialect(fileInfo.executionEnvironment.dialect) && + (name === 'list' || name === 'dict') + ? (name === 'list' ? '_list' : '_dict') + : undefined; + if (viperBuiltinModule) { + const lookupResult = importLookup({ + nameParts: [viperBuiltinModule], + importingFileUri: fileInfo.fileUri, + }); + if (lookupResult) { + const containerSymbol = lookupResult.symbolTable.get(name); + if (containerSymbol) { + symbolWithScope = { + symbol: containerSymbol, + isOutsideCallerModule: false, + isBeyondExecutionScope: false, + usesNonlocalBinding: false, + usesGlobalBinding: false, + scope: symbolWithScope.scope, + }; + } + } + } + } + + symbol = symbolWithScope.symbol; + setSymbolAccessed(fileInfo, symbol, node); + + // If we're not supposed to be analyzing this function, skip the remaining work + // to determine the name's type. Simply evaluate its type as Any. + if (!fileInfo.diagnosticRuleSet.analyzeUnannotatedFunctions) { + const containingFunction = ParseTreeUtils.getEnclosingFunction(node); + if (containingFunction && ParseTreeUtils.isUnannotatedFunction(containingFunction)) { + return { + type: AnyType.create(), + isIncomplete: false, + }; + } + } + + // Get the effective type (either the declared type or the inferred type). + // If we're using code flow analysis, pass the usage node so we consider + // only the assignment nodes that are reachable from this usage. + const effectiveTypeInfo = getEffectiveTypeOfSymbolForUsage(symbol, useCodeFlowAnalysis ? node : undefined); + let effectiveType = transformPossibleRecursiveTypeAlias(effectiveTypeInfo.type); + + if (effectiveTypeInfo.isIncomplete) { + if (isUnbound(effectiveType)) { + effectiveType = UnknownType.create(/* isIncomplete */ true); + } + isIncomplete = true; + } + + // Viper 扩展:带类型注解的变量,若声明类型求值失败返回 UnboundType, + // 转换为 UnknownType 避免误报 unbound。 + // Viper 是 AOT 静态语言,变量有显式类型注解即视为已声明绑定, + // 即使类型注解中引用了未解析的前向符号(如 uhci_td | t.CPtr 中 + // uhci_td 尚未定义),变量本身也已绑定,不应报告 unbound。 + if ( + !effectiveTypeInfo.isIncomplete && + isUnbound(effectiveType) && + IsViperDialect(fileInfo.executionEnvironment.dialect) && + symbol.hasTypedDeclarations() + ) { + effectiveType = UnknownType.create(); + } + + if (effectiveTypeInfo.isRecursiveDefinition && isNodeReachable(node)) { + // Viper 扩展:循环引用包含 CPtr 时不报错。 + // C 语义中,struct 含指向自身的指针是合法的(如链表节点): + // struct Node { struct Node* next; } + // 对应 Viper: next: 'Node' | t.CPtr + // 指针大小固定,不需要知道结构体的完整定义,打破了无限递归。 + // 但无 CPtr 的循环引用(如 next: 'Node')是真正的无限递归,应报错。 + const isViperPtrCycle = + IsViperDialect(fileInfo.executionEnvironment.dialect) && + symbol.getTypedDeclarations().some((decl) => { + if (decl.type !== DeclarationType.Variable || !decl.typeAnnotationNode) { + return false; + } + return typeAnnotationContainsCPtr(decl.typeAnnotationNode); + }); + + if (!isViperPtrCycle) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.recursiveDefinition().format({ name }), + node + ); + } + } + + const isSpecialBuiltIn = + !!effectiveType && isInstantiableClass(effectiveType) && ClassType.isSpecialBuiltIn(effectiveType); + + type = effectiveType; + if (useCodeFlowAnalysis && !isSpecialBuiltIn) { + // See if code flow analysis can tell us anything more about the type. + // If the symbol is declared outside of our execution scope, use its effective + // type. If it's declared inside our execution scope, it generally starts + // as unbound at the start of the code flow. + let typeAtStart = effectiveType; + let isTypeAtStartIncomplete = false; + + if (!symbolWithScope.isBeyondExecutionScope && symbol.isInitiallyUnbound()) { + // Viper 扩展:在 Viper 方言下,带类型注解的变量声明 + // (如 `ncpu_cr: t.CArray[t.CChar, 32]`)视为栈上分配, + // 变量已"绑定"到其声明类型,不应标记为 unbound。 + // 保持 typeAtStart = effectiveType(声明的类型注解)。 + const isViperTypedDecl = + IsViperDialect(fileInfo.executionEnvironment.dialect) && + symbol.hasTypedDeclarations(); + + if (!isViperTypedDecl) { + typeAtStart = UnboundType.create(); + + // Viper 扩展:函数内局部变量未绑定时,回退到模块作用域的全局变量。 + // Viper 语义:读取全局变量不需要 global 声明,仅写入需要。 + // 当绑定器因函数内赋值将变量视为局部变量,但读取发生在赋值之前时, + // 应解析到同名的全局变量而非报 unbound 错误。 + if ( + IsViperDialect(fileInfo.executionEnvironment.dialect) && + symbolWithScope.scope.type !== ScopeType.Module + ) { + const globalScopeResult = symbolWithScope.scope.getGlobalScope(); + if (globalScopeResult) { + const globalSymbol = globalScopeResult.scope.lookUpSymbol(name); + if (globalSymbol) { + const globalEffectiveType = getEffectiveTypeOfSymbolForUsage(globalSymbol); + typeAtStart = globalEffectiveType.type; + } + } + } + + // Is this a module-level scope? If so, see if it's an alias of a builtin. + if (symbolWithScope.scope.type === ScopeType.Module) { + assert(symbolWithScope.scope.parent); + const builtInSymbol = symbolWithScope.scope.parent.lookUpSymbol(name); + if (builtInSymbol) { + const builtInEffectiveType = getEffectiveTypeOfSymbolForUsage(builtInSymbol); + typeAtStart = builtInEffectiveType.type; + } + } + } + } + + if (symbolWithScope.isBeyondExecutionScope) { + const outerScopeTypeResult = getCodeFlowTypeForCapturedVariable( + node, + symbolWithScope, + effectiveType + ); + + if (outerScopeTypeResult?.type) { + type = outerScopeTypeResult.type; + typeAtStart = type; + isTypeAtStartIncomplete = !!outerScopeTypeResult.isIncomplete; + } + } + + const codeFlowTypeResult = getFlowTypeOfReference(node, /* startNode */ undefined, { + targetSymbolId: symbol.id, + typeAtStart: { type: typeAtStart, isIncomplete: isTypeAtStartIncomplete }, + skipConditionalNarrowing: (flags & EvalFlags.TypeExpression) !== 0, + }); + + if (codeFlowTypeResult.type) { + type = codeFlowTypeResult.type; + } + + // Viper 扩展:带类型注解的变量保持声明类型(C 静态类型语义)。 + // 在 Viper 方言下,变量类型在声明时确定,不应被赋值收窄。 + // 例如 `s: Scheduler | t.CPtr = _sched_ptr` 应保持声明类型 + // `Scheduler | t.CPtr`,不被 `_sched_ptr` 的推断类型收窄。 + if ( + IsViperDialect(fileInfo.executionEnvironment.dialect) && + symbol.hasTypedDeclarations() && + effectiveType && + !isUnbound(effectiveType) + ) { + type = effectiveType; + } + + if (codeFlowTypeResult.isIncomplete) { + isIncomplete = true; + } + } + + // Detect, report, and fill in missing type arguments if appropriate. + type = reportMissingTypeArgs(node, type, flags); + + // Report inappropriate use of variables in type expressions. + if ((flags & EvalFlags.TypeExpression) !== 0) { + type = validateSymbolIsTypeExpression(node, type, !!effectiveTypeInfo.includesVariableDecl); + } + + if (isTypeVar(type) && !type.shared.isSynthesized) { + type = validateTypeVarUsage(node, type, flags); + } + + // Add TypeForm details if appropriate. + type = addTypeFormForSymbol(node, type, flags, !!effectiveTypeInfo.includesVariableDecl); + } else { + // Handle the special case of "reveal_type" and "reveal_locals". + if (name === 'reveal_type' || name === 'reveal_locals') { + type = AnyType.create(); + } else { + // Viper 扩展:前向引用支持。 + // Viper 是 AOT 语言,结构体/函数不在乎定义顺序(类似 C 语言的前向声明)。 + // 当符号未找到时(可能在更后方定义),不报 undefinedVariable 错误, + // 而是返回 UnknownType,允许后续定义的符号被引用。 + type = UnknownType.create(); + } + } + + if (isParamSpec(type) && type.priv.scopeId) { + if (flags & EvalFlags.NoParamSpec) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.paramSpecContext(), node); + type = UnknownType.create(); + } + } + + // If we're expecting a type expression and got a sentinel literal instance, + // treat it as its instantiable counterpart. This is similar to how None + // is treated in a type expression context. + if ((flags & EvalFlags.InstantiableType) !== 0 && isClassInstance(type) && isSentinelLiteral(type)) { + type = ClassType.cloneAsInstantiable(type); + } + + type = convertSpecialFormToRuntimeValue(type, flags); + + if ((flags & EvalFlags.TypeExpression) === 0) { + reportUseOfTypeCheckOnly(type, node); + } + + if ((flags & EvalFlags.InstantiableType) !== 0) { + if ((flags & EvalFlags.AllowGeneric) === 0) { + if (isInstantiableClass(type) && ClassType.isBuiltIn(type, 'Generic')) { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.genericNotAllowed(), node); + } + } + } + + return { type, isIncomplete }; + } + + function addTypeFormForSymbol(node: ExpressionNode, type: Type, flags: EvalFlags, includesVarDecl: boolean): Type { + const isValid = isSymbolValidTypeExpression(type, includesVarDecl); + + // If the type already has type information associated with it, don't replace. + if (type.props?.typeForm) { + // If the NoConvertSpecialForm flag is set, we are evaluating in + // the interior of a type expression, so variables are not allowed. + // Clear any existing type form type for this symbol in this case. + if ((flags & EvalFlags.NoConvertSpecialForm) !== 0 && !isValid) { + type = TypeBase.cloneWithTypeForm(type, undefined); + } + return type; + } + + // If the symbol is not valid for a type expression (e.g. it's a variable), + // don't add TypeForm info. + if (!isValid) { + return type; + } + + if (isTypeVar(type) && type.priv.scopeId && !type.shared.isSynthesized) { + if (!isTypeVarTuple(type) || !type.priv.isInUnion) { + const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(node); + type = TypeBase.cloneWithTypeForm(type, convertToInstance(makeTypeVarsBound(type, liveScopeIds))); + } + } else if (isInstantiableClass(type) && !type.priv.includeSubclasses && !ClassType.isSpecialBuiltIn(type)) { + if (ClassType.isBuiltIn(type, 'Any')) { + type = TypeBase.cloneWithTypeForm(type, AnyType.create()); + } else { + type = TypeBase.cloneWithTypeForm(type, ClassType.cloneAsInstance(specializeWithDefaultTypeArgs(type))); + } + } + + if (type.props?.typeAliasInfo && TypeBase.isInstantiable(type)) { + let typeFormType = type; + if ((flags & EvalFlags.NoSpecialize) === 0) { + typeFormType = specializeTypeAliasWithDefaults(typeFormType, /* errorNode */ undefined); + } + + type = TypeBase.cloneWithTypeForm(type, convertToInstance(typeFormType)); + } + + return type; + } + + function isSymbolValidTypeExpression(type: Type, includesVarDecl: boolean): boolean { + // Verify that the name does not refer to a (non type alias) variable. + if (!includesVarDecl || type.props?.typeAliasInfo) { + return true; + } + + if (isTypeAliasPlaceholder(type)) { + return true; + } + + if (isTypeVar(type)) { + if (type.props?.specialForm || type.props?.typeAliasInfo) { + return true; + } + } + + // Exempts class types that are created by calling NewType, NamedTuple, etc. + if (isClass(type) && !type.priv.includeSubclasses && ClassType.isValidTypeAliasClass(type)) { + return true; + } + + if (isSentinelLiteral(type)) { + return true; + } + + // Viper 扩展:t.CDefine 常量允许在类型表达式中使用。 + // CDefine 是编译时常量(类似 C 的 #define),在 Viper 中可作为 + // 数组长度等类型参数使用,如: + // UHCI_INT_SLOTS: t.CDefine = 4 + // _int_slots: t.CArray[int_slot, UHCI_INT_SLOTS] + if (isCDefineType(type)) { + return true; + } + + // Viper 扩展:整数字面量允许在类型表达式中使用。 + // Viper 中 CArray[T, N] 的 N 是数组长度(整数字面量), + // 如 CArray[t.CUInt32T, 6]。 + if (isClassInstance(type) && isLiteralType(type)) { + return true; + } + + return false; + } + + // Reports diagnostics if type isn't valid within a type expression. + function validateSymbolIsTypeExpression(node: ExpressionNode, type: Type, includesVarDecl: boolean): Type { + if (isSymbolValidTypeExpression(type, includesVarDecl)) { + return type; + } + + // Disable for assignments in the typings.pyi file, since it defines special forms. + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + if (fileInfo.isTypingStubFile) { + return type; + } + + // Viper 扩展:Viper 是 AOT 静态语言,类型系统比 Python 更灵活。 + // 跨模块 import 的 typedef、TypeSet、前向引用等场景下, + // 类型求值可能返回 Unknown、Union、类实例等非标准类型表达式类型。 + // 在 Viper 模式下,不报告 "Variable not allowed in type expression" 错误。 + if (IsViperDialect(fileInfo.executionEnvironment.dialect)) { + return type; + } + + // Viper 扩展(已废弃,保留注释):TypeSet(联合类型)允许作为类型表达式。 + // Viper 中 `|` 表示类型组合(TypeSet),如 `CUInt32T | CPtr` 表示 uint32_t*。 + // 类型别名(如 UINT32PTR: t.CTypedef = t.CUInt32T | t.CPtr)的值是联合类型, + // 在 Viper 模式下应允许作为类型表达式使用。 + if ( + IsViperDialect(fileInfo.executionEnvironment.dialect) && + isUnion(type) + ) { + return type; + } + + // Viper 扩展:Unknown 类型在 Viper 模式下允许作为类型表达式。 + // 跨模块 import 的 typedef(如 from ... import * 后使用 UINT32PTR) + // 可能因 resolveAliasDeclaration 解析失败而返回 Unknown。 + // Viper 是 AOT 语言,前向引用和跨模块引用是合法的, + // 不应因类型求值失败而报 "Variable not allowed in type expression"。 + if ( + IsViperDialect(fileInfo.executionEnvironment.dialect) && + isUnknown(type) + ) { + return type; + } + + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeAnnotationVariable(), node); + return UnknownType.create(); + } + + // If the value is a special form (like a TypeVar or `Any`) and is being + // evaluated in a value expression context, convert it from its special + // meaning to its runtime value. If convertModule is true, a module is + // converted to an instance of types.ModuleType. + function convertSpecialFormToRuntimeValue(type: Type, flags: EvalFlags, convertModule = false) { + const exemptFlags = EvalFlags.TypeExpression | EvalFlags.InstantiableType | EvalFlags.NoConvertSpecialForm; + + if ((flags & exemptFlags) !== 0) { + return type; + } + + if ( + convertModule && + isModule(type) && + prefetched?.moduleTypeClass && + isInstantiableClass(prefetched.moduleTypeClass) + ) { + return ClassType.cloneAsInstance(prefetched.moduleTypeClass); + } + + // Isinstance treats traditional (non-PEP 695) type aliases that are unions + // as tuples of classes rather than unions. + if ((flags & EvalFlags.IsinstanceArg) !== 0) { + if (isUnion(type) && type.props?.typeAliasInfo && !type.props.typeAliasInfo.shared.isTypeAliasType) { + return type; + } + } + + if (!type.props?.specialForm) { + return type; + } + + // If this is a type alias and we are not supposed to specialize it, return it as is. + if ((flags & EvalFlags.NoSpecialize) !== 0 && type.props?.typeAliasInfo) { + // Special-case TypeAliasType which should be converted in this case. + if (!ClassType.isBuiltIn(type.props.specialForm, 'TypeAliasType')) { + return type; + } + } + + if (type.props?.typeForm) { + return TypeBase.cloneWithTypeForm(type.props.specialForm, type.props.typeForm); + } + + return type.props.specialForm; + } + + // Handles the case where a variable or parameter is defined in an outer + // scope and captured by an inner scope (a function, lambda, or comprehension). + function getCodeFlowTypeForCapturedVariable( + node: NameNode, + symbolWithScope: SymbolWithScope, + effectiveType: Type + ): FlowNodeTypeResult | undefined { + // This function applies only to captured variables, not those that + // are accessed via an explicit nonlocal or global binding. + if (symbolWithScope.usesGlobalBinding || symbolWithScope.usesNonlocalBinding) { + return undefined; + } + + // This function applies only to variables, parameters, and imports, not to other + // types of symbols. + const decls = symbolWithScope.symbol.getDeclarations(); + if ( + !decls.every( + (decl) => + decl.type === DeclarationType.Variable || + decl.type === DeclarationType.Param || + decl.type === DeclarationType.Alias + ) + ) { + return undefined; + } + + // If the symbol is modified in scopes other than the one in which it is + // declared (e.g. through a nonlocal or global binding), it is not eligible + // for code flow analysis. + if ( + !decls.every( + (decl) => + decl.type === DeclarationType.Param || + ScopeUtils.getScopeForNode(decl.node) === symbolWithScope.scope + ) + ) { + return undefined; + } + + // If the symbol is a non-final variable in the global scope, it is not + // eligible because it could be modified by other modules. + if ( + !decls.every( + (decl) => + decl.type !== DeclarationType.Variable || + decl.isFinal || + ScopeUtils.getScopeForNode(decl.node)?.type !== ScopeType.Module + ) + ) { + return undefined; + } + + // If the symbol is a variable captured by an inner function + // or lambda, see if we can infer the type from the outer scope. + const scopeHierarchy = ScopeUtils.getScopeHierarchy(node, symbolWithScope.scope); + + if (scopeHierarchy && scopeHierarchy.length >= 2) { + // Find the parse node associated with the scope that is just inside of the + // scope that declares the captured variable. + const innerScopeNode = ScopeUtils.findTopNodeInScope(node, scopeHierarchy[scopeHierarchy.length - 2]); + if ( + innerScopeNode?.nodeType === ParseNodeType.Function || + innerScopeNode?.nodeType === ParseNodeType.Lambda || + innerScopeNode?.nodeType === ParseNodeType.Class + ) { + const innerScopeCodeFlowNode = AnalyzerNodeInfo.getFlowNode(innerScopeNode); + if (innerScopeCodeFlowNode) { + // See if any of the assignments of the symbol are reachable + // from this node. If so, we cannot apply any narrowing because + // the type could change after the capture. + if ( + symbolWithScope.symbol.getDeclarations().every((decl) => { + // Parameter declarations always start life at the beginning + // of the execution scope, so they are always safe to narrow. + if (decl.type === DeclarationType.Param) { + return true; + } + + const declCodeFlowNode = AnalyzerNodeInfo.getFlowNode(decl.node); + if (!declCodeFlowNode) { + return false; + } + + return ( + codeFlowEngine.getFlowNodeReachability( + declCodeFlowNode, + innerScopeCodeFlowNode, + /* ignoreNoReturn */ true + ) !== Reachability.Reachable + ); + }) + ) { + let typeAtStart = effectiveType; + // Viper 扩展:带类型注解的变量声明视为栈上分配,保持声明类型。 + const capturedFileInfo = AnalyzerNodeInfo.getFileInfo(node); + const isViperTypedDecl = + IsViperDialect(capturedFileInfo.executionEnvironment.dialect) && + symbolWithScope.symbol.hasTypedDeclarations(); + if (symbolWithScope.symbol.isInitiallyUnbound() && !isViperTypedDecl) { + typeAtStart = UnboundType.create(); + } + + return getFlowTypeOfReference(node, innerScopeNode, { + targetSymbolId: symbolWithScope.symbol.id, + typeAtStart: { type: typeAtStart }, + }); + } + } + } + } + + return undefined; + } + + // Validates that a TypeVar is valid in this context. If so, it clones it + // and provides a scope ID defined by its containing scope (class, function + // or type alias). If not, it emits errors indicating why the TypeVar + // cannot be used in this location. + function validateTypeVarUsage(node: ExpressionNode, type: TypeVarType, flags: EvalFlags) { + if (!TypeBase.isInstantiable(type) || isTypeAliasPlaceholder(type)) { + return type; + } + + // If the TypeVar doesn't have a scope ID, try to assign one. + if (!type.priv.scopeId) { + type = assignTypeVarScopeId(node, type, flags); + } + + // If this is a free type var, see if we need to make it into a bound type var. + if (type.priv.scopeId && !TypeVarType.isBound(type)) { + // If this is a reference to a TypeVar defined in an outer scope, + // mark it as bound. + const scopedNode = findScopedTypeVar(node, type)?.scopeNode; + + if (scopedNode) { + const enclosingSuite = ParseTreeUtils.getEnclosingClassOrFunctionSuite(node); + + if (enclosingSuite && ParseTreeUtils.isNodeContainedWithin(enclosingSuite, scopedNode)) { + if (scopedNode.nodeType !== ParseNodeType.Class || scopedNode.d.suite !== enclosingSuite) { + type = TypeVarType.cloneAsBound(type); + } + } + } + } + + // If this is a TypeVarTuple, the name refers to the packed form. It + // must be unpacked in most contexts. + if (isUnpackedTypeVarTuple(type)) { + type = TypeVarType.cloneForPacked(type); + } + + if ((flags & EvalFlags.EnforceClassTypeVarScope) !== 0 && !enforceClassTypeVarScope(node, type)) { + return UnknownType.create(); + } + + return type; + } + + function assignTypeVarScopeId(node: ExpressionNode, type: TypeVarType, flags: EvalFlags): TypeVarType { + const scopedTypeVarInfo = findScopedTypeVar(node, type); + type = scopedTypeVarInfo.type; + + if ((flags & EvalFlags.NoTypeVarWithScopeId) !== 0 && !!type.priv.scopeId) { + if (type.shared.isSynthesized || isParamSpec(type)) { + return type; + } + + // This TypeVar already has a scope ID assigned to it. See if it + // originates from type parameter syntax. If so, allow it. + if (type.shared.isTypeParamSyntax) { + return type; + } + + // If this type variable expression is used within a generic class, + // function, or type alias that uses type parameter syntax, there is + // no need to report an error here. + const typeVarScopeNode = ParseTreeUtils.getTypeVarScopeNode(node); + if ( + typeVarScopeNode && + typeVarScopeNode.d.typeParams && + !typeVarScopeNode.d.typeParams.d.params.some((t) => t.d.name === node) + ) { + return type; + } + + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarUsedByOuterScope().format({ name: type.shared.name }), + node + ); + + return type; + } + + if ((flags & EvalFlags.TypeVarGetsCurScope) !== 0) { + if (type.priv.scopeId) { + return type; + } + + if (scopedTypeVarInfo.foundInterveningClass) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarUsedByOuterScope().format({ name: type.shared.name }), + node + ); + return type; + } + + let enclosingScope = ParseTreeUtils.getEnclosingClassOrFunction(node); + + // Handle P.args and P.kwargs as a special case for inner functions. + if ( + enclosingScope && + node.parent?.nodeType === ParseNodeType.MemberAccess && + node.parent.d.leftExpr === node + ) { + const memberName = node.parent.d.member.d.value; + if (memberName === 'args' || memberName === 'kwargs') { + const outerFunctionScope = ParseTreeUtils.getEnclosingClassOrFunction(enclosingScope); + + if (outerFunctionScope?.nodeType === ParseNodeType.Function) { + enclosingScope = outerFunctionScope; + } else if (!scopedTypeVarInfo.type.priv.scopeId) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.paramSpecNotUsedByOuterScope().format({ + name: type.shared.name, + }), + node + ); + } + } + } + + if (!enclosingScope) { + fail('AssociateTypeVarsWithCurrentScope flag was set but enclosing scope not found'); + } + + // If the enclosing scope is using type parameter syntax, traditional + // type variables can't be used in this context. + if ( + enclosingScope.d.typeParams && + !enclosingScope.d.typeParams.d.params.some((param) => param.d.name.d.value === type.shared.name) + ) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeParameterNotDeclared().format({ + name: type.shared.name, + container: enclosingScope.d.name.d.value, + }), + node + ); + } + + const scopeIdToAssign = ParseTreeUtils.getScopeIdForNode(enclosingScope); + + return TypeVarType.cloneForScopeId( + type, + scopeIdToAssign, + enclosingScope.d.name.d.value, + enclosingScope.nodeType === ParseNodeType.Function ? TypeVarScopeType.Function : TypeVarScopeType.Class + ); + } + + if ((flags & EvalFlags.AllowTypeVarWithoutScopeId) === 0) { + if (type.priv.scopeId && !scopedTypeVarInfo.foundInterveningClass) { + return type; + } + + if (!type.shared.isSynthesized && (flags & EvalFlags.InstantiableType) !== 0) { + const message = isParamSpec(type) + ? LocMessage.paramSpecNotUsedByOuterScope() + : LocMessage.typeVarNotUsedByOuterScope(); + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, message.format({ name: type.shared.name }), node); + } + } + + return type; + } + + // Enforce that the type variable is scoped to the enclosing class or + // an outer class that contains the class definition. + function enforceClassTypeVarScope(node: ExpressionNode, type: TypeVarType): boolean { + const scopeId = type.priv.freeTypeVar?.priv.scopeId ?? type.priv.scopeId; + if (!scopeId) { + return true; + } + + const enclosingClass = ParseTreeUtils.getEnclosingClass(node); + if (enclosingClass) { + const liveTypeVarScopeIds = ParseTreeUtils.getTypeVarScopesForNode(enclosingClass); + if (!liveTypeVarScopeIds.includes(scopeId)) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarInvalidForMemberVariable().format({ + name: TypeVarType.getReadableName(type), + }), + node + ); + + return false; + } + } + + return true; + } + + // Determines if the type is a generic class or type alias with missing + // type arguments. If so, it fills in these type arguments with Unknown + // and optionally reports an error. + function reportMissingTypeArgs(node: ExpressionNode, type: Type, flags: EvalFlags): Type { + if ((flags & EvalFlags.NoSpecialize) !== 0) { + return type; + } + + // Is this a generic class that needs to be specialized? + if (isInstantiableClass(type)) { + if ((flags & EvalFlags.InstantiableType) !== 0 && (flags & EvalFlags.AllowMissingTypeArgs) === 0) { + if (!type.props?.typeAliasInfo && requiresTypeArgs(type)) { + if (!type.priv.typeArgs || !type.priv.isTypeArgExplicit) { + addDiagnostic( + DiagnosticRule.reportMissingTypeArgument, + LocMessage.typeArgsMissingForClass().format({ + name: type.priv.aliasName || type.shared.name, + }), + node + ); + } + } + } + + if (!type.priv.typeArgs) { + type = createSpecializedClassType(type, /* typeArgs */ undefined, flags, node)?.type; + } + } + + // Is this a generic type alias that needs to be specialized? + if ((flags & EvalFlags.InstantiableType) !== 0) { + type = specializeTypeAliasWithDefaults(type, node); + } + + return type; + } + + // Walks up the parse tree to find a function, class, or type alias + // declaration that provides the context for a type variable. + function findScopedTypeVar(node: ExpressionNode, type: TypeVarType): ScopedTypeVarResult { + let curNode: ParseNode | undefined = node; + let nestedClassCount = 0; + + assert(TypeBase.isInstantiable(type)); + + while (curNode) { + const scopeNode = ParseTreeUtils.getTypeVarScopeNode(curNode); + if (!scopeNode) { + break; + } + curNode = scopeNode; + + let typeParamsForScope: TypeVarType[] | undefined; + let scopeUsesTypeParamSyntax = false; + + if (curNode.nodeType === ParseNodeType.Class) { + const classTypeInfo = getTypeOfClass(curNode); + if (classTypeInfo && !ClassType.isPartiallyEvaluated(classTypeInfo.classType)) { + typeParamsForScope = classTypeInfo.classType.shared.typeParams; + } + + scopeUsesTypeParamSyntax = !!curNode.d.typeParams; + nestedClassCount++; + } else if (curNode.nodeType === ParseNodeType.Function) { + const functionType = getTypeOfFunctionPredecorated(curNode); + if (functionType) { + const functionDetails = functionType.shared; + typeParamsForScope = functionDetails.typeParams; + } + + scopeUsesTypeParamSyntax = !!curNode.d.typeParams; + } else if (curNode.nodeType === ParseNodeType.TypeAlias) { + scopeUsesTypeParamSyntax = !!curNode.d.typeParams; + } + + if (typeParamsForScope) { + const match = typeParamsForScope.find((typeVar) => typeVar.shared.name === type.shared.name); + + if ( + match?.priv.scopeId !== undefined && + match.priv.scopeName !== undefined && + match.priv.scopeType !== undefined + ) { + // Use the scoped version of the TypeVar rather than the (unscoped) original type. + type = TypeVarType.cloneForScopeId( + type, + match.priv.scopeId, + match.priv.scopeName, + match.priv.scopeType + ); + type.shared.declaredVariance = match.shared.declaredVariance; + return { + type, + scopeNode, + foundInterveningClass: nestedClassCount > 1 && !scopeUsesTypeParamSyntax, + }; + } + } + + curNode = curNode.parent; + } + + // See if this is part of an assignment statement that is defining a type alias. + curNode = node; + while (curNode) { + let leftType: Type | undefined; + let typeAliasNode: TypeAliasNode | undefined; + let scopeNode: TypeAliasNode | AssignmentNode | undefined; + + if (curNode.nodeType === ParseNodeType.TypeAlias) { + leftType = readTypeCache(curNode.d.name, EvalFlags.None); + typeAliasNode = curNode; + scopeNode = curNode; + } else if (curNode.nodeType === ParseNodeType.Assignment) { + leftType = readTypeCache(curNode.d.leftExpr, EvalFlags.None); + scopeNode = curNode; + } + + if (leftType && scopeNode) { + // Is this a placeholder that was temporarily written to the cache for + // purposes of resolving type aliases? + if (leftType && isTypeVar(leftType) && leftType.shared.recursiveAlias) { + // Type alias statements cannot be used with old-style type variables. + if (typeAliasNode && !type.shared.isTypeParamSyntax && !type.props?.typeAliasInfo) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeParameterNotDeclared().format({ + name: type.shared.name, + container: typeAliasNode.d.name.d.value, + }), + node + ); + } else { + // If this is a TypeAliasType call, the recursive type parameters will already + // be populated, and we need to verify that the type parameter is in the + // list of allowed type parameters. + const allowedTypeParams = leftType.shared.recursiveAlias?.typeParams; + if (allowedTypeParams) { + if (!allowedTypeParams.some((param) => param.shared.name === type.shared.name)) { + // Return the original type. + return { type, scopeNode, foundInterveningClass: false }; + } + } + } + + return { + type: TypeVarType.cloneForScopeId( + type, + leftType.shared.recursiveAlias.typeVarScopeId, + leftType.shared.recursiveAlias.name, + TypeVarScopeType.TypeAlias + ), + scopeNode, + foundInterveningClass: false, + }; + } + } + + curNode = curNode.parent; + } + + // Return the original type. + return { type, scopeNode: undefined, foundInterveningClass: false }; + } + + function getTypeOfMemberAccess(node: MemberAccessNode, flags: EvalFlags): TypeResult { + // Compute flags specifically for evaluating the left expression. + let leftExprFlags = EvalFlags.MemberAccessBaseDefaults; + leftExprFlags |= + flags & + (EvalFlags.TypeExpression | + EvalFlags.VarTypeAnnotation | + EvalFlags.ForwardRefs | + EvalFlags.NotParsed | + EvalFlags.NoTypeVarWithScopeId | + EvalFlags.TypeVarGetsCurScope); + + // Handle special casing for ParamSpec "args" and "kwargs" accesses. + if ((flags & EvalFlags.InstantiableType) !== 0) { + const memberName = node.d.member.d.value; + if (memberName === 'args' || memberName === 'kwargs') { + leftExprFlags |= EvalFlags.NoConvertSpecialForm; + } + } + const baseTypeResult = getTypeOfExpression(node.d.leftExpr, leftExprFlags); + + if (isTypeAliasPlaceholder(baseTypeResult.type)) { + return { + type: UnknownType.create(/* isIncomplete */ true), + isIncomplete: true, + }; + } + + const typeResult = getTypeOfMemberAccessWithBaseType( + node, + baseTypeResult, + { method: 'get' }, + flags | EvalFlags.NoSpecialize + ); + + if (isCodeFlowSupportedForReference(node)) { + // Before performing code flow analysis, update the cache to prevent recursion. + writeTypeCache(node, { ...typeResult, isIncomplete: true }, flags); + writeTypeCache(node.d.member, { ...typeResult, isIncomplete: true }, flags); + + // If the type is initially unbound, see if there's a parent class that + // potentially initialized the value. + let typeAtStart = typeResult.type; + let isTypeAtStartIncomplete = !!typeResult.isIncomplete; + if (isUnbound(typeAtStart)) { + const baseType = makeTopLevelTypeVarsConcrete(baseTypeResult.type); + + let classMemberInfo: ClassMember | undefined; + if (isInstantiableClass(baseType)) { + classMemberInfo = lookUpClassMember( + baseType, + node.d.member.d.value, + MemberAccessFlags.SkipOriginalClass + ); + } else if (isClassInstance(baseType)) { + classMemberInfo = lookUpObjectMember( + baseType, + node.d.member.d.value, + MemberAccessFlags.SkipOriginalClass + ); + } + + if (classMemberInfo) { + typeAtStart = getTypeOfMember(classMemberInfo); + isTypeAtStartIncomplete = false; + } + } + + // See if we can refine the type based on code flow analysis. + const codeFlowTypeResult = getFlowTypeOfReference(node, /* startNode */ undefined, { + targetSymbolId: indeterminateSymbolId, + typeAtStart: { type: typeAtStart, isIncomplete: isTypeAtStartIncomplete }, + skipConditionalNarrowing: (flags & EvalFlags.TypeExpression) !== 0, + }); + + if (codeFlowTypeResult.type) { + typeResult.type = codeFlowTypeResult.type; + } + + if (codeFlowTypeResult.isIncomplete) { + typeResult.isIncomplete = true; + } + + // Detect, report, and fill in missing type arguments if appropriate. + typeResult.type = reportMissingTypeArgs(node, typeResult.type, flags); + + // Add TypeForm details if appropriate. + typeResult.type = addTypeFormForSymbol(node, typeResult.type, flags, /* includesVarDecl */ false); + } + + if (baseTypeResult.isIncomplete) { + typeResult.isIncomplete = true; + } + + // See if we need to log an "unknown member access" diagnostic. + let skipPartialUnknownCheck = typeResult.isIncomplete; + + // Don't report an error if the type is a partially-specialized + // class being passed as an argument. This comes up frequently in + // cases where a type is passed as an argument (e.g. "defaultdict(list)"). + // It can also come up in cases like "isinstance(x, (list, dict))". + // We need to check for functions as well to handle Callable. + if ( + (isInstantiableClass(typeResult.type) && !typeResult.type.priv.includeSubclasses) || + typeResult.type.props?.specialForm + ) { + const argNode = ParseTreeUtils.getParentNodeOfType(node, ParseNodeType.Argument); + if (argNode && argNode?.parent?.nodeType === ParseNodeType.Call) { + skipPartialUnknownCheck = true; + } + } + + if (!skipPartialUnknownCheck) { + reportPossibleUnknownAssignment( + AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.reportUnknownMemberType, + DiagnosticRule.reportUnknownMemberType, + node.d.member, + typeResult.type, + node, + /* ignoreEmptyContainers */ false + ); + } + + // Cache the type information in the member name node. + writeTypeCache(node.d.member, typeResult, flags); + + return typeResult; + } + + function getTypeOfMemberAccessWithBaseType( + node: MemberAccessNode, + baseTypeResult: TypeResult, + usage: EvaluatorUsage, + flags: EvalFlags + ): TypeResult { + let baseType = transformPossibleRecursiveTypeAlias(baseTypeResult.type); + const memberName = node.d.member.d.value; + let diag = new DiagnosticAddendum(); + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + let type: Type | undefined; + let narrowedTypeForSet: Type | undefined; + let typeErrors = false; + let isIncomplete = !!baseTypeResult.isIncomplete; + let isAsymmetricAccessor: boolean | undefined; + const isRequired = false; + const isNotRequired = false; + let memberAccessDeprecationInfo: MemberAccessDeprecationInfo | undefined; + + if (usage?.setType?.isIncomplete) { + isIncomplete = true; + } + + // If the base type was incomplete and unbound, don't proceed + // because false positive errors will be generated. + if (baseTypeResult.isIncomplete && isUnbound(baseType)) { + return { type: UnknownType.create(/* isIncomplete */ true), isIncomplete: true }; + } + + if (baseType.props?.specialForm && (flags & EvalFlags.TypeExpression) === 0) { + baseType = baseType.props.specialForm; + } + + if (isParamSpec(baseType) && baseType.priv.paramSpecAccess) { + baseType = makeTopLevelTypeVarsConcrete(baseType); + } + + switch (baseType.category) { + case TypeCategory.Any: + case TypeCategory.Unknown: + case TypeCategory.Never: { + // Viper 扩展:Unknown 类型成员访问时标黄警告"对象可能未定义"。 + // 主要用于未解析的导入模块(如 import serial 失败后 serial.puts 访问)。 + if (isUnknown(baseType) && IsViperDialect(fileInfo.executionEnvironment.dialect)) { + fileInfo.diagnosticSink.addDiagnosticWithTextRange( + 'warning', + LocMessage.viperUnknownMemberAccess().format({ + memberName, + }), + node.d.member + ); + } + type = baseType; + break; + } + + case TypeCategory.Unbound: { + break; + } + + case TypeCategory.TypeVar: { + if (isParamSpec(baseType)) { + // Handle special cases for "P.args" and "P.kwargs". + if (memberName === 'args' || memberName === 'kwargs') { + const isArgs = memberName === 'args'; + const paramNode = ParseTreeUtils.getEnclosingParam(node); + const expectedCategory = isArgs ? ParamCategory.ArgsList : ParamCategory.KwargsDict; + + if (!paramNode || paramNode.d.category !== expectedCategory) { + const errorMessage = isArgs + ? LocMessage.paramSpecArgsUsage() + : LocMessage.paramSpecKwargsUsage(); + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, errorMessage, node); + type = UnknownType.create(isIncomplete); + break; + } + + type = TypeVarType.cloneForParamSpecAccess(baseType, memberName); + break; + } + + if (!isIncomplete) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.paramSpecUnknownMember().format({ name: memberName }), + node + ); + } + + type = UnknownType.create(isIncomplete); + break; + } + + // It's illegal to reference a member from a type variable. + if ((flags & EvalFlags.TypeExpression) !== 0) { + if (!isIncomplete) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarNoMember().format({ + type: printType(baseType), + name: memberName, + }), + node.d.leftExpr + ); + } + + type = UnknownType.create(isIncomplete); + break; + } + + if (baseType.shared.recursiveAlias) { + type = UnknownType.create(/* isIncomplete */ true); + isIncomplete = true; + break; + } + + if (isTypeVarTuple(baseType)) { + break; + } + + return getTypeOfMemberAccessWithBaseType( + node, + { + type: makeTopLevelTypeVarsConcrete(baseType), + bindToSelfType: TypeBase.isInstantiable(baseType) ? convertToInstance(baseType) : baseType, + isIncomplete, + }, + usage, + EvalFlags.None + ); + } + + case TypeCategory.Class: { + let typeResult: TypeResult | undefined; + + // Viper 扩展:CPtr[Struct] 指针解引用语义。 + // 当访问 CPtr[Struct] 实例的成员时,将 baseType 替换为 Struct 实例, + // 然后继续正常的成员访问路径。这模拟 C 语言的 ptr->field 语义, + // 统一覆盖 get/set/del 三种场景,避免 CPtr 类的 __getattr__/__setattr__ + // 干扰成员查找,也避免 applyAttributeAccessOverride 在赋值场景下失效。 + if ( + IsViperDialect(fileInfo.executionEnvironment.dialect) && + isCPtrType(baseType) && + baseType.priv.typeArgs && + baseType.priv.typeArgs.length > 0 + ) { + const pointeeType = baseType.priv.typeArgs[0]; + if (isClass(pointeeType)) { + baseType = TypeBase.isInstance(pointeeType) + ? pointeeType + : ClassType.cloneAsInstance(pointeeType); + } + } + + // If this is a class-like function created via NewType, treat + // it like a function for purposes of member accesses. + if ( + ClassType.isNewTypeClass(baseType) && + !baseType.priv.includeSubclasses && + prefetched?.functionClass && + isClass(prefetched.functionClass) + ) { + baseType = ClassType.cloneAsInstance(prefetched.functionClass); + } + + const enumMemberResult = getTypeOfEnumMember( + evaluatorInterface, + node, + baseType, + memberName, + isIncomplete + ); + + if (enumMemberResult) { + if (usage.method === 'get') { + typeResult = enumMemberResult; + } else { + // Is this an attempt to delete or overwrite an enum member? + if ( + isClassInstance(enumMemberResult.type) && + ClassType.isSameGenericClass(enumMemberResult.type, ClassType.cloneAsInstance(baseType)) && + enumMemberResult.type.priv.literalValue !== undefined + ) { + const diagMessage = + usage.method === 'set' ? LocMessage.enumMemberSet() : LocMessage.enumMemberDelete(); + addDiagnostic( + DiagnosticRule.reportAttributeAccessIssue, + diagMessage.format({ name: memberName }) + diag.getString(), + node.d.member, + diag.getEffectiveTextRange() ?? node.d.member + ); + } + } + } + + if (!typeResult) { + typeResult = getTypeOfBoundMember( + node.d.member, + baseType, + memberName, + usage, + diag, + (flags & EvalFlags.TypeExpression) === 0 ? undefined : MemberAccessFlags.TypeExpression, + baseTypeResult.bindToSelfType + ); + } + + if (typeResult) { + if (!typeResult.typeErrors) { + type = addConditionToType(typeResult.type, getTypeCondition(baseType), { + skipSelfCondition: true, + skipBoundTypeVars: true, + }); + } else { + typeErrors = true; + } + + if (typeResult.isAsymmetricAccessor) { + isAsymmetricAccessor = true; + } + + if (typeResult.isIncomplete) { + isIncomplete = true; + } + + if (typeResult.narrowedTypeForSet) { + narrowedTypeForSet = addConditionToType( + typeResult.narrowedTypeForSet, + getTypeCondition(baseType), + { skipSelfCondition: true, skipBoundTypeVars: true } + ); + } + + if (typeResult.memberAccessDeprecationInfo) { + memberAccessDeprecationInfo = typeResult.memberAccessDeprecationInfo; + } + } + break; + } + + case TypeCategory.Module: { + let symbol = ModuleType.getField(baseType, memberName); + + // If the symbol isn't found in the module's symbol table, + // see if it's defined in the `ModuleType` class. This is + // needed for modules that are synthesized for namespace + // packages. + if (!symbol && prefetched?.moduleTypeClass && isInstantiableClass(prefetched.moduleTypeClass)) { + symbol = ClassType.getSymbolTable(prefetched.moduleTypeClass).get(memberName); + } + + if (symbol && !symbol.isExternallyHidden()) { + if (usage.method === 'get') { + setSymbolAccessed(fileInfo, symbol, node.d.member); + } + + const typeResult = getEffectiveTypeOfSymbolForUsage( + symbol, + /* usageNode */ undefined, + /* useLastDecl */ true + ); + type = typeResult.type; + + if ((flags & EvalFlags.TypeExpression) !== 0) { + type = validateSymbolIsTypeExpression(node, type, !!typeResult.includesVariableDecl); + } + + // Add TypeForm details if appropriate. + type = addTypeFormForSymbol(node, type, flags, !!typeResult.includesVariableDecl); + + if (isTypeVar(type)) { + type = validateTypeVarUsage(node, type, flags); + } + + // If the type resolved to "unbound", treat it as "unknown" in + // the case of a module reference because if it's truly unbound, + // that error will be reported within the module and should not + // leak into other modules that import it. + if (isUnbound(type)) { + type = UnknownType.create(/* isIncomplete */ true); + } + + if (symbol.isPrivateMember()) { + addDiagnostic( + DiagnosticRule.reportPrivateUsage, + LocMessage.privateUsedOutsideOfModule().format({ + name: memberName, + }), + node.d.member + ); + } + + if (symbol.isPrivatePyTypedImport()) { + addDiagnostic( + DiagnosticRule.reportPrivateImportUsage, + LocMessage.privateImportFromPyTypedModule().format({ + name: memberName, + module: baseType.priv.moduleName, + }), + node.d.member + ); + } + } else { + // Viper 扩展:模块级 __getattr__(PEP 562)语义不存在,跳过动态属性解析。 + + // If the field was not found and the module type is marked + // such that all fields should be Any/Unknown, return that type. + if (!type && baseType.priv.notPresentFieldType) { + type = baseType.priv.notPresentFieldType; + } + + if (!type) { + if (!isIncomplete) { + addDiagnostic( + DiagnosticRule.reportAttributeAccessIssue, + LocMessage.moduleUnknownMember().format({ + memberName, + moduleName: baseType.priv.moduleName, + }), + node.d.member + ); + } + type = evaluatorOptions.evaluateUnknownImportsAsAny ? AnyType.create() : UnknownType.create(); + } + } + break; + } + + case TypeCategory.Union: { + type = mapSubtypes(baseType, (subtype) => { + if (isUnbound(subtype)) { + // Don't do anything if it's unbound. The error will already + // be reported elsewhere. + return undefined; + } + + if (isNoneInstance(subtype)) { + assert(isClassInstance(subtype)); + const typeResult = getTypeOfBoundMember(node.d.member, subtype, memberName, usage, diag); + + if (typeResult && !typeResult.typeErrors) { + type = addConditionToType(typeResult.type, getTypeCondition(baseType), { + skipBoundTypeVars: true, + }); + if (typeResult.isIncomplete) { + isIncomplete = true; + } + + return type; + } + + if (!isIncomplete) { + addDiagnostic( + DiagnosticRule.reportOptionalMemberAccess, + LocMessage.noneUnknownMember().format({ name: memberName }), + node.d.member + ); + } + + return undefined; + } + + // Viper 扩展:联合类型 Struct | CPtr 表示"结构体或指向它的指针"。 + // 在 Viper 方言下,CPtr/CVoid 子类型不参与成员访问(指针本身无字段), + // 直接跳过,只使用其他结构体子类型的结果。 + // 这模拟 C 语言中指针通过 "->"/"." 访问结构体成员的语义, + // 避免对 CPtr 报告 "Attribute is unknown" 错误。 + // 注意:必须在 getTypeOfMemberAccessWithBaseType 之前跳过, + // 否则错误诊断已在该函数内部生成。 + if ( + IsViperDialect(fileInfo.executionEnvironment.dialect) && + (isCPtrType(subtype) || isCVoidType(subtype)) + ) { + return undefined; + } + + const typeResult = getTypeOfMemberAccessWithBaseType( + node, + { + type: subtype, + isIncomplete: baseTypeResult.isIncomplete, + }, + usage, + EvalFlags.None + ); + + if (typeResult.isIncomplete) { + isIncomplete = true; + } + + if (typeResult.memberAccessDeprecationInfo) { + memberAccessDeprecationInfo = typeResult.memberAccessDeprecationInfo; + } + + if (typeResult.typeErrors) { + typeErrors = true; + } + + return typeResult.type; + }); + break; + } + + case TypeCategory.Function: + case TypeCategory.Overloaded: { + const hasSelf = isMethodType(baseType); + + if (memberName === '__self__' && hasSelf) { + // Handle "__self__" specially because MethodType defines + // it simply as "object". We can do better here. + let functionType: FunctionType | undefined; + + if (isFunction(baseType)) { + functionType = baseType; + } else { + const overloads = OverloadedType.getOverloads(baseType); + if (overloads.length > 0) { + functionType = overloads[0]; + } + } + + type = functionType?.priv.boundToType; + } else { + const altType = hasSelf ? prefetched?.methodClass : prefetched?.functionClass; + type = getTypeOfMemberAccessWithBaseType( + node, + { type: altType ? convertToInstance(altType) : UnknownType.create() }, + usage, + flags + ).type; + } + break; + } + + default: + assertNever(baseType); + } + + // If type is undefined, emit a general error message indicating that the + // member could not be accessed. + if (!type) { + const isFunctionRule = + isFunctionOrOverloaded(baseType) || + (isClassInstance(baseType) && ClassType.isBuiltIn(baseType, ['function', 'FunctionType'])); + + if (!baseTypeResult.isIncomplete) { + let diagMessage = LocMessage.memberAccess(); + if (usage.method === 'set') { + diagMessage = LocMessage.memberSet(); + } else if (usage.method === 'del') { + diagMessage = LocMessage.memberDelete(); + } + + // If there is an expected type diagnostic addendum (used for assignments), + // use that rather than the local diagnostic addendum because it will be + // more informative. + if (usage.setExpectedTypeDiag && !usage.setExpectedTypeDiag.isEmpty()) { + diag = usage.setExpectedTypeDiag; + } + + // If the class is a TypedDict, and there's a key with the same name, + // suggest that they user want to use ["key"] name instead. + if (isClass(baseType) && baseType.shared.typedDictEntries) { + const tdKey = baseType.shared.typedDictEntries.knownItems.get(memberName); + if (tdKey) { + const subDiag = new DiagnosticAddendum(); + subDiag.addMessage(LocAddendum.typedDictKeyAccess().format({ name: memberName })); + diag.addAddendum(subDiag); + } + } + + const rule = isFunctionRule + ? DiagnosticRule.reportFunctionMemberAccess + : DiagnosticRule.reportAttributeAccessIssue; + + addDiagnostic( + rule, + diagMessage.format({ name: memberName, type: printType(baseType) }) + diag.getString(), + node.d.member, + diag.getEffectiveTextRange() ?? node.d.member + ); + } + + // If this is member access on a function, use "Any" so if the + // reportFunctionMemberAccess rule is disabled, we don't trigger + // additional reportUnknownMemberType diagnostics. + type = isFunctionRule ? AnyType.create() : UnknownType.create(); + } + + if ((flags & EvalFlags.TypeExpression) === 0) { + reportUseOfTypeCheckOnly(type, node.d.member); + } + + type = convertSpecialFormToRuntimeValue(type, flags); + + return { + type, + isIncomplete, + isAsymmetricAccessor, + narrowedTypeForSet, + isRequired, + isNotRequired, + memberAccessDeprecationInfo, + typeErrors, + }; + } + + function getTypeOfClassMemberName( + errorNode: ExpressionNode | undefined, + classType: ClassType, + memberName: string, + usage: EvaluatorUsage, + diag: DiagnosticAddendum | undefined, + flags: MemberAccessFlags, + selfType?: ClassType | TypeVarType, + recursionCount = 0 + ): ClassMemberLookup | undefined { + const isAccessedThroughObject = TypeBase.isInstance(classType); + + // Always look for a member with a declared type first. + let memberInfo = lookUpClassMember(classType, memberName, flags | MemberAccessFlags.DeclaredTypesOnly); + + // If we couldn't find a symbol with a declared type, use + // a symbol with an inferred type. + if (!memberInfo) { + memberInfo = lookUpClassMember(classType, memberName, flags); + } + + if (!memberInfo) { + // No attribute of that name was found. If this is a member access + // through an object, see if there's an attribute access override + // method ("__getattr__", etc.). + if ((flags & MemberAccessFlags.SkipAttributeAccessOverride) === 0 && errorNode) { + const generalAttrType = applyAttributeAccessOverride(errorNode, classType, usage, memberName, selfType); + if (generalAttrType) { + return { + symbol: undefined, + type: generalAttrType.type, + isTypeIncomplete: false, + isDescriptorError: false, + isClassMember: false, + isClassVar: false, + isAsymmetricAccessor: !!generalAttrType.isAsymmetricAccessor, + }; + } + } + + // Report that the member could not be accessed. + diag?.addMessage(LocAddendum.memberUnknown().format({ name: memberName })); + return undefined; + } + + let type: Type | undefined; + let isTypeIncomplete = false; + let narrowedTypeForSet: Type | undefined; + + if (memberInfo.symbol.isInitVar()) { + diag?.addMessage(LocAddendum.memberIsInitVar().format({ name: memberName })); + return undefined; + } + + if (usage.method !== 'get' && errorNode) { + // If the usage indicates a 'set' or 'delete' and the access is within the + // class definition itself, use only the declared type to avoid circular + // type evaluation. + const containingClass = ParseTreeUtils.getEnclosingClass(errorNode); + if (containingClass) { + const containingClassType = getTypeOfClass(containingClass)?.classType; + if ( + containingClassType && + isInstantiableClass(containingClassType) && + ClassType.isSameGenericClass( + isAccessedThroughObject ? ClassType.cloneAsInstance(containingClassType) : containingClassType, + classType + ) + ) { + type = getDeclaredTypeOfSymbol(memberInfo.symbol)?.type; + if (type && isInstantiableClass(memberInfo.classType)) { + type = partiallySpecializeType( + type, + memberInfo.classType, + /* typeClassType */ undefined, + selfType + ); + } + + // If we're setting a class variable via a write through an object, + // this is normally considered a type violation. But it is allowed + // if the class variable is a descriptor object. In this case, we will + // clear the flag that causes an error to be generated. + if ( + usage.method === 'set' && + isEffectivelyClassVar(memberInfo.symbol, ClassType.isDataClass(containingClassType)) && + isAccessedThroughObject + ) { + const selfClass = selfType ?? memberName === '__new__' ? undefined : classType; + const typeResult = getTypeOfMemberInternal(errorNode, memberInfo, selfClass, flags); + + if (typeResult) { + if (isDescriptorInstance(typeResult.type, /* requireSetter */ true)) { + type = typeResult.type; + flags &= MemberAccessFlags.DisallowClassVarWrites; + } + } + } + + if (!type) { + type = UnknownType.create(); + } + } + } + } + + if (!type) { + let selfClass: ClassType | TypeVarType | undefined; + + if (selfType) { + selfClass = convertToInstantiable(selfType) as TypeVarType | ClassType; + } else { + // Skip this for __new__ methods because they are not bound + // to the class but rather assume the type of the cls argument. + if (memberName !== '__new__') { + selfClass = classType; + } + } + + const typeResult = getTypeOfMemberInternal(errorNode, memberInfo, selfClass, flags); + + type = typeResult?.type ?? UnknownType.create(); + if (typeResult?.isIncomplete) { + isTypeIncomplete = true; + } + } + + // Don't include variables within typed dict classes. + if (isClass(memberInfo.classType) && ClassType.isTypedDictClass(memberInfo.classType)) { + const typedDecls = memberInfo.symbol.getTypedDeclarations(); + if (typedDecls.length > 0 && typedDecls[0].type === DeclarationType.Variable) { + diag?.addMessage(LocAddendum.memberUnknown().format({ name: memberName })); + return undefined; + } + } + + if (usage.method === 'get') { + // Mark the member accessed if it's not coming from a parent class. + if ( + errorNode && + isInstantiableClass(memberInfo.classType) && + ClassType.isSameGenericClass( + memberInfo.classType, + isAccessedThroughObject ? ClassType.cloneAsInstantiable(classType) : classType + ) + ) { + setSymbolAccessed(AnalyzerNodeInfo.getFileInfo(errorNode), memberInfo.symbol, errorNode); + } + + // Special-case `__init_subclass` and `__class_getitem__` because + // these are always treated as class methods even if they're not + // decorated as such. + if (memberName === '__init_subclass__' || memberName === '__class_getitem__') { + if (isFunction(type) && !FunctionType.isClassMethod(type)) { + type = FunctionType.cloneWithNewFlags(type, type.shared.flags | FunctionTypeFlags.ClassMethod); + } + } + } + + // If the member is a descriptor object, apply the descriptor protocol + // now. If the member is an instance or class method, bind the method. + let isDescriptorError = false; + let isAsymmetricAccessor = false; + let isDescriptorApplied = false; + let memberAccessDeprecationInfo: MemberAccessDeprecationInfo | undefined; + + type = mapSubtypes( + type, + (subtype) => { + const concreteSubtype = makeTopLevelTypeVarsConcrete(subtype); + const isClassMember = !memberInfo || (memberInfo.isClassMember && !memberInfo.isSlotsMember); + let resultType: Type; + + if (isClass(concreteSubtype) && isClassMember && errorNode) { + const descResult = applyDescriptorAccessMethod( + subtype, + concreteSubtype, + memberInfo, + classType, + selfType, + flags, + errorNode, + memberName, + usage, + diag + ); + + if (descResult.isAsymmetricAccessor) { + isAsymmetricAccessor = true; + } + + if (descResult.memberAccessDeprecationInfo) { + memberAccessDeprecationInfo = descResult.memberAccessDeprecationInfo; + } + + if (descResult.typeErrors) { + isDescriptorError = true; + } + + if (descResult.isDescriptorApplied) { + isDescriptorApplied = true; + } + + resultType = descResult.type; + } else if (isFunctionOrOverloaded(concreteSubtype) && TypeBase.isInstance(concreteSubtype)) { + const typeResult = bindMethodForMemberAccess( + subtype, + concreteSubtype, + memberInfo, + classType, + selfType, + flags, + memberName, + usage, + diag, + recursionCount + ); + + resultType = typeResult.type; + if (typeResult.typeErrors) { + isDescriptorError = true; + } + } else { + resultType = subtype; + } + + // If this is a "set" or "delete" operation, we have a bit more work to do. + if (usage.method === 'get') { + return resultType; + } + + // Check for an attempt to overwrite or delete a ClassVar member from an instance. + if ( + !isDescriptorApplied && + memberInfo && + isEffectivelyClassVar(memberInfo.symbol, ClassType.isDataClass(classType)) && + (flags & MemberAccessFlags.DisallowClassVarWrites) !== 0 + ) { + diag?.addMessage(LocAddendum.memberSetClassVar().format({ name: memberName })); + isDescriptorError = true; + } + + // Check for an attempt to overwrite or delete a final member variable. + const finalVarTypeDecl = memberInfo?.symbol + .getDeclarations() + .find((decl) => isFinalVariableDeclaration(decl)); + + if ( + finalVarTypeDecl && + errorNode && + !ParseTreeUtils.isNodeContainedWithin(errorNode, finalVarTypeDecl.node) + ) { + // If a Final instance variable is declared in the class body but is + // being assigned within an __init__ method, it's allowed. + const enclosingFunctionNode = ParseTreeUtils.getEnclosingFunction(errorNode); + if ( + !enclosingFunctionNode || + enclosingFunctionNode.d.name.d.value !== '__init__' || + (finalVarTypeDecl as VariableDeclaration).inferredTypeSource !== undefined || + isInstantiableClass(classType) + ) { + diag?.addMessage(LocMessage.finalReassigned().format({ name: memberName })); + isDescriptorError = true; + } + } + + // Check for an attempt to overwrite or delete an instance variable that is + // read-only (e.g. in a named tuple). + if (memberInfo?.isInstanceMember && isClass(memberInfo.classType) && memberInfo.isReadOnly) { + diag?.addMessage(LocAddendum.readOnlyAttribute().format({ name: memberName })); + isDescriptorError = true; + } + + return resultType; + }, + { retainTypeAlias: true } + ); + + if (!isDescriptorError && usage.method === 'set' && usage.setType) { + if (errorNode && memberInfo.symbol.hasTypedDeclarations()) { + // This is an assignment to a member with a declared type. Apply + // narrowing logic based on the assigned type. Skip this for + // descriptor-based accesses. + narrowedTypeForSet = isDescriptorApplied + ? usage.setType.type + : narrowTypeBasedOnAssignment(type, usage.setType).type; + } + + // Verify that the assigned type is compatible. + if (!assignType(type, usage.setType.type, diag?.createAddendum())) { + if (!usage.setType.isIncomplete) { + diag?.addMessage( + LocAddendum.memberAssignment().format({ + type: printType(usage.setType.type), + name: memberName, + classType: printObjectTypeForClass(classType), + }) + ); + } + + // Do not narrow the type in this case. Assume the declared type. + narrowedTypeForSet = type; + isDescriptorError = true; + } + + if ( + isInstantiableClass(memberInfo.classType) && + ClassType.isDataClassFrozen(memberInfo.classType) && + isAccessedThroughObject + ) { + diag?.addMessage( + LocAddendum.dataClassFrozen().format({ + name: printType(ClassType.cloneAsInstance(memberInfo.classType)), + }) + ); + + isDescriptorError = true; + } + } + + return { + symbol: memberInfo.symbol, + type, + isTypeIncomplete, + isDescriptorError, + isClassMember: !memberInfo.isInstanceMember, + isClassVar: memberInfo.isClassVar, + classType: memberInfo.classType, + isAsymmetricAccessor, + narrowedTypeForSet, + memberAccessDeprecationInfo, + }; + } + + // Applies descriptor access methods "__get__", "__set__", or "__delete__" + // if they apply. + function applyDescriptorAccessMethod( + memberType: Type, + concreteMemberType: ClassType, + memberInfo: ClassMember | undefined, + classType: ClassType, + selfType: ClassType | TypeVarType | undefined, + flags: MemberAccessFlags, + errorNode: ExpressionNode, + memberName: string, + usage: EvaluatorUsage, + diag: DiagnosticAddendum | undefined + ): MemberAccessTypeResult { + const isAccessedThroughObject = TypeBase.isInstance(classType); + + let accessMethodName: string; + if (usage.method === 'get') { + accessMethodName = '__get__'; + } else if (usage.method === 'set') { + accessMethodName = '__set__'; + } else { + accessMethodName = '__delete__'; + } + + const subDiag = diag ? new DiagnosticAddendum() : undefined; + + const methodTypeResult = getTypeOfBoundMember( + errorNode, + concreteMemberType, + accessMethodName, + /* usage */ undefined, + subDiag, + MemberAccessFlags.SkipInstanceMembers | MemberAccessFlags.SkipAttributeAccessOverride + ); + + if (!methodTypeResult || methodTypeResult.typeErrors) { + // Provide special error messages for properties. + if (ClassType.isPropertyClass(concreteMemberType) && usage.method !== 'get') { + const message = + usage.method === 'set' ? LocAddendum.propertyMissingSetter() : LocAddendum.propertyMissingDeleter(); + diag?.addMessage(message.format({ name: memberName })); + return { type: AnyType.create(), typeErrors: true }; + } + + if (classType.shared.typeVarScopeId) { + memberType = makeTypeVarsBound(memberType, [classType.shared.typeVarScopeId]); + } + + return { type: memberType }; + } + + const methodClassType = methodTypeResult.classType; + let methodType = methodTypeResult.type; + + if (methodTypeResult.typeErrors || !methodClassType) { + if (diag && subDiag) { + diag.addAddendum(subDiag); + } + return { type: UnknownType.create(), typeErrors: true }; + } + + if (!isFunctionOrOverloaded(methodType)) { + if (isAnyOrUnknown(methodType)) { + return { type: methodType }; + } + + // TODO - emit an error for this condition. + return { type: memberType, typeErrors: true }; + } + + // Special-case logic for properties. + if ( + ClassType.isPropertyClass(concreteMemberType) && + memberInfo && + isInstantiableClass(memberInfo.classType) && + methodType + ) { + // If the property is being accessed from a protocol class (not an instance), + // flag this as an error because a property within a protocol is meant to be + // interpreted as a read-only attribute rather than a protocol, so accessing + // it directly from the class has an ambiguous meaning. + if ((flags & MemberAccessFlags.SkipInstanceMembers) !== 0 && ClassType.isProtocolClass(classType)) { + diag?.addMessage(LocAddendum.propertyAccessFromProtocolClass()); + return { type: memberType, typeErrors: true }; + } + + // Infer return types before specializing. Otherwise a generic inferred + // return type won't be properly specialized. + inferReturnTypeIfNecessary(methodType); + + // This specialization is required specifically for properties, which should be + // generic but are not defined that way. Because of this, we use type variables + // in the synthesized methods (e.g. __get__) for the property class that are + // defined in the class that declares the fget method. + let accessMethodClass: ClassType | undefined; + if (usage.method === 'get') { + accessMethodClass = concreteMemberType.priv.fgetInfo?.classType; + } else if (usage.method === 'set') { + accessMethodClass = concreteMemberType.priv.fsetInfo?.classType; + } else { + accessMethodClass = concreteMemberType.priv.fdelInfo?.classType; + } + + if (accessMethodClass) { + const constraints = new ConstraintTracker(); + accessMethodClass = selfSpecializeClass(accessMethodClass); + assignType( + ClassType.cloneAsInstance(accessMethodClass), + ClassType.cloneAsInstance(memberInfo.classType), + /* diag */ undefined, + constraints + ); + accessMethodClass = solveAndApplyConstraints(accessMethodClass, constraints) as ClassType; + + const specializedType = partiallySpecializeType( + methodType, + accessMethodClass, + getTypeClassType(), + selfType ? (convertToInstantiable(selfType) as ClassType | TypeVarType) : classType + ); + + if (isFunctionOrOverloaded(specializedType)) { + methodType = specializedType; + } + } + } + + // Determine if we're calling __set__ on an asymmetric descriptor or property. + let isAsymmetricAccessor = false; + if (usage.method === 'set' && isClass(methodClassType)) { + if (isAsymmetricDescriptorClass(methodClassType)) { + isAsymmetricAccessor = true; + } + } + + if (!methodType) { + diag?.addMessage( + LocAddendum.descriptorAccessBindingFailed().format({ + name: accessMethodName, + className: printType(convertToInstance(methodClassType)), + }) + ); + + return { + type: UnknownType.create(), + typeErrors: true, + isDescriptorApplied: true, + isAsymmetricAccessor, + }; + } + + // Simulate a call to the access method. + const argList: Arg[] = []; + + // Provide "obj" argument. + let objArgType: Type; + if (ClassType.isClassProperty(concreteMemberType)) { + // Handle "class properties" as a special case. We need to pass + // the class rather than the object instance in this case. + objArgType = isAccessedThroughObject ? ClassType.cloneAsInstantiable(classType) : classType; + } else if (isAccessedThroughObject) { + objArgType = selfType ?? ClassType.cloneAsInstance(classType); + } else { + objArgType = getNoneType(); + } + + argList.push({ + argCategory: ArgCategory.Simple, + typeResult: { type: objArgType }, + }); + + if (usage.method === 'get') { + let classArgType: Type; + if (selfType) { + classArgType = convertToInstantiable(selfType); + } else { + classArgType = isAccessedThroughObject ? ClassType.cloneAsInstantiable(classType) : classType; + } + + // Provide "owner" argument. + argList.push({ + argCategory: ArgCategory.Simple, + typeResult: { type: classArgType }, + }); + } else if (usage.method === 'set') { + // Provide "value" argument. + argList.push({ + argCategory: ArgCategory.Simple, + typeResult: { + type: usage.setType?.type ?? UnknownType.create(), + isIncomplete: !!usage.setType?.isIncomplete, + }, + }); + } + + // Suppress diagnostics for these method calls because they would be redundant. + const callResult = suppressDiagnostics( + errorNode, + () => { + return validateCallArgs( + errorNode, + argList, + { type: methodType }, + /* constraints */ undefined, + /* skipUnknownArgCheck */ true, + /* inferenceContext */ undefined + ); + }, + (suppressedDiags) => { + // If diagnostics were recorded when suppressed, add them to the + // diagnostic as messages. + if (diag) { + suppressedDiags.forEach((message) => { + diag?.addMessageMultiline(message); + }); + } + } + ); + + // Collect deprecation information associated with the member access method. + let deprecationInfo: MemberAccessDeprecationInfo | undefined; + if (callResult.overloadsUsedForCall && callResult.overloadsUsedForCall.length >= 1) { + const overloadUsed = callResult.overloadsUsedForCall[0]; + if (overloadUsed.shared.deprecatedMessage) { + deprecationInfo = { + deprecatedMessage: overloadUsed.shared.deprecatedMessage, + accessType: ClassType.isPropertyClass(concreteMemberType) ? 'property' : 'descriptor', + accessMethod: usage.method, + }; + } + } + + if (!callResult.argumentErrors) { + return { + // For set or delete, always return Any. + type: usage.method === 'get' ? callResult.returnType ?? UnknownType.create() : AnyType.create(), + isDescriptorApplied: true, + isAsymmetricAccessor, + memberAccessDeprecationInfo: deprecationInfo, + }; + } + + return { + type: UnknownType.create(), + typeErrors: true, + isDescriptorApplied: true, + isAsymmetricAccessor, + memberAccessDeprecationInfo: deprecationInfo, + }; + } + + function bindMethodForMemberAccess( + type: Type, + concreteType: FunctionType | OverloadedType, + memberInfo: ClassMember | undefined, + classType: ClassType, + selfType: ClassType | TypeVarType | undefined, + flags: MemberAccessFlags, + memberName: string, + usage: EvaluatorUsage, + diag: DiagnosticAddendum | undefined, + recursionCount = 0 + ): TypeResult { + // Check for an attempt to overwrite a final method. + if (usage.method === 'set') { + const impl = isFunction(concreteType) ? concreteType : OverloadedType.getImplementation(concreteType); + + if (impl && isFunction(impl) && FunctionType.isFinal(impl) && memberInfo && isClass(memberInfo.classType)) { + diag?.addMessage( + LocMessage.finalMethodOverride().format({ + name: memberName, + className: memberInfo.classType.shared.name, + }) + ); + + return { type: UnknownType.create(), typeErrors: true }; + } + } + + // If this function is an instance member (e.g. a lambda that was + // assigned to an instance variable), don't perform any binding. + if (TypeBase.isInstance(classType)) { + if (!memberInfo || memberInfo.isInstanceMember) { + return { type: type }; + } + } + + const boundType = bindFunctionToClassOrObject( + classType, + concreteType, + memberInfo && isInstantiableClass(memberInfo.classType) ? memberInfo.classType : undefined, + (flags & MemberAccessFlags.TreatConstructorAsClassMethod) !== 0, + selfType && isClass(selfType) ? ClassType.cloneIncludeSubclasses(selfType) : selfType, + diag, + recursionCount + ); + + return { type: boundType ?? UnknownType.create(), typeErrors: !boundType }; + } + + function isAsymmetricDescriptorClass(classType: ClassType): boolean { + // If the value has already been cached in this type, return the cached value. + if (classType.priv.isAsymmetricDescriptor !== undefined) { + return classType.priv.isAsymmetricDescriptor; + } + + let isAsymmetric = false; + + const getterSymbolResult = lookUpClassMember(classType, '__get__', MemberAccessFlags.SkipBaseClasses); + const setterSymbolResult = lookUpClassMember(classType, '__set__', MemberAccessFlags.SkipBaseClasses); + + if (!getterSymbolResult || !setterSymbolResult) { + isAsymmetric = false; + } else { + let getterType = getTypeOfMember(getterSymbolResult); + const setterType = getTypeOfMember(setterSymbolResult); + + // If this is an overload, find the appropriate overload. + if (isOverloaded(getterType)) { + const getOverloads = OverloadedType.getOverloads(getterType).filter((overload) => { + if (overload.shared.parameters.length < 2) { + return false; + } + const param1Type = FunctionType.getParamType(overload, 1); + return !isNoneInstance(param1Type); + }); + + if (getOverloads.length === 1) { + getterType = getOverloads[0]; + } else { + isAsymmetric = true; + } + } + + // If this is an overload, find the appropriate overload. + if (isOverloaded(setterType)) { + isAsymmetric = true; + } + + // If either the setter or getter is an overload (or some other non-function type), + // conservatively assume that it's not asymmetric. + if (isFunction(getterType) && isFunction(setterType)) { + // If there's no declared return type on the getter, assume it's symmetric. + if (setterType.shared.parameters.length >= 3 && getterType.shared.declaredReturnType) { + const setterValueType = FunctionType.getParamType(setterType, 2); + const getterReturnType = FunctionType.getEffectiveReturnType(getterType) ?? UnknownType.create(); + + if (!isTypeSame(setterValueType, getterReturnType)) { + isAsymmetric = true; + } + } + } + } + + // Cache the value for next time. + classType.priv.isAsymmetricDescriptor = isAsymmetric; + return isAsymmetric; + } + + function isClassWithAsymmetricAttributeAccessor(classType: ClassType): boolean { + // If the value has already been cached in this type, return the cached value. + if (classType.priv.isAsymmetricAttributeAccessor !== undefined) { + return classType.priv.isAsymmetricAttributeAccessor; + } + + let isAsymmetric = false; + + const getterSymbolResult = lookUpClassMember(classType, '__getattr__', MemberAccessFlags.SkipBaseClasses); + const setterSymbolResult = lookUpClassMember(classType, '__setattr__', MemberAccessFlags.SkipBaseClasses); + + if (!getterSymbolResult || !setterSymbolResult) { + isAsymmetric = false; + } else { + const getterType = getEffectiveTypeOfSymbol(getterSymbolResult.symbol); + const setterType = getEffectiveTypeOfSymbol(setterSymbolResult.symbol); + + // If either the setter or getter is an overload (or some other non-function type), + // conservatively assume that it's not asymmetric. + if (isFunction(getterType) && isFunction(setterType)) { + // If there's no declared return type on the getter, assume it's symmetric. + if (setterType.shared.parameters.length >= 3 && getterType.shared.declaredReturnType) { + const setterValueType = FunctionType.getParamType(setterType, 2); + const getterReturnType = FunctionType.getEffectiveReturnType(getterType) ?? UnknownType.create(); + + if (!isTypeSame(setterValueType, getterReturnType)) { + isAsymmetric = true; + } + } + } + } + + // Cache the value for next time. + classType.priv.isAsymmetricAttributeAccessor = isAsymmetric; + return isAsymmetric; + } + + // Applies the __getattr__, __setattr__ or __delattr__ method if present. + // If it's not applicable, returns undefined. + function applyAttributeAccessOverride( + errorNode: ExpressionNode, + classType: ClassType, + usage: EvaluatorUsage, + memberName: string, + selfType?: ClassType | TypeVarType + ): MemberAccessTypeResult | undefined { + // Viper is an AOT static language without Python's instance-level + // descriptor protocol (__getattr__/__getattribute__/__setattr__). + if (IsViperDialect(AnalyzerNodeInfo.getFileInfo(errorNode).executionEnvironment.dialect)) { + // Viper 扩展:CPtr[Struct] 指针解引用语义。 + // 当访问 CPtr[Struct] 实例的成员时,将属性访问转发到 Struct 类型。 + // 模拟 C 语言的 ptr->field 语义。 + if (isCPtrType(classType) && classType.priv.typeArgs && classType.priv.typeArgs.length > 0) { + const pointeeType = classType.priv.typeArgs[0]; + if (isClass(pointeeType)) { + // 确保是实例形式以便访问实例属性 + const pointeeInstance = TypeBase.isInstance(pointeeType) + ? pointeeType + : ClassType.cloneAsInstance(pointeeType); + const memberInfo = lookUpClassMember( + pointeeInstance, + memberName, + MemberAccessFlags.Default + ); + if (memberInfo) { + const memberType = getEffectiveTypeOfSymbol(memberInfo.symbol); + return { + type: memberType, + isAsymmetricAccessor: false, + }; + } + } + } + return undefined; + } + + const getAttributeAccessMember = (name: string) => { + return getTypeOfBoundMember( + errorNode, + classType, + name, + /* usage */ undefined, + /* diag */ undefined, + MemberAccessFlags.SkipInstanceMembers | + MemberAccessFlags.SkipObjectBaseClass | + MemberAccessFlags.SkipTypeBaseClass | + MemberAccessFlags.SkipAttributeAccessOverride, + selfType + )?.type; + }; + + let accessMemberType: Type | undefined; + if (usage.method === 'get') { + accessMemberType = getAttributeAccessMember('__getattribute__') ?? getAttributeAccessMember('__getattr__'); + } else if (usage.method === 'set') { + accessMemberType = getAttributeAccessMember('__setattr__'); + } else { + assert(usage.method === 'del'); + accessMemberType = getAttributeAccessMember('__delattr__'); + } + + if (!accessMemberType) { + return undefined; + } + + const argList: Arg[] = []; + + // Provide "name" argument. + argList.push({ + argCategory: ArgCategory.Simple, + typeResult: { + type: + prefetched?.strClass && isInstantiableClass(prefetched.strClass) + ? ClassType.cloneWithLiteral(ClassType.cloneAsInstance(prefetched.strClass), memberName) + : AnyType.create(), + }, + }); + + if (usage.method === 'set') { + // Provide "value" argument. + argList.push({ + argCategory: ArgCategory.Simple, + typeResult: { + type: usage.setType?.type ?? UnknownType.create(), + isIncomplete: !!usage.setType?.isIncomplete, + }, + }); + } + + if (!isFunctionOrOverloaded(accessMemberType)) { + if (isAnyOrUnknown(accessMemberType)) { + return { type: accessMemberType }; + } + + // TODO - emit an error for this condition. + return undefined; + } + + const callResult = validateCallArgs( + errorNode, + argList, + { type: accessMemberType }, + /* constraints */ undefined, + /* skipUnknownArgCheck */ true, + /* inferenceContext */ undefined + ); + + let isAsymmetricAccessor = false; + if (usage.method === 'set') { + isAsymmetricAccessor = isClassWithAsymmetricAttributeAccessor(classType); + } + + return { + type: callResult.returnType ?? UnknownType.create(), + typeErrors: callResult.argumentErrors, + isAsymmetricAccessor, + }; + } + + function getTypeOfIndex(node: IndexNode, flags = EvalFlags.None): TypeResult { + const baseTypeResult = getTypeOfExpression(node.d.leftExpr, flags | EvalFlags.IndexBaseDefaults); + + // If this is meant to be a type and the base expression is a string expression, + // emit an error because this is an illegal annotation form and will generate a + // runtime exception. + if (flags & EvalFlags.InstantiableType) { + if (node.d.leftExpr.nodeType === ParseNodeType.StringList) { + addDiagnostic(DiagnosticRule.reportIndexIssue, LocMessage.stringNotSubscriptable(), node.d.leftExpr); + } + } + + // Viper 扩展:AOT 静态语言无 PEP 585 运行时 subscript 限制,跳过检查。 + + const indexTypeResult = getTypeOfIndexWithBaseType(node, baseTypeResult, { method: 'get' }, flags); + + if (isCodeFlowSupportedForReference(node)) { + // We limit type narrowing for index expressions to built-in types that are + // known to have symmetric __getitem__ and __setitem__ methods (i.e. the value + // passed to __setitem__ is the same type as the value returned by __getitem__). + let baseTypeSupportsIndexNarrowing = !isAny(baseTypeResult.type); + mapSubtypesExpandTypeVars(baseTypeResult.type, /* options */ undefined, (subtype) => { + if ( + !isClassInstance(subtype) || + !(ClassType.isBuiltIn(subtype) || ClassType.isTypedDictClass(subtype)) + ) { + baseTypeSupportsIndexNarrowing = false; + } + + return undefined; + }); + + if (baseTypeSupportsIndexNarrowing) { + // Before performing code flow analysis, update the cache to prevent recursion. + writeTypeCache(node, { ...indexTypeResult, isIncomplete: true }, flags); + + // See if we can refine the type based on code flow analysis. + const codeFlowTypeResult = getFlowTypeOfReference(node, /* startNode */ undefined, { + targetSymbolId: indeterminateSymbolId, + typeAtStart: { + type: indexTypeResult.type, + isIncomplete: !!baseTypeResult.isIncomplete || !!indexTypeResult.isIncomplete, + }, + skipConditionalNarrowing: (flags & EvalFlags.TypeExpression) !== 0, + }); + + if (codeFlowTypeResult.type) { + indexTypeResult.type = codeFlowTypeResult.type; + } + + if (codeFlowTypeResult.isIncomplete) { + indexTypeResult.isIncomplete = true; + } + } + } + + if (baseTypeResult.isIncomplete) { + indexTypeResult.isIncomplete = true; + } + + return indexTypeResult; + } + + // If the list of type parameters includes a TypeVarTuple, we may need to adjust + // the supplied type arguments to map to the type parameter list. + function adjustTypeArgsForTypeVarTuple( + typeArgs: TypeResultWithNode[], + typeParams: TypeVarType[], + errorNode: ExpressionNode + ): TypeResultWithNode[] { + const variadicIndex = typeParams.findIndex((param) => isTypeVarTuple(param)); + + // Is there a *tuple[T, ...] somewhere in the type arguments that we can expand if needed? + let srcUnboundedTupleType: Type | undefined; + const findUnboundedTupleIndex = (startArgIndex: number) => { + return typeArgs.findIndex((arg, index) => { + if (index < startArgIndex) { + return false; + } + if ( + isUnpackedClass(arg.type) && + arg.type.priv.tupleTypeArgs && + arg.type.priv.tupleTypeArgs.length === 1 && + arg.type.priv.tupleTypeArgs[0].isUnbounded + ) { + srcUnboundedTupleType = arg.type.priv.tupleTypeArgs[0].type; + return true; + } + + return false; + }); + }; + let srcUnboundedTupleIndex = findUnboundedTupleIndex(0); + + // Allow only one unpacked tuple that maps to a TypeVarTuple. + if (srcUnboundedTupleIndex >= 0) { + const secondUnboundedTupleIndex = findUnboundedTupleIndex(srcUnboundedTupleIndex + 1); + if (secondUnboundedTupleIndex >= 0) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.variadicTypeArgsTooMany(), + typeArgs[secondUnboundedTupleIndex].node + ); + } + } + + if ( + srcUnboundedTupleType && + srcUnboundedTupleIndex >= 0 && + variadicIndex >= 0 && + typeArgs.length < typeParams.length + ) { + // "Smear" the tuple type across type argument slots prior to the TypeVarTuple. + while (variadicIndex > srcUnboundedTupleIndex) { + typeArgs = [ + ...typeArgs.slice(0, srcUnboundedTupleIndex), + { node: typeArgs[srcUnboundedTupleIndex].node, type: srcUnboundedTupleType }, + ...typeArgs.slice(srcUnboundedTupleIndex), + ]; + srcUnboundedTupleIndex++; + } + + // "Smear" the tuple type across type argument slots following the TypeVarTuple. + while (typeArgs.length < typeParams.length) { + typeArgs = [ + ...typeArgs.slice(0, srcUnboundedTupleIndex + 1), + { node: typeArgs[srcUnboundedTupleIndex].node, type: srcUnboundedTupleType }, + ...typeArgs.slice(srcUnboundedTupleIndex + 1), + ]; + } + } + + // Do we need to adjust the type arguments to map to a variadic type + // param somewhere in the list? + if (variadicIndex >= 0) { + const variadicTypeVar = typeParams[variadicIndex]; + + // If the type param list ends with a ParamSpec with a default value, + // we can ignore it for purposes of finding type args that map to the + // TypeVarTuple. + let typeParamCount = typeParams.length; + while (typeParamCount > 0) { + const lastTypeParam = typeParams[typeParamCount - 1]; + if (!isParamSpec(lastTypeParam) || !lastTypeParam.shared.isDefaultExplicit) { + break; + } + + typeParamCount--; + } + + if (variadicIndex < typeArgs.length) { + // If there are typeArg lists at the end, these should map to ParamSpecs rather + // than the TypeVarTuple, so exclude them. + let variadicEndIndex = variadicIndex + 1 + typeArgs.length - typeParamCount; + while (variadicEndIndex > variadicIndex) { + if (!typeArgs[variadicEndIndex - 1].typeList) { + break; + } + variadicEndIndex--; + } + const variadicTypeResults = typeArgs.slice(variadicIndex, variadicEndIndex); + + // If the type args consist of a lone TypeVarTuple, don't wrap it in a tuple. + if (variadicTypeResults.length === 1 && isTypeVarTuple(variadicTypeResults[0].type)) { + validateTypeVarTupleIsUnpacked(variadicTypeResults[0].type, variadicTypeResults[0].node); + } else { + variadicTypeResults.forEach((arg, index) => { + validateTypeArg(arg, { + allowEmptyTuple: index === 0, + allowTypeVarTuple: true, + allowUnpackedTuples: true, + }); + }); + + const variadicTypes: TupleTypeArg[] = []; + if (variadicTypeResults.length !== 1 || !variadicTypeResults[0].isEmptyTupleShorthand) { + variadicTypeResults.forEach((typeResult) => { + if (isUnpackedClass(typeResult.type) && typeResult.type.priv.tupleTypeArgs) { + appendArray(variadicTypes, typeResult.type.priv.tupleTypeArgs); + } else { + variadicTypes.push({ + type: convertToInstance(typeResult.type), + isUnbounded: false, + }); + } + }); + } + + const tupleObject = makeTupleObject(evaluatorInterface, variadicTypes, /* isUnpacked */ true); + + typeArgs = [ + ...typeArgs.slice(0, variadicIndex), + { node: typeArgs[variadicIndex].node, type: tupleObject }, + ...typeArgs.slice(variadicEndIndex, typeArgs.length), + ]; + } + } else if (!variadicTypeVar.shared.isDefaultExplicit) { + // Add an empty tuple that maps to the TypeVarTuple type parameter. + typeArgs.push({ + node: errorNode, + type: makeTupleObject(evaluatorInterface, [], /* isUnpacked */ true), + }); + } + } + + return typeArgs; + } + + // If the variadic type variable is not unpacked, report an error. + function validateTypeVarTupleIsUnpacked(type: TypeVarTupleType, node: ParseNode) { + if (!type.priv.isUnpacked) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.unpackedTypeVarTupleExpected().format({ + name1: type.shared.name, + name2: type.shared.name, + }), + node + ); + return false; + } + + return true; + } + + // If the type is a generic type alias that is not specialized, provides + // default type arguments for the type alias. It optionally logs diagnostics + // for missing type arguments. + function specializeTypeAliasWithDefaults(type: Type, errorNode: ExpressionNode | undefined) { + // Is this a type alias? + const aliasInfo = type.props?.typeAliasInfo; + if (!aliasInfo) { + return type; + } + + // Is this a generic type alias that needs specializing? + if (!aliasInfo.shared.typeParams || aliasInfo.shared.typeParams.length === 0 || aliasInfo.typeArgs) { + return type; + } + + let reportDiag = false; + const defaultTypeArgs: Type[] = []; + const constraints = new ConstraintTracker(); + + aliasInfo.shared.typeParams.forEach((param) => { + if (!param.shared.isDefaultExplicit) { + reportDiag = true; + } + + let defaultType: Type; + if (param.shared.isDefaultExplicit || isParamSpec(param)) { + defaultType = solveAndApplyConstraints(param, constraints, { + replaceUnsolved: { + scopeIds: [aliasInfo.shared.typeVarScopeId], + tupleClassType: getTupleClassType(), + }, + }); + } else if (isTypeVarTuple(param) && prefetched?.tupleClass && isInstantiableClass(prefetched.tupleClass)) { + defaultType = makeTupleObject( + evaluatorInterface, + [{ type: UnknownType.create(), isUnbounded: true }], + /* isUnpacked */ true + ); + } else { + defaultType = UnknownType.create(); + } + + defaultTypeArgs.push(defaultType); + constraints.setBounds(param, defaultType); + }); + + if (reportDiag && errorNode) { + addDiagnostic( + DiagnosticRule.reportMissingTypeArgument, + LocMessage.typeArgsMissingForAlias().format({ + name: aliasInfo.shared.name, + }), + errorNode + ); + } + + type = TypeBase.cloneForTypeAlias( + solveAndApplyConstraints(type, constraints, { + replaceUnsolved: { + scopeIds: [aliasInfo.shared.typeVarScopeId], + tupleClassType: getTupleClassType(), + }, + }), + { ...aliasInfo, typeArgs: defaultTypeArgs } + ); + + return type; + } + + // Handles index expressions that are providing type arguments for a + // generic type alias. + function createSpecializedTypeAlias( + node: IndexNode, + baseType: Type, + flags: EvalFlags + ): TypeResultWithNode | undefined { + let aliasInfo = baseType.props?.typeAliasInfo; + let aliasBaseType = baseType; + + if (!aliasInfo && baseType.props?.typeForm) { + aliasInfo = baseType.props.typeForm?.props?.typeAliasInfo; + aliasBaseType = convertToInstantiable(baseType.props.typeForm); + } + + if (!aliasInfo?.shared.typeParams || (aliasInfo.shared.typeParams.length === 0 && aliasInfo.typeArgs)) { + return undefined; + } + + // If this is not instantiable, then the index expression isn't a specialization. + if (!TypeBase.isInstantiable(aliasBaseType)) { + return undefined; + } + + // If this is already specialized, the index expression isn't a specialization. + if (aliasInfo.typeArgs) { + return undefined; + } + + inferVarianceForTypeAlias(baseType); + + const typeParams = aliasInfo.shared.typeParams; + let typeArgs: TypeResultWithNode[] | undefined; + typeArgs = adjustTypeArgsForTypeVarTuple(getTypeArgs(node, flags), typeParams, node); + let reportedError = false; + + typeArgs = transformTypeArgsForParamSpec(typeParams, typeArgs, node); + if (!typeArgs) { + typeArgs = []; + reportedError = true; + } + + let minTypeArgCount = typeParams.length; + const firstDefaultParamIndex = typeParams.findIndex((param) => !!param.shared.isDefaultExplicit); + if (firstDefaultParamIndex >= 0) { + minTypeArgCount = firstDefaultParamIndex; + } + + if (typeArgs.length > typeParams.length) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.typeArgsTooMany().format({ + name: printType(aliasBaseType), + expected: typeParams.length, + received: typeArgs.length, + }), + typeArgs[typeParams.length].node + ); + reportedError = true; + } else if (typeArgs.length < minTypeArgCount) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.typeArgsTooFew().format({ + name: printType(aliasBaseType), + expected: typeParams.length, + received: typeArgs.length, + }), + node.d.items[node.d.items.length - 1] + ); + reportedError = true; + } + + // Handle the mypy_extensions.FlexibleAlias type specially. + if ( + isInstantiableClass(aliasBaseType) && + aliasBaseType.shared.fullName === 'mypy_extensions.FlexibleAlias' && + typeArgs.length >= 1 + ) { + return { node, type: typeArgs[0].type }; + } + + const constraints = new ConstraintTracker(); + const diag = new DiagnosticAddendum(); + + typeParams.forEach((param, index) => { + if (isParamSpec(param) && index < typeArgs.length) { + const typeArgType = typeArgs[index].type; + const typeList = typeArgs[index].typeList; + + if (typeList) { + const functionType = FunctionType.createSynthesizedInstance('', FunctionTypeFlags.ParamSpecValue); + typeList.forEach((paramTypeResult, paramIndex) => { + let paramType = paramTypeResult.type; + + if (!validateTypeArg(paramTypeResult)) { + paramType = UnknownType.create(); + } + + FunctionType.addParam( + functionType, + FunctionParam.create( + ParamCategory.Simple, + convertToInstance(paramType), + FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared, + `__p${paramIndex}` + ) + ); + }); + + if (typeList.length > 0) { + FunctionType.addPositionOnlyParamSeparator(functionType); + } + + assignTypeVar( + evaluatorInterface, + param, + functionType, + diag, + constraints, + AssignTypeFlags.RetainLiteralsForTypeVar + ); + } else if (isParamSpec(typeArgType)) { + assignTypeVar( + evaluatorInterface, + param, + convertToInstance(typeArgType), + diag, + constraints, + AssignTypeFlags.RetainLiteralsForTypeVar + ); + } else if (isInstantiableClass(typeArgType) && ClassType.isBuiltIn(typeArgType, 'Concatenate')) { + const concatTypeArgs = typeArgType.priv.typeArgs; + const functionType = FunctionType.createInstance('', '', '', FunctionTypeFlags.None); + + if (concatTypeArgs && concatTypeArgs.length > 0) { + concatTypeArgs.forEach((typeArg, index) => { + if (index === concatTypeArgs.length - 1) { + FunctionType.addPositionOnlyParamSeparator(functionType); + + if (isParamSpec(typeArg)) { + FunctionType.addParamSpecVariadics(functionType, typeArg); + } else if (isEllipsisType(typeArg)) { + FunctionType.addDefaultParams(functionType); + functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm; + } + } else { + FunctionType.addParam( + functionType, + FunctionParam.create( + ParamCategory.Simple, + typeArg, + FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared, + `__p${index}` + ) + ); + } + }); + } + + assignTypeVar( + evaluatorInterface, + param, + functionType, + diag, + constraints, + AssignTypeFlags.RetainLiteralsForTypeVar + ); + } else if (isEllipsisType(typeArgType)) { + const functionType = FunctionType.createSynthesizedInstance( + '', + FunctionTypeFlags.ParamSpecValue | FunctionTypeFlags.GradualCallableForm + ); + FunctionType.addDefaultParams(functionType); + assignTypeVar(evaluatorInterface, param, functionType, diag, constraints); + } else { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.typeArgListExpected(), + typeArgs[index].node + ); + reportedError = true; + } + } else { + if (index < typeArgs.length && typeArgs[index].typeList) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.typeArgListNotAllowed(), + typeArgs[index].node + ); + reportedError = true; + } + + let typeArgType: Type; + if (index < typeArgs.length) { + typeArgType = convertToInstance(typeArgs[index].type); + } else if (param.shared.isDefaultExplicit) { + typeArgType = solveAndApplyConstraints(param, constraints, { + replaceUnsolved: { + scopeIds: [aliasInfo.shared.typeVarScopeId], + tupleClassType: getTupleClassType(), + }, + }); + } else { + typeArgType = UnknownType.create(); + } + + if ((flags & EvalFlags.EnforceVarianceConsistency) !== 0) { + const usageVariances = inferVarianceForTypeAlias(aliasBaseType); + if (usageVariances && index < usageVariances.length) { + const usageVariance = usageVariances[index]; + + if (!isVarianceOfTypeArgCompatible(typeArgType, usageVariance)) { + const messageDiag = diag.createAddendum(); + messageDiag.addMessage( + LocAddendum.varianceMismatchForTypeAlias().format({ + typeVarName: printType(typeArgType), + typeAliasParam: printType(typeParams[index]), + }) + ); + messageDiag.addTextRange(typeArgs[index].node); + } + } + } + + if (isUnpacked(typeArgType) && !isTypeVarTuple(param)) { + const messageDiag = diag.createAddendum(); + messageDiag.addMessage(LocMessage.unpackedArgInTypeArgument()); + messageDiag.addTextRange(typeArgs[index].node); + typeArgType = UnknownType.create(); + } + + assignTypeVar( + evaluatorInterface, + param, + typeArgType, + diag, + constraints, + AssignTypeFlags.RetainLiteralsForTypeVar + ); + } + }); + + if (!diag.isEmpty()) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.typeNotSpecializable().format({ type: printType(aliasBaseType) }) + diag.getString(), + node, + diag.getEffectiveTextRange() ?? node + ); + reportedError = true; + } + + const solutionSet = solveConstraints(evaluatorInterface, constraints).getMainSolutionSet(); + const aliasTypeArgs: Type[] = []; + + aliasInfo.shared.typeParams?.forEach((typeParam) => { + let typeVarType = solutionSet.getType(typeParam); + + // Fill in any unsolved type arguments with unknown. + if (!typeVarType) { + typeVarType = getUnknownForTypeVar(typeParam, getTupleClassType()); + constraints.setBounds(typeParam, typeVarType); + } + + aliasTypeArgs.push(typeVarType); + }); + + let type = TypeBase.cloneForTypeAlias(solveAndApplyConstraints(aliasBaseType, constraints), { + ...aliasInfo, + typeArgs: aliasTypeArgs, + }); + + type = TypeBase.cloneWithTypeForm(type, reportedError ? undefined : convertToInstance(type)); + + if (baseType.props?.typeAliasInfo) { + return { type, node }; + } + + return { type: TypeBase.cloneWithTypeForm(baseType, convertToInstance(type)), node }; + } + + function getTypeOfIndexWithBaseType( + node: IndexNode, + baseTypeResult: TypeResult, + usage: EvaluatorUsage, + flags: EvalFlags + ): TypeResult { + // Handle the case where we're specializing a generic type alias. + const typeAliasResult = createSpecializedTypeAlias(node, baseTypeResult.type, flags); + if (typeAliasResult) { + return typeAliasResult; + } + + // Handle the case where Never or NoReturn are being specialized. + if (isNever(baseTypeResult.type) && baseTypeResult.type.props?.specialForm) { + // Swap in the special form type, which is the Never or NoReturn class. + baseTypeResult = { ...baseTypeResult, type: baseTypeResult.type.props.specialForm }; + } + + // Handle the case where a TypeAliasType symbol is being specialized + // in a value expression. + if ( + isClassInstance(baseTypeResult.type) && + ClassType.isBuiltIn(baseTypeResult.type, 'TypeAliasType') && + baseTypeResult.type.props?.typeForm + ) { + const typeAliasInfo = baseTypeResult.type.props.typeForm.props?.typeAliasInfo; + if (typeAliasInfo && typeAliasInfo.shared.typeParams) { + const origTypeAlias = TypeBase.cloneForTypeAlias( + convertToInstantiable(baseTypeResult.type.props.typeForm), + { ...typeAliasInfo, typeArgs: undefined } + ); + const typeFormType = createSpecializedTypeAlias(node, origTypeAlias, flags); + if (typeFormType) { + return { + type: TypeBase.cloneWithTypeForm(baseTypeResult.type, convertToInstance(typeFormType.type)), + }; + } + } + } + + if (isTypeVar(baseTypeResult.type) && isTypeAliasPlaceholder(baseTypeResult.type)) { + const typeArgTypes = getTypeArgs(node, flags).map((t) => convertToInstance(t.type)); + const type = TypeBase.cloneForTypeAlias(baseTypeResult.type, { + shared: baseTypeResult.type.shared.recursiveAlias!, + typeArgs: typeArgTypes, + }); + return { type }; + } + + let isIncomplete = baseTypeResult.isIncomplete; + let isRequired = false; + let isNotRequired = false; + let isReadOnly = false; + + // Viper 扩展:TypeSet 类型组合的下标访问语义。 + // Viper 中 `|` 表示类型组合(如 CConst | str = const char*), + // 对 TypeSet 中的修饰类型(CConst/CStatic/CPtr/CVoid/CDefine/CArray)跳过下标访问, + // 让 combineTypes 只合并非修饰类型的结果(如 str/bytes 的下标返回 int/CChar)。 + const viperIndexFileInfo = AnalyzerNodeInfo.getFileInfo(node); + const isViperTypeSetIndex = + IsViperDialect(viperIndexFileInfo.executionEnvironment.dialect) && + isUnion(baseTypeResult.type) && + baseTypeResult.type.priv.subtypes.some( + (subtype) => + isClassInstance(subtype) && + (isCConstType(subtype) || + isCStaticType(subtype) || + isCPtrType(subtype) || + isCVoidType(subtype) || + isCDefineType(subtype) || + isCArrayType(subtype)) + ); + + const type = mapSubtypesExpandTypeVars( + baseTypeResult.type, + /* options */ undefined, + (concreteSubtype, unexpandedSubtype) => { + const selfType = isTypeVar(unexpandedSubtype) ? unexpandedSubtype : undefined; + + // Viper TypeSet:修饰类型跳过下标访问,避免产生 Unknown 污染结果。 + if ( + isViperTypeSetIndex && + isClassInstance(concreteSubtype) && + (isCConstType(concreteSubtype) || + isCStaticType(concreteSubtype) || + isCPtrType(concreteSubtype) || + isCVoidType(concreteSubtype) || + isCDefineType(concreteSubtype) || + isCArrayType(concreteSubtype)) + ) { + return undefined; + } + + if (isAnyOrUnknown(concreteSubtype)) { + if ((flags & EvalFlags.TypeExpression) !== 0) { + // If we are expecting a type annotation here, assume that + // the subscripts are type arguments and evaluate them + // accordingly. + getTypeArgs(node, flags); + } + + return concreteSubtype; + } + + if (flags & EvalFlags.InstantiableType) { + if (isTypeVar(unexpandedSubtype)) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarNotSubscriptable().format({ + type: printType(unexpandedSubtype), + }), + node.d.leftExpr + ); + + // Evaluate the index expressions as though they are type arguments for error-reporting. + getTypeArgs(node, flags); + + return UnknownType.create(); + } + } + + if (isInstantiableClass(concreteSubtype)) { + // See if the class has a custom metaclass that supports __getitem__, etc. + if ( + concreteSubtype.shared.effectiveMetaclass && + isInstantiableClass(concreteSubtype.shared.effectiveMetaclass) && + !ClassType.isBuiltIn(concreteSubtype.shared.effectiveMetaclass, ['type', '_InitVarMeta']) && + (flags & EvalFlags.InstantiableType) === 0 + ) { + const itemMethodType = getBoundMagicMethod( + concreteSubtype, + getIndexAccessMagicMethodName(usage), + /* selfType */ undefined, + node.d.leftExpr + ); + + if ((flags & EvalFlags.TypeExpression) !== 0) { + // If the class doesn't derive from Generic, a type argument should not be allowed. + addDiagnostic( + DiagnosticRule.reportInvalidTypeArguments, + LocMessage.typeArgsExpectingNone().format({ + name: printType(ClassType.cloneAsInstance(concreteSubtype)), + }), + node + ); + } + + if (itemMethodType) { + return getTypeOfIndexedObjectOrClass(node, concreteSubtype, selfType, usage).type; + } + } + + // Setting the value of an indexed class will always result + // in an exception. + if (usage.method === 'set') { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.genericClassAssigned(), + node.d.leftExpr + ); + } else if (usage.method === 'del') { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.genericClassDeleted(), + node.d.leftExpr + ); + } + + if (ClassType.isSpecialBuiltIn(concreteSubtype, 'Literal')) { + // Special-case Literal types. + return createLiteralType(concreteSubtype, node, flags); + } + + if (ClassType.isBuiltIn(concreteSubtype, 'InitVar')) { + // Special-case InitVar, used in dataclasses. + const typeArgs = getTypeArgs(node, flags); + + if ((flags & EvalFlags.TypeExpression) !== 0) { + if ((flags & EvalFlags.VarTypeAnnotation) === 0) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.initVarNotAllowed(), + node.d.leftExpr + ); + } + } + + if (typeArgs.length === 1) { + return typeArgs[0].type; + } else { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.typeArgsMismatchOne().format({ received: typeArgs.length }), + node.d.leftExpr + ); + + return UnknownType.create(); + } + } + + if (ClassType.isEnumClass(concreteSubtype)) { + // Special-case Enum types. + // TODO - validate that there's only one index entry + // that is a str type. + // TODO - validate that literal strings are referencing + // a known enum member. + return ClassType.cloneAsInstance(concreteSubtype); + } + + const isAnnotatedClass = + isInstantiableClass(concreteSubtype) && ClassType.isBuiltIn(concreteSubtype, 'Annotated'); + const hasCustomClassGetItem = + isInstantiableClass(concreteSubtype) && ClassType.hasCustomClassGetItem(concreteSubtype); + const isGenericClass = + concreteSubtype.shared.typeParams?.length > 0 || + ClassType.isSpecialBuiltIn(concreteSubtype) || + ClassType.isBuiltIn(concreteSubtype, 'type') || + ClassType.isPartiallyEvaluated(concreteSubtype); + const isFinalAnnotation = + isInstantiableClass(concreteSubtype) && ClassType.isBuiltIn(concreteSubtype, 'Final'); + const isClassVarAnnotation = + isInstantiableClass(concreteSubtype) && ClassType.isBuiltIn(concreteSubtype, 'ClassVar'); + + // This feature is currently experimental. + const supportsTypedDictTypeArg = + AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.enableExperimentalFeatures && + ClassType.isBuiltIn(concreteSubtype, 'TypedDict'); + + let typeArgs = getTypeArgs(node, flags, { + isAnnotatedClass, + hasCustomClassGetItem: hasCustomClassGetItem || !isGenericClass, + isFinalAnnotation, + isClassVarAnnotation, + supportsTypedDictTypeArg, + }); + + if (!isAnnotatedClass) { + typeArgs = adjustTypeArgsForTypeVarTuple(typeArgs, concreteSubtype.shared.typeParams, node); + } + + // If this is a custom __class_getitem__, there's no need to specialize the class. + // Just return it as is. + if (hasCustomClassGetItem) { + return concreteSubtype; + } + + if (concreteSubtype.priv.typeArgs) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeArguments, + LocMessage.classAlreadySpecialized().format({ + type: printType(convertToInstance(concreteSubtype), { expandTypeAlias: true }), + }), + node.d.leftExpr + ); + return concreteSubtype; + } + + const result = createSpecializedClassType(concreteSubtype, typeArgs, flags, node); + if (result.isRequired) { + isRequired = true; + } else if (result.isNotRequired) { + isNotRequired = true; + } + + if (result.isReadOnly) { + isReadOnly = true; + } + + return result.type; + } + + if (isNoneInstance(concreteSubtype)) { + if (!isIncomplete) { + addDiagnostic( + DiagnosticRule.reportOptionalSubscript, + LocMessage.noneNotSubscriptable(), + node.d.leftExpr + ); + } + + return UnknownType.create(); + } + + if (isClassInstance(concreteSubtype)) { + // Viper TypeSet:str/bytes 在类型组合中表示 C 字符串指针(i8*), + // 下标访问 a[i] 应返回 t.CChar(int 别名),而非 str/bytes。 + if ( + isViperTypeSetIndex && + ClassType.isBuiltIn(concreteSubtype, ['str', 'bytes']) + ) { + // 构造带 CChar 别名信息的 int 类型(保留 t.CChar 显示信息) + const ccharType = getViperCCharType(node); + if (ccharType) { + return ccharType; + } + return concreteSubtype; + } + // Viper TypeSet:非修饰基本类型(int/float/bool 等)在类型组合中 + // 表示指针指向的目标类型(如 CConst | int | CPtr = const int*), + // 下标访问 ptr[i] 应返回该类型本身(解引用),不报 __getitem__ 错误。 + if ( + isViperTypeSetIndex && + ClassType.isBuiltIn(concreteSubtype, [ + 'int', 'float', 'bool', 'complex', 'object', + ]) + ) { + return concreteSubtype; + } // Viper 扩展:str/bytes 表示 C 字符串指针(i8* / i8*)。 // 无论读写,下标访问 a[i] 都是指针解引用操作(C 语义): // - 读取 a[i] 返回 t.CChar(i8 字节),而非 Python str.__getitem__ 的 str @@ -8425,190 +8425,190 @@ export function createTypeEvaluator( } return concreteSubtype; } - const typeResult = getTypeOfIndexedObjectOrClass(node, concreteSubtype, selfType, usage); - if (typeResult.isIncomplete) { - isIncomplete = true; - } - return typeResult.type; - } - - if (isNever(concreteSubtype)) { - return NeverType.createNever(); - } - - if (isUnbound(concreteSubtype)) { - return UnknownType.create(); - } - - if (!isIncomplete) { - addDiagnostic( - DiagnosticRule.reportIndexIssue, - LocMessage.typeNotSubscriptable().format({ type: printType(concreteSubtype) }), - node.d.leftExpr - ); - } - - return UnknownType.create(); - } - ); - - // In case we didn't walk the list items above, do so now. - // If we have, this information will be cached. - if (!baseTypeResult.isIncomplete) { - node.d.items.forEach((item) => { - if (!isTypeCached(item.d.valueExpr)) { - getTypeOfExpression(item.d.valueExpr, flags & EvalFlags.ForwardRefs); - } - }); - } - - return { type, isIncomplete, isReadOnly, isRequired, isNotRequired }; - } - - // Determines the effective variance of the type parameters for a generic - // type alias. Normally, variance is not important for type aliases, but - // it can be important in cases where the type alias is used to specify - // a base class in a class definition. - function inferVarianceForTypeAlias(type: Type): Variance[] | undefined { - const aliasInfo = type.props?.typeAliasInfo; - - // If this isn't a generic type alias, there's nothing to do. - if (!aliasInfo || !aliasInfo.shared.typeParams) { - return undefined; - } - - // Is the computed variance info already cached? - if (aliasInfo.shared.computedVariance) { - return aliasInfo.shared.computedVariance; - } - - const typeParams = aliasInfo.shared.typeParams; - - // Start with all of the usage variances unknown. - const usageVariances: Variance[] = typeParams.map(() => Variance.Unknown); - - // Prepopulate the cached value for the type alias to handle - // recursive type aliases. - aliasInfo.shared.computedVariance = usageVariances; - - // Traverse the type alias type definition and adjust the usage - // variances accordingly. - updateUsageVariancesRecursive(type, typeParams, usageVariances, Variance.Covariant); - - return usageVariances; - } - - // Looks at uses of the type parameters within the type and adjusts the - // variances accordingly. For example, if the type is `Mapping[T1, T2]`, - // then T1 will be set to invariant and T2 will be set to covariant. - function updateUsageVariancesRecursive( - type: Type, - typeAliasTypeParams: TypeVarType[], - usageVariances: Variance[], - varianceContext: Variance, - pendingTypes: Type[] = [], - recursionCount = 0 - ) { - if (recursionCount > maxTypeRecursionCount) { - return; - } - - const transformedType = transformPossibleRecursiveTypeAlias(type); - const isRecursiveTypeAlias = transformedType !== type; - - // If this is a recursive type alias, see if we've already recursed - // seen it once before in the recursion stack. If so, don't recurse - // further. - if (isRecursiveTypeAlias) { - const pendingOverlaps = pendingTypes.filter((pendingType) => isTypeSame(pendingType, type)); - if (pendingOverlaps.length > 1) { - return; - } - - pendingTypes.push(type); - } - - recursionCount++; - - // Define a helper function that performs the actual usage variant update. - function updateUsageVarianceForType(type: Type, variance: Variance) { - doForEachSubtype(type, (subtype) => { - const typeParamIndex = typeAliasTypeParams.findIndex((param) => isTypeSame(param, subtype)); - if (typeParamIndex >= 0) { - usageVariances[typeParamIndex] = combineVariances(usageVariances[typeParamIndex], variance); - } else { - updateUsageVariancesRecursive( - subtype, - typeAliasTypeParams, - usageVariances, - variance, - pendingTypes, - recursionCount - ); - } - }); - } - - doForEachSubtype(transformedType, (subtype) => { - if (subtype.category === TypeCategory.Function) { - subtype.shared.parameters.forEach((param, index) => { - const paramType = FunctionType.getParamType(subtype, index); - updateUsageVarianceForType(paramType, invertVariance(varianceContext)); - }); - - const returnType = FunctionType.getEffectiveReturnType(subtype); - if (returnType) { - updateUsageVarianceForType(returnType, varianceContext); - } - } else if (subtype.category === TypeCategory.Class) { - if (subtype.priv.typeArgs) { - // If the class includes type parameters that uses auto variance, - // compute the calculated variance. - inferVarianceForClass(subtype); - - // Is the class specialized using any type arguments that correspond to - // the type alias' type parameters? - subtype.priv.typeArgs.forEach((typeArg, classParamIndex) => { - if (isTupleClass(subtype)) { - updateUsageVarianceForType(typeArg, varianceContext); - } else if (classParamIndex < subtype.shared.typeParams.length) { - const classTypeParam = subtype.shared.typeParams[classParamIndex]; - if (isUnpackedClass(typeArg) && typeArg.priv.tupleTypeArgs) { - typeArg.priv.tupleTypeArgs.forEach((tupleTypeArg) => { - updateUsageVarianceForType(tupleTypeArg.type, Variance.Invariant); - }); - } else { - const effectiveVariance = - classTypeParam.priv.computedVariance ?? classTypeParam.shared.declaredVariance; - updateUsageVarianceForType( - typeArg, - varianceContext === Variance.Contravariant - ? invertVariance(effectiveVariance) - : effectiveVariance - ); - } - } - }); - } - } - }); - - if (isRecursiveTypeAlias) { - pendingTypes.pop(); - } - } - - function getIndexAccessMagicMethodName(usage: EvaluatorUsage): string { - if (usage.method === 'get') { - return '__getitem__'; - } else if (usage.method === 'set') { - return '__setitem__'; - } else { - assert(usage.method === 'del'); - return '__delitem__'; - } - } - + const typeResult = getTypeOfIndexedObjectOrClass(node, concreteSubtype, selfType, usage); + if (typeResult.isIncomplete) { + isIncomplete = true; + } + return typeResult.type; + } + + if (isNever(concreteSubtype)) { + return NeverType.createNever(); + } + + if (isUnbound(concreteSubtype)) { + return UnknownType.create(); + } + + if (!isIncomplete) { + addDiagnostic( + DiagnosticRule.reportIndexIssue, + LocMessage.typeNotSubscriptable().format({ type: printType(concreteSubtype) }), + node.d.leftExpr + ); + } + + return UnknownType.create(); + } + ); + + // In case we didn't walk the list items above, do so now. + // If we have, this information will be cached. + if (!baseTypeResult.isIncomplete) { + node.d.items.forEach((item) => { + if (!isTypeCached(item.d.valueExpr)) { + getTypeOfExpression(item.d.valueExpr, flags & EvalFlags.ForwardRefs); + } + }); + } + + return { type, isIncomplete, isReadOnly, isRequired, isNotRequired }; + } + + // Determines the effective variance of the type parameters for a generic + // type alias. Normally, variance is not important for type aliases, but + // it can be important in cases where the type alias is used to specify + // a base class in a class definition. + function inferVarianceForTypeAlias(type: Type): Variance[] | undefined { + const aliasInfo = type.props?.typeAliasInfo; + + // If this isn't a generic type alias, there's nothing to do. + if (!aliasInfo || !aliasInfo.shared.typeParams) { + return undefined; + } + + // Is the computed variance info already cached? + if (aliasInfo.shared.computedVariance) { + return aliasInfo.shared.computedVariance; + } + + const typeParams = aliasInfo.shared.typeParams; + + // Start with all of the usage variances unknown. + const usageVariances: Variance[] = typeParams.map(() => Variance.Unknown); + + // Prepopulate the cached value for the type alias to handle + // recursive type aliases. + aliasInfo.shared.computedVariance = usageVariances; + + // Traverse the type alias type definition and adjust the usage + // variances accordingly. + updateUsageVariancesRecursive(type, typeParams, usageVariances, Variance.Covariant); + + return usageVariances; + } + + // Looks at uses of the type parameters within the type and adjusts the + // variances accordingly. For example, if the type is `Mapping[T1, T2]`, + // then T1 will be set to invariant and T2 will be set to covariant. + function updateUsageVariancesRecursive( + type: Type, + typeAliasTypeParams: TypeVarType[], + usageVariances: Variance[], + varianceContext: Variance, + pendingTypes: Type[] = [], + recursionCount = 0 + ) { + if (recursionCount > maxTypeRecursionCount) { + return; + } + + const transformedType = transformPossibleRecursiveTypeAlias(type); + const isRecursiveTypeAlias = transformedType !== type; + + // If this is a recursive type alias, see if we've already recursed + // seen it once before in the recursion stack. If so, don't recurse + // further. + if (isRecursiveTypeAlias) { + const pendingOverlaps = pendingTypes.filter((pendingType) => isTypeSame(pendingType, type)); + if (pendingOverlaps.length > 1) { + return; + } + + pendingTypes.push(type); + } + + recursionCount++; + + // Define a helper function that performs the actual usage variant update. + function updateUsageVarianceForType(type: Type, variance: Variance) { + doForEachSubtype(type, (subtype) => { + const typeParamIndex = typeAliasTypeParams.findIndex((param) => isTypeSame(param, subtype)); + if (typeParamIndex >= 0) { + usageVariances[typeParamIndex] = combineVariances(usageVariances[typeParamIndex], variance); + } else { + updateUsageVariancesRecursive( + subtype, + typeAliasTypeParams, + usageVariances, + variance, + pendingTypes, + recursionCount + ); + } + }); + } + + doForEachSubtype(transformedType, (subtype) => { + if (subtype.category === TypeCategory.Function) { + subtype.shared.parameters.forEach((param, index) => { + const paramType = FunctionType.getParamType(subtype, index); + updateUsageVarianceForType(paramType, invertVariance(varianceContext)); + }); + + const returnType = FunctionType.getEffectiveReturnType(subtype); + if (returnType) { + updateUsageVarianceForType(returnType, varianceContext); + } + } else if (subtype.category === TypeCategory.Class) { + if (subtype.priv.typeArgs) { + // If the class includes type parameters that uses auto variance, + // compute the calculated variance. + inferVarianceForClass(subtype); + + // Is the class specialized using any type arguments that correspond to + // the type alias' type parameters? + subtype.priv.typeArgs.forEach((typeArg, classParamIndex) => { + if (isTupleClass(subtype)) { + updateUsageVarianceForType(typeArg, varianceContext); + } else if (classParamIndex < subtype.shared.typeParams.length) { + const classTypeParam = subtype.shared.typeParams[classParamIndex]; + if (isUnpackedClass(typeArg) && typeArg.priv.tupleTypeArgs) { + typeArg.priv.tupleTypeArgs.forEach((tupleTypeArg) => { + updateUsageVarianceForType(tupleTypeArg.type, Variance.Invariant); + }); + } else { + const effectiveVariance = + classTypeParam.priv.computedVariance ?? classTypeParam.shared.declaredVariance; + updateUsageVarianceForType( + typeArg, + varianceContext === Variance.Contravariant + ? invertVariance(effectiveVariance) + : effectiveVariance + ); + } + } + }); + } + } + }); + + if (isRecursiveTypeAlias) { + pendingTypes.pop(); + } + } + + function getIndexAccessMagicMethodName(usage: EvaluatorUsage): string { + if (usage.method === 'get') { + return '__getitem__'; + } else if (usage.method === 'set') { + return '__setitem__'; + } else { + assert(usage.method === 'del'); + return '__delitem__'; + } + } + // Viper 扩展:从 t 模块获取 CPtr 类型的实例类型。 // Viper 中 `T | t.CPtr` 表示指向 T 的指针(如 `MemBuddy | t.CPtr`), // CPtr 是裸指针类型的基类。用于构造 `__new__` 无返回注解时的默认返回类型。 @@ -8640,17422 +8640,17473 @@ export function createTypeEvaluator( return undefined; } - // Viper 扩展:构造带 t.CChar 类型别名信息的 int 类型(表示 C 的 char 类型)。 - // Viper 中 str/bytes 是 i8* 指针,下标访问/迭代/字符运算返回 t.CChar(int 别名)。 - // 优先从 t 模块查找 CChar 类型别名以保留显示信息;找不到时回退为 - // 构造带 typeAliasInfo 的 int 类型。查找失败返回 undefined。 - function getViperCCharType(node: ExpressionNode): Type | undefined { - const tSymbolWithScope = lookUpSymbolRecursive(node, 't', /* honorCodeFlow */ true); - if (tSymbolWithScope) { - const tEffectiveType = getEffectiveTypeOfSymbolForUsage( - tSymbolWithScope.symbol, - /* usageNode */ undefined, - /* useLastDecl */ true - ).type; - if (tEffectiveType && isModule(tEffectiveType)) { - const ccharSymbol = ModuleType.getField(tEffectiveType, 'CChar'); - if (ccharSymbol) { - // 优先使用声明类型(保留 typeAliasInfo) - const ccharDeclType = getDeclaredTypeOfSymbol(ccharSymbol)?.type; - if (ccharDeclType) { - return convertToInstance(ccharDeclType); - } - // 回退到有效类型 - const ccharType = getEffectiveTypeOfSymbolForUsage( - ccharSymbol, - /* usageNode */ undefined, - /* useLastDecl */ true - ).type; - if (ccharType) { - return convertToInstance(ccharType); - } - } - } - } - // 回退:构造带 CChar 别名信息的 int 类型 - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - const intType = getBuiltInObject(node, 'int'); - const ccharAliasInfo: TypeAliasInfo = { - shared: { - name: 'CChar', - fullName: 't.CChar', - moduleName: 't', - fileUri: fileInfo.fileUri, - typeVarScopeId: '', - isTypeAliasType: false, - typeParams: undefined, - computedVariance: undefined, - }, - typeArgs: undefined, - }; - return TypeBase.cloneForTypeAlias(intType, ccharAliasInfo); - } - - function getTypeOfIndexedObjectOrClass( - node: IndexNode, - baseType: ClassType, - selfType: ClassType | TypeVarType | undefined, - usage: EvaluatorUsage - ): TypeResult { - // Handle index operations for TypedDict classes specially. - if (isClassInstance(baseType) && ClassType.isTypedDictClass(baseType)) { - const typeFromTypedDict = getTypeOfIndexedTypedDict(evaluatorInterface, node, baseType, usage); - if (typeFromTypedDict) { - return typeFromTypedDict; - } - } - - const magicMethodName = getIndexAccessMagicMethodName(usage); - - // Viper 扩展:CPtr[T] 指针下标解引用语义。 - // 当对 CPtr[T] 实例执行下标访问 ptr[i] 时(模拟 C 语言的 ptr[i] = *(ptr + i)): - // 1. 如果 T 本身有 __getitem__/__setitem__/__delitem__,优先调用 T 的方法 - // (例如 T 是 CArray,ptr[i] 应转发到 CArray.__getitem__) - // 2. 否则,指针下标直接返回 T 类型(解引用语义) - // 这与成员访问(getTypeOfMemberAccessWithBaseType 第 5963 行)的 CPtr 解引用逻辑对应。 - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - if ( - IsViperDialect(fileInfo.executionEnvironment.dialect) && - isCPtrType(baseType) && - baseType.priv.typeArgs && - baseType.priv.typeArgs.length > 0 - ) { - const pointeeType = baseType.priv.typeArgs[0]; - if (isClass(pointeeType)) { - // T 可能是类对象(如 CPtr[Scheduler])或类实例,统一转为实例 - const pointeeInstance = TypeBase.isInstance(pointeeType) - ? pointeeType - : ClassType.cloneAsInstance(pointeeType); - // 检查 T 是否有自己的 item access magic method - const pointeeMethodType = getBoundMagicMethod( - pointeeInstance, - magicMethodName, - selfType, - node.d.leftExpr - ); - if (pointeeMethodType) { - // T 有自己的 __getitem__ 等,转发给 T(将 baseType 替换为 T 实例) - baseType = pointeeInstance; - } else { - // T 没有 __getitem__ 等,指针下标返回 T 类型(解引用) - return { type: pointeeInstance }; - } - } - } - - const itemMethodType = getBoundMagicMethod(baseType, magicMethodName, selfType, node.d.leftExpr); - - if (!itemMethodType) { - // Viper 扩展:类型组合(TypeSet)语义下的静默跳过。 - // Viper 中 `|` 表示类型组合(如 CConst | str = const char*), - // 而非 Python 的联合类型(任选其一)。修饰类型(CConst/CStatic/CPtr/ - // CVoid/CDefine/CArray)本身不提供 __getitem__ 等方法,但联合类型中 - // 其他子类型(如 str)会提供。此处对修饰类型静默返回 Unknown, - // 不报错,让 mapSubtypesExpandTypeVars 处理其他子类型时获得正确方法。 - if ( - IsViperDialect(fileInfo.executionEnvironment.dialect) && - (isCConstType(baseType) || isCStaticType(baseType) || isCPtrType(baseType) || isCVoidType(baseType) || - isCDefineType(baseType) || isCArrayType(baseType)) - ) { - return { type: UnknownType.create() }; - } - addDiagnostic( - DiagnosticRule.reportIndexIssue, - LocMessage.methodNotDefinedOnType().format({ - name: magicMethodName, - type: printType(baseType), - }), - node.d.leftExpr - ); - return { type: UnknownType.create() }; - } - - // Handle the special case where the object is a tuple and - // the index is a constant number (integer) or a slice with integer - // start and end values. In these cases, we can determine - // the exact type by indexing into the tuple type array. - if ( - node.d.items.length === 1 && - !node.d.trailingComma && - !node.d.items[0].d.name && - node.d.items[0].d.argCategory === ArgCategory.Simple && - isClassInstance(baseType) - ) { - const index0Expr = node.d.items[0].d.valueExpr; - const valueType = getTypeOfExpression(index0Expr).type; - - if ( - isClassInstance(valueType) && - ClassType.isBuiltIn(valueType, 'int') && - isLiteralType(valueType) && - typeof valueType.priv.literalValue === 'number' - ) { - const indexValue = valueType.priv.literalValue; - const tupleType = getSpecializedTupleType(baseType); - - if (tupleType && tupleType.priv.tupleTypeArgs) { - if (isTupleIndexUnambiguous(tupleType, indexValue)) { - if (indexValue >= 0 && indexValue < tupleType.priv.tupleTypeArgs.length) { - return { type: tupleType.priv.tupleTypeArgs[indexValue].type }; - } else if (indexValue < 0 && tupleType.priv.tupleTypeArgs.length + indexValue >= 0) { - return { - type: tupleType.priv.tupleTypeArgs[tupleType.priv.tupleTypeArgs.length + indexValue] - .type, - }; - } - } - } - } else if (isClassInstance(valueType) && ClassType.isBuiltIn(valueType, 'slice')) { - const tupleType = getSpecializedTupleType(baseType); - - if (tupleType && index0Expr.nodeType === ParseNodeType.Slice) { - const slicedTupleType = getSlicedTupleType(evaluatorInterface, tupleType, index0Expr); - if (slicedTupleType) { - return { type: slicedTupleType }; - } - } - } - } - - const positionalArgs = node.d.items.filter((item) => item.d.argCategory === ArgCategory.Simple); - const unpackedListArgs = node.d.items.filter((item) => item.d.argCategory === ArgCategory.UnpackedList); - - let positionalIndexType: Type; - let isPositionalIndexTypeIncomplete = false; - - if (positionalArgs.length === 1 && unpackedListArgs.length === 0 && !node.d.trailingComma) { - // Handle the common case where there is a single positional argument. - const typeResult = getTypeOfExpression(positionalArgs[0].d.valueExpr); - positionalIndexType = typeResult.type; - if (typeResult.isIncomplete) { - isPositionalIndexTypeIncomplete = true; - } - } else { - // Package up all of the positionals into a tuple. - const tupleTypeArgs: TupleTypeArg[] = []; - - const getDeterministicTupleEntries = (type: Type): TupleTypeArg[] | undefined => { - let aggregatedArgs: TupleTypeArg[] | undefined; - let isDeterministic = true; - - doForEachSubtype(type, (subtype) => { - if (!isDeterministic) { - return; - } - - const tupleType = getSpecializedTupleType(subtype); - const tupleTypeArgs = tupleType?.priv.tupleTypeArgs; - - if ( - !tupleTypeArgs || - tupleTypeArgs.some((entry) => entry.isUnbounded || isTypeVarTuple(entry.type)) - ) { - isDeterministic = false; - return; - } - - if (!aggregatedArgs) { - aggregatedArgs = tupleTypeArgs.map((entry) => ({ type: entry.type, isUnbounded: false })); - return; - } - - if (aggregatedArgs.length !== tupleTypeArgs.length) { - isDeterministic = false; - return; - } - - for (let i = 0; i < aggregatedArgs.length; i++) { - aggregatedArgs[i] = { - type: combineTypes([aggregatedArgs[i].type, tupleTypeArgs[i].type]), - isUnbounded: false, - }; - } - }); - - if (!isDeterministic || !aggregatedArgs) { - return undefined; - } - - return aggregatedArgs; - }; - - node.d.items.forEach((arg) => { - if (arg.d.argCategory === ArgCategory.Simple) { - const typeResult = getTypeOfExpression(arg.d.valueExpr); - tupleTypeArgs.push({ type: typeResult.type, isUnbounded: false }); - if (typeResult.isIncomplete) { - isPositionalIndexTypeIncomplete = true; - } - return; - } - - if (arg.d.argCategory === ArgCategory.UnpackedList) { - const typeResult = getTypeOfExpression(arg.d.valueExpr); - if (typeResult.isIncomplete) { - isPositionalIndexTypeIncomplete = true; - } - - const deterministicEntries = getDeterministicTupleEntries(typeResult.type); - if (deterministicEntries) { - appendArray(tupleTypeArgs, deterministicEntries); - return; - } - - const iterableType = - getTypeOfIterator(typeResult, /* isAsync */ false, arg.d.valueExpr)?.type ?? - UnknownType.create(); - tupleTypeArgs.push({ type: iterableType, isUnbounded: true }); - } - }); - - const unboundedCount = tupleTypeArgs.filter((typeArg) => typeArg.isUnbounded).length; - if (unboundedCount > 1) { - const firstUnboundedIndex = tupleTypeArgs.findIndex((typeArg) => typeArg.isUnbounded); - const removedEntries = tupleTypeArgs.splice(firstUnboundedIndex); - tupleTypeArgs.push({ - type: combineTypes(removedEntries.map((entry) => entry.type)), - isUnbounded: true, - }); - } - - positionalIndexType = makeTupleObject(evaluatorInterface, tupleTypeArgs); - } - - const argList: Arg[] = [ - { - argCategory: ArgCategory.Simple, - typeResult: { type: positionalIndexType, isIncomplete: isPositionalIndexTypeIncomplete }, - }, - ]; - - if (usage.method === 'set') { - let setType = usage.setType?.type ?? AnyType.create(); - - // Expand constrained type variables. - if (isTypeVar(setType) && TypeVarType.hasConstraints(setType)) { - const conditionFilter = isClassInstance(baseType) ? baseType.props?.condition : undefined; - setType = makeTopLevelTypeVarsConcrete( - setType, - /* makeParamSpecsConcrete */ undefined, - conditionFilter - ); - } - - argList.push({ - argCategory: ArgCategory.Simple, - typeResult: { - type: setType, - isIncomplete: !!usage.setType?.isIncomplete, - }, - }); - } - - const callResult = validateCallArgs( - node, - argList, - { type: itemMethodType }, - /* constraints */ undefined, - /* skipUnknownArgCheck */ true, - /* inferenceContext */ undefined - ); - - return { - type: callResult.returnType ?? UnknownType.create(), - isIncomplete: !!callResult.isTypeIncomplete, - }; - } - - function getTypeArgs(node: IndexNode, flags: EvalFlags, options?: GetTypeArgsOptions): TypeResultWithNode[] { - const typeArgs: TypeResultWithNode[] = []; - let adjFlags = flags | EvalFlags.NoConvertSpecialForm; - adjFlags &= ~EvalFlags.TypeFormArg; - - const allowFinalClassVar = () => { - // If the annotation is a variable within the body of a dataclass, a - // Final is allowed with a ClassVar annotation. In all other cases, - // it's disallowed. - const enclosingClassNode = ParseTreeUtils.getEnclosingClass(node, /* stopeAtFunction */ true); - if (enclosingClassNode) { - const classTypeInfo = getTypeOfClass(enclosingClassNode); - if (classTypeInfo && ClassType.isDataClass(classTypeInfo.classType)) { - return true; - } - } - return false; - }; - - if (options?.isFinalAnnotation) { - adjFlags |= EvalFlags.NoFinal; - - if (!allowFinalClassVar()) { - adjFlags |= EvalFlags.NoClassVar; - } - } else if (options?.isClassVarAnnotation) { - adjFlags |= EvalFlags.NoClassVar; - - if (!allowFinalClassVar()) { - adjFlags |= EvalFlags.NoFinal; - } - } else { - adjFlags &= ~( - EvalFlags.NoSpecialize | - EvalFlags.NoParamSpec | - EvalFlags.NoTypeVarTuple | - EvalFlags.AllowRequired | - EvalFlags.EnforceVarianceConsistency - ); - - if (!options?.isAnnotatedClass) { - adjFlags |= EvalFlags.NoClassVar | EvalFlags.NoFinal; - } - - adjFlags |= EvalFlags.AllowUnpackedTuple | EvalFlags.AllowConcatenate; - } - - // Create a local function that validates a single type argument. - const getTypeArgTypeResult = (expr: ExpressionNode, argIndex: number) => { - let typeResult: TypeResultWithNode; - - // If it's a custom __class_getitem__, none of the arguments should be - // treated as types. - if (options?.hasCustomClassGetItem) { - adjFlags = - EvalFlags.NoParamSpec | EvalFlags.NoTypeVarTuple | EvalFlags.NoSpecialize | EvalFlags.NoClassVar; - typeResult = { - ...getTypeOfExpression(expr, adjFlags), - node: expr, - }; - } else if (options?.isAnnotatedClass && argIndex > 0) { - // If it's an Annotated[a, b, c], only the first index should be - // treated as a type. The others can be regular (non-type) objects. - adjFlags = - EvalFlags.NoParamSpec | EvalFlags.NoTypeVarTuple | EvalFlags.NoSpecialize | EvalFlags.NoClassVar; - if (isAnnotationEvaluationPostponed(AnalyzerNodeInfo.getFileInfo(node))) { - adjFlags |= EvalFlags.ForwardRefs; - } - - typeResult = { - ...getTypeOfExpression(expr, adjFlags), - node: expr, - }; - } else { - typeResult = getTypeArg(expr, adjFlags, !!options?.supportsTypedDictTypeArg && argIndex === 0); - } - - return typeResult; - }; - - // A tuple is treated the same as a list of items in the index. - if ( - node.d.items.length === 1 && - !node.d.trailingComma && - !node.d.items[0].d.name && - node.d.items[0].d.valueExpr.nodeType === ParseNodeType.Tuple - ) { - node.d.items[0].d.valueExpr.d.items.forEach((item, index) => { - typeArgs.push(getTypeArgTypeResult(item, index)); - }); - - // Set the node's type so it isn't reevaluated later. - setTypeResultForNode(node.d.items[0].d.valueExpr, { type: UnknownType.create() }); - - return typeArgs; - } - - node.d.items.forEach((arg, index) => { - const typeResult = getTypeArgTypeResult(arg.d.valueExpr, index); - - if (arg.d.argCategory !== ArgCategory.Simple) { - if (arg.d.argCategory === ArgCategory.UnpackedList) { - if (!options?.isAnnotatedClass || index === 0) { - const unpackedType = applyUnpackToTupleLike(typeResult.type); - - if (unpackedType) { - typeResult.type = unpackedType; - } else { - if ((flags & EvalFlags.TypeExpression) !== 0) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.unpackNotAllowed(), - arg.d.valueExpr - ); - typeResult.typeErrors = true; - } else { - typeResult.type = UnknownType.create(); - } - } - } - } - } - - if (arg.d.name) { - if ((flags & EvalFlags.TypeExpression) !== 0) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.keywordArgInTypeArgument(), - arg.d.valueExpr - ); - typeResult.typeErrors = true; - } else { - typeResult.type = UnknownType.create(); - } - } - - if ( - arg.d.valueExpr.nodeType !== ParseNodeType.Error || - arg.d.valueExpr.d.category !== ErrorExpressionCategory.MissingIndexOrSlice - ) { - typeArgs.push(typeResult); - } - }); - - return typeArgs; - } - - function applyUnpackToTupleLike(type: Type): Type | undefined { - if (isTypeVarTuple(type)) { - if (!type.priv.isUnpacked) { - return TypeVarType.cloneForUnpacked(type); - } - - return undefined; - } - - if (isParamSpec(type)) { - return undefined; - } - - // Is this a TypeVar that has a tuple upper bound? - if (isTypeVar(type)) { - const upperBound = type.shared.boundType; - - if (upperBound && isClassInstance(upperBound) && isTupleClass(upperBound)) { - return TypeVarType.cloneForUnpacked(type); - } - - return undefined; - } - - if (isInstantiableClass(type) && !type.priv.includeSubclasses) { - if (isTupleClass(type)) { - return ClassType.cloneForUnpacked(type); - } - } - - return undefined; - } - - function getTypeArg(node: ExpressionNode, flags: EvalFlags, supportsDictExpression: boolean): TypeResultWithNode { - let typeResult: TypeResultWithNode; - - let adjustedFlags = - flags | EvalFlags.InstantiableType | EvalFlags.ConvertEllipsisToAny | EvalFlags.StrLiteralAsType; - - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - if (fileInfo.isStubFile) { - adjustedFlags |= EvalFlags.ForwardRefs; - } - - if (node.nodeType === ParseNodeType.List) { - typeResult = { - type: UnknownType.create(), - typeList: node.d.items.map((entry) => { - return { ...getTypeOfExpression(entry, adjustedFlags), node: entry }; - }), - node, - }; - - // Set the node's type so it isn't reevaluated later. - setTypeResultForNode(node, { type: UnknownType.create() }); - } else if (node.nodeType === ParseNodeType.Dictionary && supportsDictExpression) { - const inlinedTypeDict = - prefetched?.typedDictClass && isInstantiableClass(prefetched.typedDictClass) - ? createTypedDictTypeInlined(evaluatorInterface, node, prefetched.typedDictClass) - : undefined; - const keyTypeFallback = - prefetched?.strClass && isInstantiableClass(prefetched.strClass) - ? prefetched.strClass - : UnknownType.create(); - - typeResult = { - type: keyTypeFallback, - inlinedTypeDict, - node, - }; - } else { - typeResult = { ...getTypeOfExpression(node, adjustedFlags), node }; - - if (node.nodeType === ParseNodeType.Dictionary) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.dictInAnnotation(), node); - } - - if ((flags & EvalFlags.NoClassVar) !== 0) { - // "ClassVar" is not allowed as a type argument. - if (isClass(typeResult.type) && ClassType.isBuiltIn(typeResult.type, 'ClassVar')) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.classVarNotAllowed(), node); - } - } - } - - return typeResult; - } - - function buildTupleTypesList( - entryTypeResults: TypeResult[], - stripLiterals: boolean, - convertModule: boolean - ): TupleTypeArg[] { - const entryTypes: TupleTypeArg[] = []; - - for (const typeResult of entryTypeResults) { - let possibleUnpackedTuple: Type | undefined; - if (typeResult.unpackedType) { - possibleUnpackedTuple = typeResult.unpackedType; - } else if (isUnpacked(typeResult.type)) { - possibleUnpackedTuple = typeResult.type; - } - - // Is this an unpacked tuple? If so, we can append the individual - // unpacked entries onto the new tuple. If it's not an upacked tuple - // but some other iterator (e.g. a List), we won't know the number of - // items, so we'll need to leave the Tuple open-ended. - if ( - possibleUnpackedTuple && - isClassInstance(possibleUnpackedTuple) && - possibleUnpackedTuple.priv.tupleTypeArgs - ) { - const typeArgs = possibleUnpackedTuple.priv.tupleTypeArgs; - - if (!typeArgs) { - entryTypes.push({ type: UnknownType.create(), isUnbounded: true }); - } else { - appendArray(entryTypes, typeArgs); - } - } else if (isNever(typeResult.type) && typeResult.isIncomplete && !typeResult.unpackedType) { - entryTypes.push({ type: UnknownType.create(/* isIncomplete */ true), isUnbounded: false }); - } else { - let entryType = convertSpecialFormToRuntimeValue(typeResult.type, EvalFlags.None, convertModule); - entryType = stripLiterals ? stripTypeForm(stripLiteralValue(entryType)) : entryType; - entryTypes.push({ type: entryType, isUnbounded: !!typeResult.unpackedType }); - } - } - - // If there are multiple unbounded entries, combine all of them into a single - // unbounded entry to avoid violating the invariant that there can be at most - // one unbounded entry in a tuple. - if (entryTypes.filter((t) => t.isUnbounded).length > 1) { - const firstUnboundedEntryIndex = entryTypes.findIndex((t) => t.isUnbounded); - const removedEntries = entryTypes.splice(firstUnboundedEntryIndex); - entryTypes.push({ type: combineTypes(removedEntries.map((t) => t.type)), isUnbounded: true }); - } - - return entryTypes; - } - - // Viper 扩展:判断调用节点是否为 Viper C 整数类型的构造调用。 - // 支持两种形式: - // 1. 直接名称调用:CUInt32T(addr, t.CPtr) - // 2. 成员访问调用:t.CUInt32T(addr, t.CPtr) - // 通过提取调用表达式左侧的名称(CUInt32T),查询 _viperCIntegerTypeNames 白名单判断。 - function isViperCIntegerCall(node: CallNode): boolean { - const leftExpr = node.d.leftExpr; - let name: string | undefined; - - if (leftExpr.nodeType === ParseNodeType.Name) { - // 形式 1:直接名称调用 - name = leftExpr.d.value; - } else if (leftExpr.nodeType === ParseNodeType.MemberAccess) { - // 形式 2:成员访问调用(如 t.CUInt32T) - name = leftExpr.d.member.d.value; - } - - return name !== undefined && isViperCIntegerTypeName(name); - } - - // Viper 扩展:判断调用节点是否为 __sizeof__ 方法调用。 - // 支持两种形式: - // 1. 类调用:Type.__sizeof__() - // 2. 实例调用:instance.__sizeof__() - // Viper 中 __sizeof__ 是类方法(sizeof(Type) -> int),不需要 self 参数。 - function isViperSizeOfCall(node: CallNode): boolean { - const leftExpr = node.d.leftExpr; - if (leftExpr.nodeType === ParseNodeType.MemberAccess) { - return leftExpr.d.member.d.value === '__sizeof__'; - } - return false; - } - - // Viper 扩展:检查类型注解 AST 节点是否包含 CPtr 引用。 - // 用于判断循环引用是否被指针打破(如 next: 'Node' | t.CPtr)。 - // 递归遍历类型注解表达式,查找名称为 CPtr/CNeedPtr/CAutoPtr/CArrayPtr 的标识符。 - function typeAnnotationContainsCPtr(node: ExpressionNode): boolean { - return checkCPtrRecursively(node); - } - - function checkCPtrRecursively(node: ParseNode): boolean { - if (!node) { - return false; - } - - // 检查名称节点是否为 CPtr 系列 - if (node.nodeType === ParseNodeType.Name) { - const name = (node as NameNode).d.value; - return name === 'CPtr' || name === 'CNeedPtr' || name === 'CAutoPtr' || name === 'CArrayPtr'; - } - - // 检查成员访问节点(如 t.CPtr),检查成员名 - if (node.nodeType === ParseNodeType.MemberAccess) { - const memberAccess = node as MemberAccessNode; - const memberName = memberAccess.d.member.d.value; - if ( - memberName === 'CPtr' || memberName === 'CNeedPtr' || - memberName === 'CAutoPtr' || memberName === 'CArrayPtr' - ) { - return true; - } - // 继续检查左侧表达式 - return checkCPtrRecursively(memberAccess.d.leftExpr); - } - - // 检查二进制操作节点(如 'Node' | t.CPtr),递归检查两侧 - if (node.nodeType === ParseNodeType.BinaryOperation) { - const binaryOp = node as BinaryOperationNode; - return checkCPtrRecursively(binaryOp.d.leftExpr) || checkCPtrRecursively(binaryOp.d.rightExpr); - } - - // 检查索引节点(如 CPtr[Node]),递归检查左侧和索引项 - if (node.nodeType === ParseNodeType.Index) { - const indexNode = node as IndexNode; - if (checkCPtrRecursively(indexNode.d.leftExpr)) { - return true; - } - for (const item of indexNode.d.items) { - if (checkCPtrRecursively(item.d.valueExpr)) { - return true; - } - } - return false; - } - - // 检查字符串列表节点(字符串注解如 'Node'),检查字符串内容 - if (node.nodeType === ParseNodeType.StringList) { - const stringList = node as StringListNode; - for (const stringNode of stringList.d.strings) { - if (stringNode.nodeType === ParseNodeType.String) { - const strValue = (stringNode as any).d?.value ?? ''; - // 检查字符串注解中是否包含 CPtr 引用 - if (/\b(CPtr|CNeedPtr|CAutoPtr|CArrayPtr)\b/.test(strValue)) { - return true; - } - } - } - return false; - } - - // 检查类型注解节点(如 x: Type),递归检查类型表达式 - if (node.nodeType === ParseNodeType.TypeAnnotation) { - const typeAnnot = node as TypeAnnotationNode; - return checkCPtrRecursively(typeAnnot.d.annotation); - } - - // 对于其他节点类型,尝试遍历子节点 - const d = (node as any).d; - if (d && typeof d === 'object') { - for (const key of Object.keys(d)) { - const value = d[key]; - if (value && typeof value === 'object' && value.nodeType !== undefined) { - if (checkCPtrRecursively(value)) { - return true; - } - } else if (Array.isArray(value)) { - for (const item of value) { - if (item && typeof item === 'object' && item.nodeType !== undefined) { - if (checkCPtrRecursively(item)) { - return true; - } - } - } - } - } - } - - return false; - } - - // Viper 扩展:检测 C 整数类型构造调用的第二参数是否为 t.CPtr。 - // 若是,则返回 CPtr 类的 instantiable 形式,用于构造 CPtr[T] 指针类型; - // 否则返回 undefined。 - // 示例:t.CUInt32T(addr, t.CPtr) → 返回 CPtr 类,外层构造 CPtr[CUInt32T]。 - // - // 判断方式:评估第二参数表达式的类型,若为 instantiable CPtr 类则匹配。 - // 这样可以正确处理 t.CPtr、CPtr(直接引用)以及别名导入等各种形式。 - function tryGetCPtrTypeFromSecondArg( - node: CallNode - ): ClassType | undefined { - if (node.d.args.length < 2) { - return undefined; - } - - const secondArgExpr = node.d.args[1].d.valueExpr; - // 若是关键字参数且名字不是 base,则跳过(仅识别位置参数或 base=... 形式) - const argName = node.d.args[1].d.name; - if (argName && argName.d.value !== 'base') { - return undefined; - } - - // 评估第二参数表达式的类型 - const secondArgTypeResult = getTypeOfExpression( - secondArgExpr, - EvalFlags.CallBaseDefaults - ); - const secondArgType = secondArgTypeResult.type; - - // 期望第二参数为 CPtr 类的 instantiable 形式(type[CPtr]) - if (isInstantiableClass(secondArgType) && isCPtrType(secondArgType)) { - return secondArgType; - } - - return undefined; - } - - // Viper 扩展:统计构造调用中作为"指针级数提升"的 t.CPtr 类型参数个数。 - // 语义:每个作为位置参数(或 base=... 形式)的 t.CPtr 类都表示再包一层指针。 - // 用于多级指针构造: - // - t.CUInt32T(x, t.CPtr) → 1 个 t.CPtr → 1 级 CPtr[CUInt32T] - // - t.CUInt32T(x, t.CPtr, t.CPtr) → 2 个 t.CPtr → 2 级 CPtr[CPtr[CUInt32T]] - // - t.CPtr(x, t.CPtr) → 1 个 t.CPtr → CPtr 自身 1 级 + 1 = 2 级 - // 遇到非 t.CPtr 类型参数即停止统计(级数提升参数须为连续的 t.CPtr)。 - function countCPtrTypeArgs(node: CallNode): number { - if (node.d.args.length < 2) { - return 0; - } - let count = 0; - for (let i = 1; i < node.d.args.length; i++) { - const arg = node.d.args[i]; - // 仅识别位置参数或 base=... 形式(与 tryGetCPtrTypeFromSecondArg 一致) - const argName = arg.d.name; - if (argName && argName.d.value !== 'base') { - break; - } - const argExpr = arg.d.valueExpr; - if (!argExpr) { - break; - } - const argType = getTypeOfExpression(argExpr, EvalFlags.CallBaseDefaults).type; - if (isInstantiableClass(argType) && isCPtrType(argType)) { - count++; - } else { - break; - } - } - return count; - } - - // Viper 扩展:构造 N 级嵌套 CPtr 实例类型。 - // 内层为 innermostType,外层包裹 N 层 CPtr: - // buildNestedCPtrType(cptrClass, integerT, 0) → integerT - // buildNestedCPtrType(cptrClass, integerT, 1) → CPtr[integerT] - // buildNestedCPtrType(cptrClass, integerT, 2) → CPtr[CPtr[integerT]] - // cptrClass 为 CPtr 的 instantiable 形式(type[CPtr])。 - function buildNestedCPtrType(cptrClass: ClassType, innermostType: Type, levels: number): Type { - let result = innermostType; - for (let i = 0; i < levels; i++) { - const cptrInstance = ClassType.cloneAsInstance(cptrClass); - result = ClassType.specialize(cptrInstance, [result]); - } - return result; - } - - function getTypeOfCall( - node: CallNode, - flags: EvalFlags, - inferenceContext: InferenceContext | undefined - ): TypeResult { - let baseTypeResult: TypeResult | undefined; - - // Check for the use of `type(x)` within a type annotation. This isn't - // allowed, and it's a common mistake, so we want to emit a diagnostic - // that guides the user to the right solution. - if ( - (flags & EvalFlags.TypeExpression) !== 0 && - node.d.leftExpr.nodeType === ParseNodeType.Name && - node.d.leftExpr.d.value === 'type' - ) { - const diag = new DiagnosticAddendum(); - diag.addMessage(LocAddendum.useTypeInstead()); - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.typeCallNotAllowed() + diag.getString(), - node - ); - } - - // Handle immediate calls of lambdas specially. - if (node.d.leftExpr.nodeType === ParseNodeType.Lambda) { - baseTypeResult = getTypeOfLambdaForCall(node, inferenceContext); - } else { - baseTypeResult = getTypeOfExpression( - node.d.leftExpr, - EvalFlags.CallBaseDefaults | (flags & EvalFlags.ForwardRefs) - ); - } - - const argList = ParseTreeUtils.getArgsByRuntimeOrder(node).map((arg) => { - const functionArg: Arg = { - valueExpression: arg.d.valueExpr, - argCategory: arg.d.argCategory, - node: arg, - name: arg.d.name, - }; - return functionArg; - }); - - let typeResult: TypeResult = { type: UnknownType.create() }; - - baseTypeResult.type = ensureSignatureIsUnique(baseTypeResult.type, node); - - if (!isTypeAliasPlaceholder(baseTypeResult.type)) { - if (node.d.leftExpr.nodeType === ParseNodeType.Name && node.d.leftExpr.d.value === 'super') { - // Handle the built-in "super" call specially. - typeResult = getTypeOfSuperCall(node); - } else if ( - isAnyOrUnknown(baseTypeResult.type) && - node.d.leftExpr.nodeType === ParseNodeType.Name && - node.d.leftExpr.d.value === 'reveal_type' - ) { - // Handle the implicit "reveal_type" call. - typeResult = getTypeOfRevealType(node, inferenceContext); - } else if (isFunction(baseTypeResult.type) && FunctionType.isBuiltIn(baseTypeResult.type, 'reveal_type')) { - // Handle the "typing.reveal_type" call. - typeResult = getTypeOfRevealType(node, inferenceContext); - } else if (isFunction(baseTypeResult.type) && FunctionType.isBuiltIn(baseTypeResult.type, 'assert_type')) { - // Handle the "typing.assert_type" call. - typeResult = getTypeOfAssertType(node, inferenceContext); - } else if (isClass(baseTypeResult.type) && ClassType.isBuiltIn(baseTypeResult.type, 'TypeForm')) { - // Handle the "typing.TypeForm" call. - typeResult = getTypeOfTypeForm(node, baseTypeResult.type); - } else if ( - isAnyOrUnknown(baseTypeResult.type) && - node.d.leftExpr.nodeType === ParseNodeType.Name && - node.d.leftExpr.d.value === 'reveal_locals' - ) { - if (node.d.args.length === 0) { - // Handle the special-case "reveal_locals" call. - typeResult.type = getTypeOfRevealLocals(node); - } else { - addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.revealLocalsArgs(), node); - } - } else if ( - // Viper 扩展:拦截 C 整数类型的构造调用(如 t.CUInt32T(addr, t.CPtr))。 - // 这些类型在 vipershed-fallback/stdlib/t.vpi 中定义为 TypeAlias = int, - // 但在 Viper 语义中它们是独立的 C 整数类型,构造调用应支持指针强转语义, - // 而不是走 int.__new__ 的参数类型检查(int.__new__ 期望 str|bytes|bytearray, - // 而 Viper 强转常用指针地址作为参数)。 - IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect) && - isInstantiableClass(baseTypeResult.type) && - ClassType.isBuiltIn(baseTypeResult.type, 'int') && - isViperCIntegerCall(node) - ) { - // 跳过 int.__new__ 的参数类型检查,直接返回该整数类型的实例。 - // Viper 的 C 整数类型构造调用本质是 C 语言的强转 (uint32_t)addr, - // 参数类型检查由 Viper 编译器在语义分析阶段处理,LSP 不需要重复检查。 - // - // 指针级数提升形式:每个作为位置参数的 t.CPtr 都表示再包一层指针: - // t.CUInt32T(addr) → 0 个 t.CPtr → CUInt32T 实例(值) - // t.CUInt32T(addr, t.CPtr) → 1 个 t.CPtr → CPtr[CUInt32T] (uint32_t*) - // t.CUInt32T(addr, t.CPtr, t.CPtr) → 2 个 t.CPtr → CPtr[CPtr[CUInt32T]] (uint32_t**) - const integerInstanceType = ClassType.cloneAsInstance(baseTypeResult.type); - const ptrLevels = countCPtrTypeArgs(node); - if (ptrLevels > 0) { - // 取 CPtr 的 instantiable 形式用于构造嵌套指针类型 - const cptrClass = tryGetCPtrTypeFromSecondArg(node); - if (cptrClass) { - typeResult.type = buildNestedCPtrType(cptrClass, integerInstanceType, ptrLevels); - } else { - typeResult.type = integerInstanceType; - } - } else { - typeResult.type = integerInstanceType; - } - } else if ( - // Viper 扩展:拦截 t.CPtr(...) 自身构造调用。 - // CPtr 不是 built-in int,不会进入上面的整数分支;若走默认 validateCallArgs, - // 按 __init__(value, *types) 只返回无级数信息的 CPtr 实例,丢失指针级数语义, - // 导致后续跨级赋值(如 a: t.CPtr = t.CPtr(x, t.CPtr))无法被检测。 - // 此处按 t.CPtr 参数个数构造正确的多级指针类型: - // t.CPtr(x) → 1 级(CPtr 实例) - // t.CPtr(x, t.CPtr) → 2 级(CPtr[CPtr]) - // t.CPtr(x, t.CPtr, t.CPtr) → 3 级(CPtr[CPtr[CPtr]]) - // CPtr 自身已是 1 级,额外每个 t.CPtr 参数再 +1 级。 - IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect) && - isInstantiableClass(baseTypeResult.type) && - isCPtrType(baseTypeResult.type) - ) { - const extraLevels = countCPtrTypeArgs(node); - // 最内层为 CPtr 实例(1 级),外层包裹 extraLevels 层 CPtr - const cptrInstance = ClassType.cloneAsInstance(baseTypeResult.type); - typeResult.type = buildNestedCPtrType(baseTypeResult.type, cptrInstance, extraLevels); - } else if ( - // Viper 扩展:拦截非整数、非 CPtr 的 CType 子类指针构造调用。 - // 覆盖 t.CVoid(0, t.CPtr)、t.CFloat(x, t.CPtr) 等形式: - // 这些类型不是 built-in int(不进入 9477 整数分支),也不是 CPtr(不进入 9509), - // 若走默认 validateCallArgs 只返回值实例,丢失 t.CPtr 参数的指针级数语义。 - // 按 Viper 的 C 强转语义,t.CVoid(0, t.CPtr) 应产生 CPtr[CVoid](void* 指针), - // t.CVoid(0, t.CPtr, t.CPtr) 应产生 CPtr[CPtr[CVoid]](void** 指针)。 - // 这与 t.CUInt32T(addr, t.CPtr)→CPtr[CUInt32T] 的指针级数提升语义一致。 - // 命中场景:t.CVoid(0, t.CPtr) 赋值给 UINT32PTR(uint32_t*)时同级指针转换。 - IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect) && - isInstantiableClass(baseTypeResult.type) && - isCTypeSubclassType(baseTypeResult.type) && - !isCPtrType(baseTypeResult.type) && - countCPtrTypeArgs(node) > 0 - ) { - const instanceType = ClassType.cloneAsInstance(baseTypeResult.type); - const ptrLevels = countCPtrTypeArgs(node); - const cptrClass = tryGetCPtrTypeFromSecondArg(node); - if (cptrClass) { - typeResult.type = buildNestedCPtrType(cptrClass, instanceType, ptrLevels); - } else { - typeResult.type = instanceType; - } - } else if ( - // Viper 扩展:拦截 __sizeof__ 调用。 - // Viper 中 __sizeof__ 语义是 C 语言的 sizeof(Type) -> int, - // 是类方法/静态方法,不需要 self 参数。但 object.__sizeof__ 是实例方法, - // 当通过类调用 Type.__sizeof__() 时会报 "Argument missing for parameter self"。 - // 此处跳过参数检查,直接返回 int 类型。 - IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect) && - isViperSizeOfCall(node) - ) { - // Viper 的 __sizeof__ 是类方法(sizeof(Type)),返回类型大小(字节数)。 - // 跳过 object.__sizeof__ 的实例方法参数检查,直接返回 int。 - typeResult.type = getBuiltInObject(node, 'int'); - } else { - const callResult = validateCallArgs( - node, - argList, - baseTypeResult, - /* constraints */ undefined, - /* skipUnknownArgCheck */ false, - inferenceContext - ); - - typeResult.type = callResult.returnType ?? UnknownType.create(); - - if (callResult.argumentErrors) { - typeResult.typeErrors = true; - } else { - typeResult.overloadsUsedForCall = callResult.overloadsUsedForCall; - } - - if (callResult.isTypeIncomplete) { - typeResult.isIncomplete = true; - } - } - - if (baseTypeResult.isIncomplete) { - typeResult.isIncomplete = true; - } - } else { - typeResult.isIncomplete = true; - } - - // Don't bother evaluating the arguments if we're speculatively evaluating the call - // or the base type is incomplete. - if (!isSpeculativeModeInUse(node) && !baseTypeResult.isIncomplete) { - // Touch all of the args so they're marked accessed even if there were errors. - // We skip this if it's a TypeVar() call in the typing.pyi module because - // this results in a cyclical type resolution problem whereby we try to - // retrieve the str class, which inherits from Sequence, which inherits from - // Iterable, which uses a TypeVar. Without this, Iterable and Sequence classes - // have invalid type parameters. - const isCyclicalTypeVarCall = - isInstantiableClass(baseTypeResult.type) && - ClassType.isBuiltIn(baseTypeResult.type, 'TypeVar') && - AnalyzerNodeInfo.getFileInfo(node).isTypingStubFile; - - if (!isCyclicalTypeVarCall) { - argList.forEach((arg) => { - if ( - arg.valueExpression && - arg.valueExpression.nodeType !== ParseNodeType.StringList && - !isTypeCached(arg.valueExpression) - ) { - getTypeOfExpression(arg.valueExpression); - } - }); - } - } - - if ((flags & EvalFlags.TypeExpression) !== 0) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeAnnotationCall(), node); - - typeResult = { type: UnknownType.create() }; - } - - return typeResult; - } - - // This function is used in cases where a lambda is defined and immediately - // called. In this case, we can't use normal bidirectional type inference - // to determine the lambda's type. It needs to be inferred from the argument - // types instead. - function getTypeOfLambdaForCall(node: CallNode, inferenceContext: InferenceContext | undefined): TypeResult { - assert(node.d.leftExpr.nodeType === ParseNodeType.Lambda); - - const expectedType = FunctionType.createSynthesizedInstance(''); - expectedType.shared.declaredReturnType = inferenceContext - ? inferenceContext.expectedType - : UnknownType.create(); - - let isArgTypeIncomplete = false; - node.d.args.forEach((arg, index) => { - const argTypeResult = getTypeOfExpression(arg.d.valueExpr); - if (argTypeResult.isIncomplete) { - isArgTypeIncomplete = true; - } - - FunctionType.addParam( - expectedType, - FunctionParam.create( - ParamCategory.Simple, - argTypeResult.type, - FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared, - `p${index.toString()}` - ) - ); - }); - - // If the lambda's param list ends with a "/" positional parameter separator, - // add a corresponding separator to the expected type. - const lambdaParams = node.d.leftExpr.d.params; - if (lambdaParams.length > 0) { - const lastParam = lambdaParams[lambdaParams.length - 1]; - if (lastParam.d.category === ParamCategory.Simple && !lastParam.d.name) { - FunctionType.addPositionOnlyParamSeparator(expectedType); - } - } - - function getLambdaType() { - return getTypeOfExpression(node.d.leftExpr, EvalFlags.CallBaseDefaults, makeInferenceContext(expectedType)); - } - - // If one or more of the arguments are incomplete, use speculative mode - // for the lambda evaluation because it may need to be reevaluated once - // the arg types are complete. - let typeResult = - isArgTypeIncomplete || isSpeculativeModeInUse(node) || inferenceContext?.isTypeIncomplete - ? useSpeculativeMode(node.d.leftExpr, getLambdaType) - : getLambdaType(); - - // If bidirectional type inference failed, use normal type inference instead. - if (typeResult.typeErrors) { - typeResult = getTypeOfExpression(node.d.leftExpr, EvalFlags.CallBaseDefaults); - } - - return typeResult; - } - - function getTypeOfTypeForm(node: CallNode, typeFormClass: ClassType): TypeResult { - if ( - node.d.args.length !== 1 || - node.d.args[0].d.argCategory !== ArgCategory.Simple || - node.d.args[0].d.name !== undefined - ) { - addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.typeFormArgs(), node); - return { type: UnknownType.create() }; - } - - const typeFormResult = getTypeOfArgExpectingType(convertNodeToArg(node.d.args[0]), { - typeFormArg: true, - noNonTypeSpecialForms: true, - typeExpression: true, - }); - - if (!typeFormResult.typeErrors && typeFormResult.type.props?.typeForm) { - typeFormResult.type = convertToInstance( - ClassType.specialize(typeFormClass, [convertToInstance(typeFormResult.type.props.typeForm)]) - ); - } - - return typeFormResult; - } - - function getTypeOfAssertType(node: CallNode, inferenceContext: InferenceContext | undefined): TypeResult { - if ( - node.d.args.length !== 2 || - node.d.args[0].d.argCategory !== ArgCategory.Simple || - node.d.args[0].d.name !== undefined || - node.d.args[0].d.argCategory !== ArgCategory.Simple || - node.d.args[1].d.name !== undefined - ) { - addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.assertTypeArgs(), node); - return { type: UnknownType.create() }; - } - - const arg0TypeResult = getTypeOfExpression(node.d.args[0].d.valueExpr, /* flags */ undefined, inferenceContext); - if (arg0TypeResult.isIncomplete) { - return { type: UnknownType.create(/* isIncomplete */ true), isIncomplete: true }; - } - - const assertedType = convertToInstance( - getTypeOfArgExpectingType(convertNodeToArg(node.d.args[1]), { - typeExpression: true, - }).type - ); - - // We'll replace TypeGuard and TypeIs with bool for purposes of assert_type testing. - // The spec is unclear on whether this is the correct behavior, but it seems to be - // what mypy does -- and what various library authors expect. - const arg0Type = stripTypeGuard(arg0TypeResult.type); - - if ( - !isTypeSame(assertedType, arg0Type, { - treatAnySameAsUnknown: true, - ignorePseudoGeneric: true, - ignoreConditions: true, - }) - ) { - const srcDestTypes = printSrcDestTypes(arg0TypeResult.type, assertedType, { expandTypeAlias: true }); - - addDiagnostic( - DiagnosticRule.reportAssertTypeFailure, - LocMessage.assertTypeTypeMismatch().format({ - expected: srcDestTypes.destType, - received: srcDestTypes.sourceType, - }), - node.d.args[0].d.valueExpr - ); - } - - return { type: arg0TypeResult.type }; - } - - function convertNodeToArg(node: ArgumentNode): ArgWithExpression { - return { - argCategory: node.d.argCategory, - name: node.d.name, - valueExpression: node.d.valueExpr, - }; - } - - function getTypeOfRevealType(node: CallNode, inferenceContext: InferenceContext | undefined): TypeResult { - let arg0Value: ExpressionNode | undefined; - let expectedRevealTypeNode: ExpressionNode | undefined; - let expectedRevealType: Type | undefined; - let expectedTextNode: ExpressionNode | undefined; - let expectedText: string | undefined; - - // Make sure there is only one positional argument passed as arg 0. - node.d.args.forEach((arg, index) => { - if (index === 0) { - if (arg.d.argCategory === ArgCategory.Simple && !arg.d.name) { - arg0Value = arg.d.valueExpr; - } - } else if (arg.d.argCategory !== ArgCategory.Simple || !arg.d.name) { - arg0Value = undefined; - } else if (arg.d.name.d.value === 'expected_text') { - expectedTextNode = arg.d.valueExpr; - const expectedTextType = getTypeOfExpression(arg.d.valueExpr).type; - - if ( - !isClassInstance(expectedTextType) || - !ClassType.isBuiltIn(expectedTextType, 'str') || - typeof expectedTextType.priv.literalValue !== 'string' - ) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.revealTypeExpectedTextArg(), - arg.d.valueExpr - ); - } else { - expectedText = expectedTextType.priv.literalValue; - } - } else if (arg.d.name.d.value === 'expected_type') { - expectedRevealTypeNode = arg.d.valueExpr; - expectedRevealType = convertToInstance( - getTypeOfArgExpectingType(convertNodeToArg(arg), { - typeExpression: true, - }).type - ); - } - }); - - if (!arg0Value) { - addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.revealTypeArgs(), node); - return { type: UnknownType.create() }; - } - - const typeResult = getTypeOfExpression(arg0Value, /* flags */ undefined, inferenceContext); - const type = typeResult.type; - - const exprString = ParseTreeUtils.printExpression(arg0Value); - const typeString = printType(type, { expandTypeAlias: true }); - - if (!typeResult.isIncomplete) { - if (expectedText !== undefined) { - if (expectedText !== typeString) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.revealTypeExpectedTextMismatch().format({ - expected: expectedText, - received: typeString, - }), - expectedTextNode ?? arg0Value - ); - } - } - - if (expectedRevealType) { - if (!isTypeSame(expectedRevealType, type, { ignorePseudoGeneric: true })) { - const expectedRevealTypeText = printType(expectedRevealType); - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.revealTypeExpectedTypeMismatch().format({ - expected: expectedRevealTypeText, - received: typeString, - }), - expectedRevealTypeNode ?? arg0Value - ); - } - } - - addInformation(LocAddendum.typeOfSymbol().format({ name: exprString, type: typeString }), node.d.args[0]); - } - - return { type, isIncomplete: typeResult.isIncomplete }; - } - - function getTypeOfRevealLocals(node: CallNode) { - let curNode: ParseNode | undefined = node; - let scope: Scope | undefined; - - while (curNode) { - scope = ScopeUtils.getScopeForNode(curNode); - - // Stop when we get a valid scope that's not a list comprehension - // scope. That includes lambdas, functions, classes, and modules. - if (scope && scope.type !== ScopeType.Comprehension) { - break; - } - - curNode = curNode.parent; - } - - const infoMessages: string[] = []; - - if (scope) { - scope.symbolTable.forEach((symbol, name) => { - if (!symbol.isIgnoredForProtocolMatch()) { - const typeOfSymbol = getEffectiveTypeOfSymbol(symbol); - infoMessages.push( - LocAddendum.typeOfSymbol().format({ - name, - type: printType(typeOfSymbol, { expandTypeAlias: true }), - }) - ); - } - }); - } - - if (infoMessages.length > 0) { - addInformation(infoMessages.join('\n'), node); - } else { - addInformation(LocMessage.revealLocalsNone(), node); - } - - return getNoneType(); - } - - function getTypeOfSuperCall(node: CallNode): TypeResult { - if (node.d.args.length > 2) { - addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.superCallArgCount(), node.d.args[2]); - } - - const enclosingFunction = ParseTreeUtils.getEnclosingFunctionEvaluationScope(node); - const enclosingClass = enclosingFunction ? ParseTreeUtils.getEnclosingClass(enclosingFunction) : undefined; - const enclosingClassType = enclosingClass ? getTypeOfClass(enclosingClass)?.classType : undefined; - - // Determine which class the "super" call is applied to. If - // there is no first argument, then the class is implicit. - let targetClassType: Type; - if (node.d.args.length > 0) { - targetClassType = getTypeOfExpression(node.d.args[0].d.valueExpr).type; - const concreteTargetClassType = makeTopLevelTypeVarsConcrete(targetClassType); - - if ( - !isAnyOrUnknown(concreteTargetClassType) && - !isInstantiableClass(concreteTargetClassType) && - !isMetaclassInstance(concreteTargetClassType) - ) { - addDiagnostic( - DiagnosticRule.reportArgumentType, - LocMessage.superCallFirstArg().format({ type: printType(targetClassType) }), - node.d.args[0].d.valueExpr - ); - } - } else { - if (enclosingClassType) { - targetClassType = enclosingClassType ?? UnknownType.create(); - - // Zero-argument forms of super are not allowed within static methods. - // This results in a runtime exception. - if (enclosingFunction) { - const functionInfo = getFunctionInfoFromDecorators( - evaluatorInterface, - enclosingFunction, - /* isInClass */ true - ); - - if ((functionInfo?.flags & FunctionTypeFlags.StaticMethod) !== 0) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.superCallZeroArgFormStaticMethod(), - node.d.leftExpr - ); - } - } - } else { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.superCallZeroArgForm(), - node.d.leftExpr - ); - targetClassType = UnknownType.create(); - } - } - - const concreteTargetClassType = makeTopLevelTypeVarsConcrete(targetClassType); - - // Determine whether to further narrow the type. - let secondArgType: Type | undefined; - let bindToType: ClassType | undefined; - - if (node.d.args.length > 1) { - secondArgType = getTypeOfExpression(node.d.args[1].d.valueExpr).type; - const secondArgConcreteType = makeTopLevelTypeVarsConcrete(secondArgType); - - let reportError = false; - - doForEachSubtype(secondArgConcreteType, (secondArgSubtype) => { - if (isAnyOrUnknown(secondArgSubtype)) { - // Ignore unknown or any types. - } else if (isClassInstance(secondArgSubtype)) { - if (isInstantiableClass(concreteTargetClassType)) { - if ( - !derivesFromClassRecursive( - ClassType.cloneAsInstantiable(secondArgSubtype), - concreteTargetClassType, - /* ignoreUnknown */ true - ) - ) { - reportError = true; - } - } - bindToType = secondArgSubtype; - } else if (isInstantiableClass(secondArgSubtype)) { - if (isInstantiableClass(concreteTargetClassType)) { - if ( - !ClassType.isBuiltIn(concreteTargetClassType, 'type') && - !derivesFromClassRecursive( - secondArgSubtype, - concreteTargetClassType, - /* ignoreUnknown */ true - ) - ) { - reportError = true; - } - } - bindToType = secondArgSubtype; - } else { - reportError = true; - } - }); - - if (reportError) { - addDiagnostic( - DiagnosticRule.reportArgumentType, - LocMessage.superCallSecondArg().format({ type: printType(targetClassType) }), - node.d.args[1].d.valueExpr - ); - - return { type: UnknownType.create() }; - } - } else if (enclosingClassType) { - bindToType = ClassType.cloneAsInstance(enclosingClassType); - - // Get the type from the self or cls parameter if it is explicitly annotated. - // If it's a TypeVar, change the bindToType into a conditional type. - const enclosingMethod = ParseTreeUtils.getEnclosingFunction(node); - let implicitBindToType: Type | undefined; - - if (enclosingMethod) { - const methodTypeInfo = getTypeOfFunction(enclosingMethod); - if (methodTypeInfo) { - const methodType = methodTypeInfo.functionType; - if ( - FunctionType.isClassMethod(methodType) || - FunctionType.isConstructorMethod(methodType) || - FunctionType.isInstanceMethod(methodType) - ) { - if ( - methodType.shared.parameters.length > 0 && - FunctionParam.isTypeDeclared(methodType.shared.parameters[0]) - ) { - let paramType = FunctionType.getParamType(methodType, 0); - const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(node); - paramType = makeTypeVarsBound(paramType, liveScopeIds); - implicitBindToType = makeTopLevelTypeVarsConcrete(paramType); - } - } - } - } - - if (bindToType && implicitBindToType) { - const typeCondition = getTypeCondition(implicitBindToType); - if (typeCondition) { - bindToType = addConditionToType(bindToType, typeCondition); - } else if (isClass(implicitBindToType)) { - bindToType = implicitBindToType; - } - } - } - - // Determine whether super() should return an instance of the class or - // the class itself. It depends on whether the super() call is located - // within an instance method or not. - let resultIsInstance = true; - if (node.d.args.length <= 1) { - const enclosingMethod = ParseTreeUtils.getEnclosingFunction(node); - if (enclosingMethod) { - const methodType = getTypeOfFunction(enclosingMethod); - if (methodType) { - if ( - FunctionType.isStaticMethod(methodType.functionType) || - FunctionType.isConstructorMethod(methodType.functionType) || - FunctionType.isClassMethod(methodType.functionType) - ) { - resultIsInstance = false; - } - } - } - } - - // Python docs indicate that super() isn't valid for - // operations other than member accesses or attribute lookups. - const parentNode = node.parent; - if (parentNode?.nodeType === ParseNodeType.MemberAccess) { - const memberName = parentNode.d.member.d.value; - let effectiveTargetClass = isClass(concreteTargetClassType) ? concreteTargetClassType : undefined; - - // If the bind-to type is a protocol, don't use the effective target class. - // This pattern is used for mixins, where the mixin type is a protocol class - // that is used to decorate the "self" or "cls" parameter. - let isProtocolClass = false; - if ( - bindToType && - ClassType.isProtocolClass(bindToType) && - effectiveTargetClass && - !ClassType.isSameGenericClass( - TypeBase.isInstance(bindToType) ? ClassType.cloneAsInstantiable(bindToType) : bindToType, - effectiveTargetClass - ) - ) { - isProtocolClass = true; - effectiveTargetClass = undefined; - } - - if (bindToType) { - bindToType = selfSpecializeClass(bindToType, { useBoundTypeVars: true }); - } - - const lookupResults = bindToType - ? lookUpClassMember(bindToType, memberName, MemberAccessFlags.Default, effectiveTargetClass) - : undefined; - - let resultType: Type; - if (lookupResults && isInstantiableClass(lookupResults.classType)) { - resultType = lookupResults.classType; - - if (isProtocolClass) { - // If the bindToType is a protocol class, set the "include subclasses" flag - // so we don't enforce that called methods are implemented within the protocol. - resultType = ClassType.cloneIncludeSubclasses(resultType); - } - } else if ( - effectiveTargetClass && - !isAnyOrUnknown(effectiveTargetClass) && - !derivesFromAnyOrUnknown(effectiveTargetClass) - ) { - resultType = prefetched?.objectClass ?? UnknownType.create(); - } else { - resultType = UnknownType.create(); - } - - let bindToSelfType: ClassType | TypeVarType | undefined; - if (bindToType) { - if (secondArgType) { - // If a TypeVar was passed as the second argument, use it - // to derive the the self type. - if (isTypeVar(secondArgType)) { - bindToSelfType = convertToInstance(secondArgType); - } - } else { - // If this is a zero-argument form of super(), synthesize - // a Self type to bind to. - bindToSelfType = TypeBase.cloneForCondition( - TypeVarType.cloneAsBound( - synthesizeTypeVarForSelfCls( - ClassType.cloneIncludeSubclasses(bindToType, /* includeSubclasses */ false), - /* isClsParam */ false - ) - ), - bindToType.props?.condition - ); - } - } - - const type = resultIsInstance ? convertToInstance(resultType, /* includeSubclasses */ false) : resultType; - - return { type, bindToSelfType }; - } - - // Handle the super() call when used outside of a member access expression. - if (isInstantiableClass(concreteTargetClassType)) { - // We don't know which member is going to be accessed, so we cannot - // deterministically determine the correct type in this case. We'll - // use a heuristic that produces the "correct" (desired) behavior in - // most cases. If there's a bindToType and the targetClassType is one - // of the base classes of the bindToType, we'll return the next base - // class. - if (bindToType) { - let nextBaseClassType: Type | undefined; - - if ( - ClassType.isSameGenericClass( - TypeBase.isInstance(bindToType) ? ClassType.cloneAsInstantiable(bindToType) : bindToType, - concreteTargetClassType - ) - ) { - if (bindToType.shared.baseClasses.length > 0) { - nextBaseClassType = bindToType.shared.baseClasses[0]; - } - } else { - const baseClassIndex = bindToType.shared.baseClasses.findIndex( - (baseClass) => - isClass(baseClass) && - ClassType.isSameGenericClass(baseClass, concreteTargetClassType as ClassType) - ); - - if (baseClassIndex >= 0 && baseClassIndex < bindToType.shared.baseClasses.length - 1) { - nextBaseClassType = bindToType.shared.baseClasses[baseClassIndex + 1]; - } - } - - if (nextBaseClassType) { - if (isInstantiableClass(nextBaseClassType)) { - nextBaseClassType = specializeForBaseClass(bindToType, nextBaseClassType); - } - return { type: resultIsInstance ? convertToInstance(nextBaseClassType) : nextBaseClassType }; - } - - // There's not much we can say about the type. Simply return object or type. - if (prefetched?.typeClass && isInstantiableClass(prefetched.typeClass)) { - return { - type: resultIsInstance ? getObjectType() : convertToInstance(prefetched.typeClass), - }; - } - } else { - // If the class derives from one or more unknown classes, - // return unknown here to prevent spurious errors. - if (concreteTargetClassType.shared.mro.some((mroBase) => isAnyOrUnknown(mroBase))) { - return { type: UnknownType.create() }; - } - - const baseClasses = concreteTargetClassType.shared.baseClasses; - if (baseClasses.length > 0) { - const baseClassType = baseClasses[0]; - if (isInstantiableClass(baseClassType)) { - return { - type: resultIsInstance ? ClassType.cloneAsInstance(baseClassType) : baseClassType, - }; - } - } - } - } - - return { type: UnknownType.create() }; - } - - // When evaluating a call, the errorNode is typically the call node, which - // encompasses all of the argument expressions. This means we can normally - // use the errorNode as the root for speculative evaluation. However, there - // are some cases where we don't have a call node (e.g. in the case of an - // __init_subclass__ validation). Here we need to find some other parent - // node of the error node that encompasses all of the arguments. - function getSpeculativeNodeForCall(errorNode: ExpressionNode): ParseNode { - // If the error node is within an arg, expand to include the parent of the arg list. - const argParent = ParseTreeUtils.getParentNodeOfType(errorNode, ParseNodeType.Argument); - if (argParent?.parent) { - return argParent.parent; - } - - // If the error node is the name in a class declaration, expand to include the class node. - if ( - errorNode.nodeType === ParseNodeType.Name && - errorNode.parent?.nodeType === ParseNodeType.Class && - errorNode.parent.d.name === errorNode - ) { - return errorNode.parent; - } - - return errorNode; - } - - // Attempts to find an overloaded function for each set of argument - // types in the expandedArgTypes list. If an argument type is undefined, - // its type is evaluated from the argument's expression using the - // corresponding parameter's expected type. The first time this is called, - // there will be only one argument list in expandedArgTypes, and all entries - // (one for each argument) will be undefined. On subsequent calls, this - // list will grow to include union expansions. - function validateOverloadsWithExpandedTypes( - errorNode: ExpressionNode, - expandedArgTypes: (Type | undefined)[][], - argParamMatches: MatchArgsToParamsResult[], - constraints: ConstraintTracker | undefined, - skipUnknownArgCheck: boolean | undefined, - inferenceContext: InferenceContext | undefined - ): CallResult { - const returnTypes: Type[] = []; - let matchedOverloads: MatchedOverloadInfo[] = []; - let isTypeIncomplete = false; - let overloadsUsedForCall: FunctionType[] = []; - let isDefinitiveMatchFound = false; - const speculativeNode = getSpeculativeNodeForCall(errorNode); - - for (let expandedTypesIndex = 0; expandedTypesIndex < expandedArgTypes.length; expandedTypesIndex++) { - let matchedOverload: FunctionType | undefined; - const argTypeOverride = expandedArgTypes[expandedTypesIndex]; - const hasArgTypeOverride = argTypeOverride.some((a) => a !== undefined); - let possibleMatchResults: MatchedOverloadInfo[] = []; - let possibleMatchInvolvesIncompleteUnknown = false; - isDefinitiveMatchFound = false; - - for (let overloadIndex = 0; overloadIndex < argParamMatches.length; overloadIndex++) { - const overload = argParamMatches[overloadIndex].overload; - - let matchResults = argParamMatches[overloadIndex]; - if (hasArgTypeOverride) { - matchResults = { ...argParamMatches[overloadIndex] }; - matchResults.argParams = matchResults.argParams.map((argParam, argIndex) => { - if (!argTypeOverride[argIndex]) { - return argParam; - } - const argParamCopy = { ...argParam }; - argParamCopy.argType = argTypeOverride[argIndex]; - return argParamCopy; - }); - } - - // Clone the constraints so we don't modify the original. - const effectiveConstraints = constraints?.clone() ?? new ConstraintTracker(); - - // Use speculative mode so we don't output any diagnostics or - // record any final types in the type cache. - const callResult = useSpeculativeMode(speculativeNode, () => { - return validateArgTypesWithContext( - errorNode, - matchResults, - effectiveConstraints, - /* skipUnknownArgCheck */ true, - inferenceContext - ); - }); - - if (callResult.isTypeIncomplete) { - isTypeIncomplete = true; - } - - if (!callResult.argumentErrors && callResult.returnType) { - overloadsUsedForCall.push(overload); - - matchedOverload = overload; - const matchedOverloadInfo: MatchedOverloadInfo = { - overload: matchedOverload, - matchResults, - constraints: effectiveConstraints, - returnType: callResult.returnType, - argResults: callResult.argResults ?? [], - }; - matchedOverloads.push(matchedOverloadInfo); - - if (callResult.anyOrUnknownArg || matchResults.unpackedArgOfUnknownLength) { - possibleMatchResults.push(matchedOverloadInfo); - - if (callResult.anyOrUnknownArg) { - if (isIncompleteUnknown(callResult.anyOrUnknownArg)) { - possibleMatchInvolvesIncompleteUnknown = true; - } - } - } else { - returnTypes.push(callResult.returnType); - isDefinitiveMatchFound = true; - break; - } - } - } - - // If we didn't find a definitive match that doesn't depend on - // an Any or Unknown argument, fall back on the possible match. - // If there were multiple possible matches, evaluate the type as - // Unknown, but include the "possible types" to allow for completion - // suggestions. - if (!isDefinitiveMatchFound && possibleMatchResults.length > 0) { - possibleMatchResults = filterOverloadMatchesForUnpackedArgs(possibleMatchResults); - possibleMatchResults = filterOverloadMatchesForAnyArgs(possibleMatchResults); - - // Did the filtering produce a single result? If so, we're done. - if (possibleMatchResults.length === 1) { - overloadsUsedForCall = [possibleMatchResults[0].overload]; - returnTypes.push(possibleMatchResults[0].returnType); - matchedOverloads = [possibleMatchResults[0]]; - } else { - // Eliminate any return types that are subsumed by other return types. - let dedupedMatchResults: Type[] = []; - let dedupedResultsIncludeAny = false; - - possibleMatchResults.forEach((result) => { - let isSubtypeSubsumed = false; - - for (let dedupedIndex = 0; dedupedIndex < dedupedMatchResults.length; dedupedIndex++) { - if (assignType(dedupedMatchResults[dedupedIndex], result.returnType)) { - const anyOrUnknown = containsAnyOrUnknown( - dedupedMatchResults[dedupedIndex], - /* recurse */ false - ); - if (!anyOrUnknown) { - isSubtypeSubsumed = true; - } else if (isAny(anyOrUnknown)) { - dedupedResultsIncludeAny = true; - } - break; - } else if (assignType(result.returnType, dedupedMatchResults[dedupedIndex])) { - const anyOrUnknown = containsAnyOrUnknown(result.returnType, /* recurse */ false); - if (!anyOrUnknown) { - dedupedMatchResults[dedupedIndex] = NeverType.createNever(); - } else if (isAny(anyOrUnknown)) { - dedupedResultsIncludeAny = true; - } - break; - } - } - - if (!isSubtypeSubsumed) { - dedupedMatchResults.push(result.returnType); - } - }); - - dedupedMatchResults = dedupedMatchResults.filter((t) => !isNever(t)); - const combinedTypes = combineTypes(dedupedMatchResults); - - let returnType = combinedTypes; - if (dedupedMatchResults.length > 1) { - // If one or more of the deduped types is Any or contains Any, - // we will assume that the person who defined the overload really - // wanted Any rather than Unknown. In cases where the deduped types - // simply contains conflicting results without an Any, we'll use - // an UnknownType. - if (dedupedResultsIncludeAny) { - returnType = AnyType.create(); - } else { - returnType = UnknownType.createPossibleType( - combinedTypes, - possibleMatchInvolvesIncompleteUnknown - ); - } - } - - returnTypes.push(returnType); - } - } - - if (!matchedOverload) { - return { argumentErrors: true, isTypeIncomplete, overloadsUsedForCall }; - } - } - - // We found a match for all of the expanded argument lists. Copy the - // resulting type var context back into the caller's type var context. - // Use the type var context from the last matched overload because it - // includes the type var solutions for all earlier matched overloads. - if (constraints && isDefinitiveMatchFound) { - constraints.copyFromClone(matchedOverloads[matchedOverloads.length - 1].constraints); - } - - // And run through the first expanded argument list one more time to - // populate the type cache. - const finalConstraints = constraints ?? matchedOverloads[0].constraints; - const finalCallResult = validateArgTypesWithContext( - errorNode, - matchedOverloads[0].matchResults, - finalConstraints, - skipUnknownArgCheck, - inferenceContext - ); - - if (finalCallResult.isTypeIncomplete) { - isTypeIncomplete = true; - } - - return { - argumentErrors: finalCallResult.argumentErrors, - anyOrUnknownArg: finalCallResult.anyOrUnknownArg, - returnType: combineTypes(returnTypes), - isTypeIncomplete, - specializedInitSelfType: finalCallResult.specializedInitSelfType, - overloadsUsedForCall, - }; - } - - // Determines whether one or more overloads can be eliminated because they - // rely on an unpacked argument of unknown length when there is at least - // one overload that doesn't because it maps to an *args parameter. - function filterOverloadMatchesForUnpackedArgs(matches: MatchedOverloadInfo[]): MatchedOverloadInfo[] { - if (matches.length < 2) { - return matches; - } - - // Is there at least one overload that relies on unpacked args for a match? - const unpackedArgsOverloads = matches.filter((match) => match.matchResults.unpackedArgMapsToVariadic); - if (unpackedArgsOverloads.length === matches.length || unpackedArgsOverloads.length === 0) { - return matches; - } - - return unpackedArgsOverloads; - } - - // Determines whether multiple incompatible overloads match - // due to an Any or Unknown argument type. - function filterOverloadMatchesForAnyArgs(matches: MatchedOverloadInfo[]): MatchedOverloadInfo[] { - if (matches.length < 2) { - return matches; - } - - // If all of the return types match, select the first one. - if ( - areTypesSame( - matches.map((match) => match.returnType), - { treatAnySameAsUnknown: true } - ) - ) { - return [matches[0]]; - } - - const firstArgResults = matches[0].argResults; - if (!firstArgResults) { - return matches; - } - - let foundAmbiguousAnyArg = false; - for (let i = 0; i < firstArgResults.length; i++) { - // If the arg is Any or Unknown, see if the corresponding - // parameter types differ in any way. - if (isAnyOrUnknown(firstArgResults[i].argType)) { - const paramTypes = matches.map((match) => - i < match.matchResults.argParams.length - ? match.matchResults.argParams[i].paramType - : UnknownType.create() - ); - if (!areTypesSame(paramTypes, { treatAnySameAsUnknown: true })) { - foundAmbiguousAnyArg = true; - } - } - } - - // If the first overload has a different number of effective arguments - // than latter overloads, don't filter any of them. This typically means - // that one of the arguments is an unpacked iterator, and it maps to - // an indeterminate number of parameters, which means that the overload - // selection is ambiguous. - if (foundAmbiguousAnyArg || matches.some((match) => match.argResults.length !== firstArgResults.length)) { - return matches; - } - - return [matches[0]]; - } - - function getBestOverloadForArgs( - errorNode: ExpressionNode, - typeResult: TypeResult, - argList: Arg[] - ): FunctionType | undefined { - let overloadIndex = 0; - const matches: MatchArgsToParamsResult[] = []; - const speculativeNode = getSpeculativeNodeForCall(errorNode); - - useSignatureTracker(errorNode, () => { - // Create a list of potential overload matches based on arguments. - OverloadedType.getOverloads(typeResult.type).forEach((overload) => { - useSpeculativeMode(speculativeNode, () => { - const matchResults = matchArgsToParams( - errorNode, - argList, - { type: overload, isIncomplete: typeResult.isIncomplete }, - overloadIndex - ); - - if (!matchResults.argumentErrors) { - matches.push(matchResults); - } - - overloadIndex++; - }); - }); - }); - - let winningOverloadIndex: number | undefined; - - matches.forEach((match, matchIndex) => { - if (winningOverloadIndex === undefined) { - useSpeculativeMode(speculativeNode, () => { - const callResult = validateArgTypes( - errorNode, - match, - new ConstraintTracker(), - /* skipUnknownArgCheck */ true - ); - - if (callResult && !callResult.argumentErrors) { - winningOverloadIndex = matchIndex; - } - }); - } - }); - - return winningOverloadIndex === undefined ? undefined : matches[winningOverloadIndex].overload; - } - - function validateOverloadedArgTypes( - errorNode: ExpressionNode, - argList: Arg[], - typeResult: TypeResult, - constraints: ConstraintTracker | undefined, - skipUnknownArgCheck: boolean | undefined, - inferenceContext: InferenceContext | undefined - ): CallResult { - const filteredMatchResults: MatchArgsToParamsResult[] = []; - let contextFreeArgTypes: Type[] | undefined; - let isTypeIncomplete = !!typeResult.isIncomplete; - const type = typeResult.type; - const speculativeNode = getSpeculativeNodeForCall(errorNode); - - // Start by evaluating the types of the arguments without any expected - // type. Also, filter the list of overloads based on the number of - // positional and keyword arguments that are present. We do all of this - // speculatively because we don't want to record any types in the type - // cache or record any diagnostics at this stage. - useSpeculativeMode(speculativeNode, () => { - let overloadIndex = 0; - OverloadedType.getOverloads(type).forEach((overload) => { - // Consider only the functions that have the @overload decorator, - // not the final function that omits the overload. This is the - // intended behavior according to PEP 484. - const matchResults = matchArgsToParams( - errorNode, - argList, - { type: overload, isIncomplete: typeResult.isIncomplete }, - overloadIndex - ); - - if (!matchResults.argumentErrors) { - filteredMatchResults.push(matchResults); - } - - overloadIndex++; - }); - }); - - // If there are no possible arg/param matches among the overloads, - // emit an error that includes the argument types. - if (filteredMatchResults.length === 0) { - // Skip the error message if we're in speculative mode because it's very - // expensive, and we're going to suppress the diagnostic anyway. - if (!canSkipDiagnosticForNode(errorNode)) { - const overloads = OverloadedType.getOverloads(type); - const functionName = - overloads.length > 0 && overloads[0].shared.name - ? overloads[0].shared.name - : ''; - const diagAddendum = new DiagnosticAddendum(); - const argTypes = argList.map((t) => { - const typeString = printType(getTypeOfArg(t, /* inferenceContext */ undefined).type); - - if (t.argCategory === ArgCategory.UnpackedList) { - return `*${typeString}`; - } - - if (t.argCategory === ArgCategory.UnpackedDictionary) { - return `**${typeString}`; - } - - return typeString; - }); - - diagAddendum.addMessage(LocAddendum.argumentTypes().format({ types: argTypes.join(', ') })); - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.noOverload().format({ name: functionName }) + diagAddendum.getString(), - errorNode - ); - } - - return { argumentErrors: true, isTypeIncomplete, overloadsUsedForCall: [] }; - } - - // Create a helper function that evaluates the overload that best - // matches the arg/param lists. - function evaluateUsingBestMatchingOverload(skipUnknownArgCheck: boolean, emitNoOverloadFoundError: boolean) { - // Find the match with the smallest argument match score. If there - // are more than one with the same score, use the one with the - // largest index. Later overloads tend to be more general. - const bestMatch = filteredMatchResults.reduce((previous, current) => { - if (current.argumentMatchScore === previous.argumentMatchScore) { - return current.overloadIndex > previous.overloadIndex ? current : previous; - } - return current.argumentMatchScore < previous.argumentMatchScore ? current : previous; - }); - - // If there is more than one filtered match, report that no match - // was possible and emit a diagnostic that provides the most likely. - if (emitNoOverloadFoundError) { - const functionName = bestMatch.overload.shared.name || ''; - const diagnostic = addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.noOverload().format({ name: functionName }), - errorNode - ); - - const overrideDecl = bestMatch.overload.shared.declaration; - if (diagnostic && overrideDecl) { - diagnostic.addRelatedInfo( - LocAddendum.overloadIndex().format({ index: bestMatch.overloadIndex + 1 }), - overrideDecl.uri, - overrideDecl.range - ); - } - } - - const effectiveConstraints = constraints ?? new ConstraintTracker(); - - return validateArgTypesWithContext( - errorNode, - bestMatch, - effectiveConstraints, - skipUnknownArgCheck, - inferenceContext - ); - } - - // If there is only one possible arg/param match among the overloads, - // use the normal type matching mechanism because it is faster and - // will provide a clearer error message. - if (filteredMatchResults.length === 1) { - return evaluateUsingBestMatchingOverload( - /* skipUnknownArgCheck */ false, - /* emitNoOverloadFoundError */ false - ); - } - - let expandedArgTypes: (Type | undefined)[][] | undefined = [argList.map((arg) => undefined)]; - - while (true) { - const callResult = validateOverloadsWithExpandedTypes( - errorNode, - expandedArgTypes, - filteredMatchResults, - constraints, - skipUnknownArgCheck, - inferenceContext - ); - - if (callResult.isTypeIncomplete) { - isTypeIncomplete = true; - } - - if (!callResult.argumentErrors) { - return callResult; - } - - // We didn't find an overload match. Try to expand the next union - // argument type into individual types and retry with the expanded types. - if (!contextFreeArgTypes) { - useSpeculativeMode(getSpeculativeNodeForCall(errorNode), () => { - // Evaluate the types of each argument expression without regard to - // the context. We'll use this to determine whether we need to do - // union expansion. - contextFreeArgTypes = argList.map((arg) => { - if (arg.typeResult) { - return arg.typeResult.type; - } - - if (arg.valueExpression) { - const valueExpressionNode = arg.valueExpression; - return useSpeculativeMode(valueExpressionNode, () => { - return getTypeOfExpression(valueExpressionNode).type; - }); - } - - return AnyType.create(); - }); - }); - } - - expandedArgTypes = expandArgTypes(contextFreeArgTypes!, expandedArgTypes); - - // Check for combinatoric explosion and break out of loop. - if (!expandedArgTypes || expandedArgTypes.length > maxTotalOverloadArgTypeExpansionCount) { - break; - } - } - - // We couldn't find any valid overloads. Skip the error message if we're - // in speculative mode because it's very expensive, and we're going to - // suppress the diagnostic anyway. - if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { - const result = evaluateUsingBestMatchingOverload( - /* skipUnknownArgCheck */ true, - /* emitNoOverloadFoundError */ true - ); - - // Replace the result with an unknown type since we don't know - // what overload should have been used. - result.returnType = UnknownType.create(); - return { ...result, argumentErrors: true }; - } - - return { argumentErrors: true, isTypeIncomplete, overloadsUsedForCall: [] }; - } - - // Replaces each item in the expandedArgTypes with n items where n is - // the number of subtypes in a union or other expandable type. - // The contextFreeArgTypes parameter represents the types of the arguments - // evaluated with no bidirectional type inference (i.e. without the help of - // the corresponding parameter's expected type). If the function returns - // undefined, that indicates that all types have been expanded, and no - // more expansion is possible. - function expandArgTypes( - contextFreeArgTypes: Type[], - expandedArgTypes: (Type | undefined)[][] - ): (Type | undefined)[][] | undefined { - // Find the rightmost already-expanded argument. - let indexToExpand = contextFreeArgTypes.length - 1; - while (indexToExpand >= 0 && !expandedArgTypes[0][indexToExpand]) { - indexToExpand--; - } - - // Move to the next candidate for expansion. - indexToExpand++; - - if (indexToExpand >= contextFreeArgTypes.length) { - return undefined; - } - - let expandedTypes: Type[] | undefined; - while (indexToExpand < contextFreeArgTypes.length) { - // Is this a union type? If so, we can expand it. - const argType = contextFreeArgTypes[indexToExpand]; - - expandedTypes = expandArgType(argType); - if (expandedTypes) { - break; - } - indexToExpand++; - } - - // We have nothing left to expand. - if (!expandedTypes) { - return undefined; - } - - // Expand entry indexToExpand. - const newExpandedArgTypes: (Type | undefined)[][] = []; - - expandedArgTypes.forEach((preExpandedTypes) => { - expandedTypes.forEach((subtype) => { - const expandedTypes = [...preExpandedTypes]; - expandedTypes[indexToExpand] = subtype; - newExpandedArgTypes.push(expandedTypes); - }); - }); - - return newExpandedArgTypes; - } - - function expandArgType(type: Type): Type[] | undefined { - const expandedTypes: Type[] = []; - - // Expand any top-level type variables with constraints. - type = makeTopLevelTypeVarsConcrete(type); - - doForEachSubtype(type, (subtype) => { - if (isClassInstance(subtype)) { - // Expand any bool or Enum literals. - const expandedLiteralTypes = enumerateLiteralsForType(evaluatorInterface, subtype); - if (expandedLiteralTypes && expandedLiteralTypes.length <= maxSingleOverloadArgTypeExpansionCount) { - appendArray(expandedTypes, expandedLiteralTypes); - return; - } - - // Expand any fixed-size tuples. - const expandedTuples = expandTuple(subtype, maxSingleOverloadArgTypeExpansionCount); - if (expandedTuples) { - appendArray(expandedTypes, expandedTuples); - return; - } - } - - expandedTypes.push(subtype); - }); - - return expandedTypes.length > 1 ? expandedTypes : undefined; - } - - // Validates that the arguments can be assigned to the call's parameter - // list, specializes the call based on arg types, and returns the - // specialized type of the return value. If it detects an error along - // the way, it emits a diagnostic and sets argumentErrors to true. - function validateCallArgs( - errorNode: ExpressionNode, - argList: Arg[], - callTypeResult: TypeResult, - constraints: ConstraintTracker | undefined, - skipUnknownArgCheck: boolean | undefined, - inferenceContext: InferenceContext | undefined, - recursionCount = 0 - ): CallResult { - let argumentErrors = false; - let isTypeIncomplete = false; - let specializedInitSelfType: Type | undefined; - const overloadsUsedForCall: FunctionType[] = []; - - if (recursionCount > maxTypeRecursionCount) { - return { returnType: UnknownType.create(), argumentErrors: true, overloadsUsedForCall }; - } - recursionCount++; - - // Viper 方言允许联合类型强转,例如 `(memory_block | t.CPtr)(addr)` - // 或 `(t.CInt | t.CPtr)(x)`。Vipeye 会把联合类型标记为 specialForm - // 并在上方报 objectNotCallable;Viper 是 AOT 静态语言,这类联合强转是 - // 合法语义(C 风格类型转换),因此在 Viper 模式下直接放行并返回 Unknown, - // 避免对联合类型逐子类型校验时产生的误报。 - if ( - isUnion(callTypeResult.type) && - IsViperDialect(AnalyzerNodeInfo.getFileInfo(errorNode).executionEnvironment.dialect) - ) { - return { returnType: UnknownType.create(), argumentErrors: false, overloadsUsedForCall }; - } - - // Special forms are not callable. - if (callTypeResult.type.props?.specialForm) { - const exprNode = errorNode.nodeType === ParseNodeType.Call ? errorNode.d.leftExpr : errorNode; - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.objectNotCallable().format({ - type: printType(callTypeResult.type.props.specialForm, { expandTypeAlias: true }), - }), - exprNode - ); - return { returnType: UnknownType.create(), argumentErrors: true, overloadsUsedForCall }; - } - - let returnType = mapSubtypesExpandTypeVars( - callTypeResult.type, - { sortSubtypes: true }, - (expandedSubtype, unexpandedSubtype, isLastIteration) => { - return useSpeculativeMode( - isLastIteration ? undefined : getSpeculativeNodeForCall(errorNode), - () => { - const callResult = validateCallArgsForSubtype( - errorNode, - argList, - expandedSubtype, - unexpandedSubtype, - !!callTypeResult.isIncomplete, - constraints, - skipUnknownArgCheck, - inferenceContext, - recursionCount - ); - - if (callResult.argumentErrors) { - argumentErrors = true; - } - - if (callResult.isTypeIncomplete) { - isTypeIncomplete = true; - } - - if (callResult.overloadsUsedForCall) { - appendArray(overloadsUsedForCall, callResult.overloadsUsedForCall); - } - - specializedInitSelfType = callResult.specializedInitSelfType; - - return callResult.returnType; - }, - { - allowDiagnostics: true, - } - ); - } - ); - - // If we ended up with a "Never" type because all code paths returned - // undefined due to argument errors, transform the result into an Unknown - // to avoid subsequent false positives. - if (argumentErrors && isNever(returnType) && !returnType.priv.isNoReturn) { - returnType = UnknownType.create(); - } - - return { - argumentErrors, - returnType, - isTypeIncomplete, - specializedInitSelfType, - overloadsUsedForCall, - }; - } - - function validateCallArgsForSubtype( - errorNode: ExpressionNode, - argList: Arg[], - expandedCallType: Type, - unexpandedCallType: Type, - isCallTypeIncomplete: boolean, - constraints: ConstraintTracker | undefined, - skipUnknownArgCheck: boolean | undefined, - inferenceContext: InferenceContext | undefined, - recursionCount: number - ): CallResult { - function touchArgTypes() { - if (!isCallTypeIncomplete) { - argList.forEach((arg) => { - if (arg.valueExpression && !isSpeculativeModeInUse(arg.valueExpression)) { - getTypeOfArg(arg, /* inferenceContext */ undefined); - } - }); - } - } - - switch (expandedCallType.category) { - case TypeCategory.Never: - case TypeCategory.Unknown: - case TypeCategory.Any: { - // Create a dummy callable that accepts all arguments and validate - // that the argument expressions are valid. - const dummyFunctionType = FunctionType.createInstance('', '', '', FunctionTypeFlags.None); - FunctionType.addDefaultParams(dummyFunctionType); - - const dummyCallResult = validateCallForFunction( - errorNode, - argList, - dummyFunctionType, - isCallTypeIncomplete, - constraints, - skipUnknownArgCheck, - inferenceContext - ); - - return { ...dummyCallResult, returnType: expandedCallType }; - } - - case TypeCategory.Function: { - return validateCallForFunction( - errorNode, - argList, - expandedCallType, - isCallTypeIncomplete, - constraints, - skipUnknownArgCheck, - inferenceContext - ); - } - - case TypeCategory.Overloaded: { - return validateCallForOverloaded( - errorNode, - argList, - expandedCallType, - isCallTypeIncomplete, - constraints, - skipUnknownArgCheck, - inferenceContext - ); - } - - case TypeCategory.Class: { - if (isNoneInstance(expandedCallType)) { - addDiagnostic(DiagnosticRule.reportOptionalCall, LocMessage.noneNotCallable(), errorNode); - - touchArgTypes(); - return { argumentErrors: true }; - } - - if (TypeBase.isInstantiable(expandedCallType)) { - return validateCallForInstantiableClass( - errorNode, - argList, - expandedCallType, - unexpandedCallType, - skipUnknownArgCheck, - inferenceContext - ); - } - - return validateCallForClassInstance( - errorNode, - argList, - expandedCallType, - unexpandedCallType, - constraints, - skipUnknownArgCheck, - inferenceContext, - recursionCount - ); - } - - // TypeVars should have been expanded in most cases, - // but we still need to handle the case of Type[T] where - // T is a constrained type that contains a union. We also - // need to handle recursive type aliases. - case TypeCategory.TypeVar: { - return validateCallArgs( - errorNode, - argList, - { type: transformPossibleRecursiveTypeAlias(expandedCallType), isIncomplete: isCallTypeIncomplete }, - constraints, - skipUnknownArgCheck, - inferenceContext, - recursionCount - ); - } - - case TypeCategory.Module: { - addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.moduleNotCallable(), errorNode); - - touchArgTypes(); - return { argumentErrors: true }; - } - } - - touchArgTypes(); - return { argumentErrors: true }; - } - - function validateCallForFunction( - errorNode: ExpressionNode, - argList: Arg[], - type: FunctionType, - isCallTypeIncomplete: boolean, - constraints: ConstraintTracker | undefined, - skipUnknownArgCheck: boolean | undefined, - inferenceContext: InferenceContext | undefined - ): CallResult { - if (TypeBase.isInstantiable(type)) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.callableNotInstantiable().format({ - type: printType(type), - }), - errorNode - ); - return { returnType: undefined, argumentErrors: true }; - } - - // The stdlib collections/__init__.pyi stub file defines namedtuple - // as a function rather than a class, so we need to check for it here. - if (FunctionType.isBuiltIn(type, 'namedtuple')) { - addDiagnostic(DiagnosticRule.reportUntypedNamedTuple, LocMessage.namedTupleNoTypes(), errorNode); - - const result: CallResult = { - returnType: createNamedTupleType(evaluatorInterface, errorNode, argList, /* includesTypes */ false), - }; - - validateArgs(errorNode, argList, { type: type }, constraints, skipUnknownArgCheck, inferenceContext); - - return result; - } - - // Handle the NewType specially, replacing the normal return type. - if (FunctionType.isBuiltIn(type, 'NewType')) { - return { returnType: createNewType(errorNode, argList) }; - } - - const functionResult = validateArgs( - errorNode, - argList, - { type, isIncomplete: isCallTypeIncomplete }, - constraints, - skipUnknownArgCheck, - inferenceContext - ); - - let isTypeIncomplete = !!functionResult.isTypeIncomplete; - let returnType = functionResult.returnType; - - let argumentErrors = !!functionResult.argumentErrors; - if (!argumentErrors) { - // Call the function transform logic to handle special-cased functions. - const transformed = applyFunctionTransform(evaluatorInterface, errorNode, argList, type, { - argumentErrors: !!functionResult.argumentErrors, - returnType: functionResult.returnType ?? UnknownType.create(isTypeIncomplete), - isTypeIncomplete, - }); - - returnType = transformed.returnType; - if (transformed.isTypeIncomplete) { - isTypeIncomplete = true; - } - if (transformed.argumentErrors) { - argumentErrors = true; - } - } - - if (FunctionType.isBuiltIn(type, '__import__')) { - // For the special __import__ type, we'll override the return type to be "Any". - // This is required because we don't know what module was imported, and we don't - // want to fail type checks when accessing members of the resulting module type. - returnType = AnyType.create(); - } - - return { - returnType, - isTypeIncomplete, - argumentErrors, - overloadsUsedForCall: functionResult.overloadsUsedForCall, - specializedInitSelfType: functionResult.specializedInitSelfType, - }; - } - - // Determines whether a symbol is abstract. In an ABC class, this means a function - // is specifically decorated with @abstractmethod. In a protocol class, the rules - // are more complicated and depend on whether the method is defined in a stub file. - function getAbstractSymbolInfo(classType: ClassType, symbolName: string): AbstractSymbol | undefined { - const isProtocolClass = ClassType.isProtocolClass(classType); - - const symbol = ClassType.getSymbolTable(classType).get(symbolName); - if (!symbol) { - return undefined; - } - - // Ignore instance variables. Also, ignore named tuple members, which are - // modeled in vipeye as instance variables, but their runtime implementation - // uses a descriptor object. - if (!symbol.isClassMember() && !symbol.isNamedTupleMemberMember()) { - return undefined; - } - - const lastDecl = getLastTypedDeclarationForSymbol(symbol); - if (!lastDecl) { - return undefined; - } - - // Handle protocol variables specially. - if (isProtocolClass && lastDecl.type === DeclarationType.Variable) { - // If none of the declarations involve assignments, assume it's - // not implemented in the protocol. - const allDecls = symbol.getDeclarations(); - if (!allDecls.some((decl) => decl.type === DeclarationType.Variable && !!decl.inferredTypeSource)) { - return { symbol, symbolName, classType, hasImplementation: false }; - } - } - - if (lastDecl.type !== DeclarationType.Function) { - return undefined; - } - - let isAbstract = false; - const lastFunctionInfo = getFunctionInfoFromDecorators(evaluatorInterface, lastDecl.node, /* isInClass */ true); - if ((lastFunctionInfo.flags & FunctionTypeFlags.AbstractMethod) !== 0) { - isAbstract = true; - } - - const isStubFile = AnalyzerNodeInfo.getFileInfo(lastDecl.node).isStubFile; - - // In an overloaded method, the first overload can also be marked abstract. - // In stub files, there is no implementation, so this is the only way to mark - // an overloaded method as abstract. - const firstDecl = symbol.getDeclarations()[0]; - let firstFunctionInfo: FunctionDecoratorInfo | undefined; - - if (firstDecl !== lastDecl && firstDecl.type === DeclarationType.Function) { - firstFunctionInfo = getFunctionInfoFromDecorators(evaluatorInterface, firstDecl.node, /* isInClass */ true); - if ((firstFunctionInfo.flags & FunctionTypeFlags.AbstractMethod) !== 0) { - isAbstract = true; - } - - // If there's no implementation, assume it's unimplemented. - if (isProtocolClass && (lastFunctionInfo.flags & FunctionTypeFlags.Overloaded) !== 0) { - // If this is a protocol class method defined in a stub file and - // it's not marked abstract, assume it's not abstract and implemented. - if (isProtocolClass && !isAbstract && isStubFile) { - return undefined; - } - - return { symbol, symbolName, classType, hasImplementation: false }; - } - } - - // In a non-protocol class, if the method isn't explicitly marked abstract, - // then it's not abstract. - if (!isProtocolClass && !isAbstract) { - return undefined; - } - - const hasImplementation = - !ParseTreeUtils.isSuiteEmpty(lastDecl.node.d.suite) && !methodAlwaysRaisesNotImplemented(lastDecl); - - // If this is a protocol class, the method isn't explicitly marked - // as abstract, and there is an implementation, then it's a default - // implementation, and it's not considered abstract. If it's in a stub - // file, assume it's implemented in this case. - if (isProtocolClass && !isAbstract) { - if (hasImplementation || isStubFile) { - return undefined; - } - } - - return { symbol, symbolName, classType, hasImplementation }; - } - - function validateCallForOverloaded( - errorNode: ExpressionNode, - argList: Arg[], - expandedCallType: OverloadedType, - isCallTypeIncomplete: boolean, - constraints: ConstraintTracker | undefined, - skipUnknownArgCheck: boolean | undefined, - inferenceContext: InferenceContext | undefined - ): CallResult { - const overloads = OverloadedType.getOverloads(expandedCallType); - // Handle the 'cast' call as a special case. - if ( - overloads.length > 0 && - FunctionType.isBuiltIn(overloads[0], ['typing.cast', 'typing_extensions.cast']) && - argList.length === 2 - ) { - return { returnType: evaluateCastCall(argList, errorNode) }; - } - - const callResult = validateOverloadedArgTypes( - errorNode, - argList, - { type: expandedCallType, isIncomplete: isCallTypeIncomplete }, - constraints, - skipUnknownArgCheck, - inferenceContext - ); - - let returnType = callResult.returnType ?? UnknownType.create(); - let isTypeIncomplete = !!callResult.isTypeIncomplete; - let argumentErrors = !!callResult.argumentErrors; - - if (!argumentErrors) { - // Call the function transform logic to handle special-cased functions. - const transformed = applyFunctionTransform(evaluatorInterface, errorNode, argList, expandedCallType, { - argumentErrors: !!callResult.argumentErrors, - returnType: callResult.returnType ?? UnknownType.create(isTypeIncomplete), - isTypeIncomplete, - }); - - returnType = transformed.returnType; - if (transformed.isTypeIncomplete) { - isTypeIncomplete = true; - } - - if (transformed.argumentErrors) { - argumentErrors = true; - } - } - - return { - returnType, - isTypeIncomplete, - argumentErrors, - overloadsUsedForCall: callResult.overloadsUsedForCall, - specializedInitSelfType: callResult.specializedInitSelfType, - }; - } - - function validateCallForInstantiableClass( - errorNode: ExpressionNode, - argList: Arg[], - expandedCallType: ClassType, - unexpandedCallType: Type, - skipUnknownArgCheck: boolean | undefined, - inferenceContext: InferenceContext | undefined - ): CallResult { - if (expandedCallType.priv.literalValue !== undefined) { - addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.literalNotCallable(), errorNode); - - return { returnType: UnknownType.create(), argumentErrors: true }; - } - - if (ClassType.isBuiltIn(expandedCallType)) { - const className = expandedCallType.priv.aliasName ?? expandedCallType.shared.name; - - // Handle a call to a metaclass explicitly. - if (isInstantiableMetaclass(expandedCallType)) { - if (expandedCallType.priv.typeArgs && expandedCallType.priv.isTypeArgExplicit) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.objectNotCallable().format({ - type: printType(expandedCallType), - }), - errorNode - ); - return { returnType: UnknownType.create(), argumentErrors: true }; - } - - // Validate the constructor arguments. - validateConstructorArgs( - evaluatorInterface, - errorNode, - argList, - expandedCallType, - skipUnknownArgCheck, - inferenceContext - ); - - // The one-parameter form of "type" returns the class - // for the specified object. - if (expandedCallType.shared.name === 'type' && argList.length === 1) { - const argTypeResult = getTypeOfArg(argList[0], /* inferenceContext */ undefined); - const argType = argTypeResult.type; - const returnType = mapSubtypes(argType, (subtype) => { - if (isNever(subtype)) { - return subtype; - } - - if (isClass(subtype)) { - // Specifically handle the case where the subtype is a class-like - // object created by calling NewType. At runtime, it's actually - // a FunctionType object. - if ( - isClassInstance(subtype) && - ClassType.isNewTypeClass(subtype) && - !subtype.priv.includeSubclasses - ) { - if (prefetched?.functionClass) { - return prefetched.functionClass; - } - } - - return convertToInstantiable(stripLiteralValue(subtype)); - } - - if (TypeBase.isInstance(subtype)) { - if (isFunction(subtype) || isTypeVar(subtype)) { - return convertToInstantiable(subtype); - } - } - - return ClassType.specialize(ClassType.cloneAsInstance(expandedCallType), [ - UnknownType.create(), - ]); - }); - - return { returnType, isTypeIncomplete: argTypeResult.isIncomplete }; - } - - if (argList.length >= 2) { - // The two-parameter form of a call to a metaclass returns a new class - // built from the specified base types. - return { - returnType: createClassFromMetaclass(errorNode, argList, expandedCallType) || AnyType.create(), - }; - } - - // If the parameter to type() is not statically known, - // fall back to Any. - return { returnType: AnyType.create() }; - } - - if (className === 'TypeVar') { - return { - returnType: createTypeVarType(errorNode, expandedCallType, argList), - }; - } - - if (className === 'TypeVarTuple') { - return { - returnType: createTypeVarTupleType(errorNode, expandedCallType, argList), - }; - } - - if (className === 'ParamSpec') { - return { - returnType: createParamSpecType(errorNode, expandedCallType, argList), - }; - } - - if (className === 'TypeAliasType') { - const newTypeAlias = createTypeAliasType(errorNode, argList); - if (newTypeAlias) { - return { returnType: newTypeAlias }; - } - } - - if (className === 'NamedTuple') { - const result: CallResult = { - returnType: createNamedTupleType(evaluatorInterface, errorNode, argList, /* includesTypes */ true), - }; - - const initTypeResult = getBoundInitMethod( - evaluatorInterface, - errorNode, - ClassType.cloneAsInstance(expandedCallType), - /* diag */ undefined, - /* additionalFlags */ MemberAccessFlags.Default - ); - - if (initTypeResult && isOverloaded(initTypeResult.type)) { - validateOverloadedArgTypes( - errorNode, - argList, - { type: initTypeResult.type }, - /* constraints */ undefined, - skipUnknownArgCheck, - /* inferenceContext */ undefined - ); - } - - return result; - } - - if (className === 'NewType') { - return { returnType: createNewType(errorNode, argList) }; - } - - // Handle sentinel calls specially. - if ( - expandedCallType.shared.fullName === 'builtins.sentinel' || - expandedCallType.shared.fullName === 'typing_extensions.sentinel' || - expandedCallType.shared.fullName === 'typing_extensions.Sentinel' - ) { - return { returnType: createSentinelType(evaluatorInterface, errorNode, argList) }; - } - - if (ClassType.isSpecialFormClass(expandedCallType)) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.typeNotIntantiable().format({ type: className }), - errorNode - ); - - return { returnType: UnknownType.create(), argumentErrors: true }; - } - - if (className === 'TypedDict') { - return { returnType: createTypedDictType(evaluatorInterface, errorNode, expandedCallType, argList) }; - } - - if (className === 'auto' && argList.length === 0) { - return { returnType: getEnumAutoValueType(evaluatorInterface, errorNode) }; - } - } - - // Is it a call to an Enum class factory? - if ( - isClass(expandedCallType) && - expandedCallType.shared.effectiveMetaclass && - isClass(expandedCallType.shared.effectiveMetaclass) && - isEnumMetaclass(expandedCallType.shared.effectiveMetaclass) && - !isEnumClassWithMembers(evaluatorInterface, expandedCallType) - ) { - return { - returnType: - createEnumType(evaluatorInterface, errorNode, expandedCallType, argList) ?? - convertToInstance(unexpandedCallType), - }; - } - - if (ClassType.supportsAbstractMethods(expandedCallType)) { - const abstractSymbols = getAbstractSymbols(expandedCallType); - - if ( - abstractSymbols.length > 0 && - !expandedCallType.priv.includeSubclasses && - !isTypeVar(unexpandedCallType) - ) { - // If the class is abstract, it can't be instantiated. - const diagAddendum = new DiagnosticAddendum(); - const errorsToDisplay = 2; - - abstractSymbols.forEach((abstractMethod, index) => { - if (index === errorsToDisplay) { - diagAddendum.addMessage( - LocAddendum.memberIsAbstractMore().format({ - count: abstractSymbols.length - errorsToDisplay, - }) - ); - } else if (index < errorsToDisplay) { - if (isInstantiableClass(abstractMethod.classType)) { - const className = abstractMethod.classType.shared.name; - diagAddendum.addMessage( - LocAddendum.memberIsAbstract().format({ - type: className, - name: abstractMethod.symbolName, - }) - ); - } - } - }); - - addDiagnostic( - DiagnosticRule.reportAbstractUsage, - LocMessage.instantiateAbstract().format({ - type: expandedCallType.shared.name, - }) + diagAddendum.getString(), - errorNode - ); - } - } - - if (ClassType.isProtocolClass(expandedCallType) && !expandedCallType.priv.includeSubclasses) { - // If the class is a protocol, it can't be instantiated. - addDiagnostic( - DiagnosticRule.reportAbstractUsage, - LocMessage.instantiateProtocol().format({ - type: expandedCallType.shared.name, - }), - errorNode - ); - } - - // Assume this is a call to the constructor. - const constructorResult = validateConstructorArgs( - evaluatorInterface, - errorNode, - argList, - expandedCallType, - skipUnknownArgCheck, - inferenceContext - ); - - const overloadsUsedForCall = constructorResult.overloadsUsedForCall; - const argumentErrors = constructorResult.argumentErrors; - const isTypeIncomplete = constructorResult.isTypeIncomplete; - - let returnType = constructorResult.returnType; - - // If the expandedCallType originated from a TypeVar, convert - // the constructed type back to the TypeVar. For example, if - // we have `cls: Type[_T]` followed by `_T()`. - if (isTypeVar(unexpandedCallType)) { - returnType = convertToInstance(unexpandedCallType); - } - - // If we instantiated the "deprecated" class, attach the deprecation - // message to the instance. - if ( - errorNode.nodeType === ParseNodeType.Call && - returnType && - isClassInstance(returnType) && - ClassType.isBuiltIn(returnType, 'deprecated') - ) { - returnType = ClassType.cloneForDeprecatedInstance(returnType, getDeprecatedMessageFromCall(errorNode)); - } - - // If we instantiated a type, transform it into a class. - // This can happen if someone directly instantiates a metaclass - // deriving from type. - if ( - returnType && - isClassInstance(returnType) && - returnType.shared.mro.some( - (baseClass) => isInstantiableClass(baseClass) && ClassType.isBuiltIn(baseClass, 'type') - ) - ) { - let newClassName = '__class_' + returnType.shared.name; - if (argList.length === 3) { - const firstArgType = getTypeOfArg(argList[0], /* inferenceContext */ undefined).type; - - if ( - isClassInstance(firstArgType) && - ClassType.isBuiltIn(firstArgType, 'str') && - typeof firstArgType.priv.literalValue === 'string' - ) { - newClassName = firstArgType.priv.literalValue; - } - } - - const newClassType = ClassType.createInstantiable( - newClassName, - '', - '', - AnalyzerNodeInfo.getFileInfo(errorNode).fileUri, - ClassTypeFlags.None, - ParseTreeUtils.getTypeSourceId(errorNode), - ClassType.cloneAsInstantiable(returnType), - ClassType.cloneAsInstantiable(returnType) - ); - newClassType.shared.baseClasses.push(getBuiltInType(errorNode, 'object')); - newClassType.shared.effectiveMetaclass = expandedCallType; - newClassType.shared.declaration = returnType.shared.declaration; - - computeMroLinearization(newClassType); - returnType = newClassType; - } - - return { returnType, overloadsUsedForCall, argumentErrors, isTypeIncomplete }; - } - - function validateCallForClassInstance( - errorNode: ExpressionNode, - argList: Arg[], - expandedCallType: ClassType, - unexpandedCallType: Type, - constraints: ConstraintTracker | undefined, - skipUnknownArgCheck: boolean | undefined, - inferenceContext: InferenceContext | undefined, - recursionCount: number - ): CallResult { - const callDiag = new DiagnosticAddendum(); - const callMethodResult = getTypeOfBoundMember( - errorNode, - expandedCallType, - '__call__', - /* usage */ undefined, - callDiag, - MemberAccessFlags.SkipInstanceMembers | MemberAccessFlags.SkipAttributeAccessOverride, - /* selfType */ undefined, - recursionCount - ); - const callMethodType = callMethodResult?.type; - - if (!callMethodType || callMethodResult.typeErrors) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.objectNotCallable().format({ - type: printType(expandedCallType), - }) + callDiag.getString(), - errorNode - ); - - return { returnType: UnknownType.create(), argumentErrors: true }; - } - - const callResult = validateCallArgs( - errorNode, - argList, - { type: callMethodType }, - constraints, - skipUnknownArgCheck, - inferenceContext, - recursionCount - ); - - let returnType = callResult.returnType ?? UnknownType.create(); - if ( - isTypeVar(unexpandedCallType) && - TypeBase.isInstantiable(unexpandedCallType) && - isClass(expandedCallType) && - ClassType.isBuiltIn(expandedCallType, 'type') - ) { - // Handle the case where a type[T] is being called. We presume this - // will instantiate an object of type T. - returnType = convertToInstance(unexpandedCallType); - } - - return { - returnType, - argumentErrors: callResult.argumentErrors, - overloadsUsedForCall: callResult.overloadsUsedForCall, - }; - } - - // Evaluates the type of the "cast" call. - function evaluateCastCall(argList: Arg[], errorNode: ExpressionNode) { - if (argList[0].argCategory !== ArgCategory.Simple && argList[0].valueExpression) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.unpackInAnnotation(), - argList[0].valueExpression - ); - } - - // Verify that the cast is necessary. - let castToType = getTypeOfArgExpectingType(argList[0], { typeExpression: true }).type; - - const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(errorNode); - castToType = makeTypeVarsBound(castToType, liveScopeIds); - - let castFromType = getTypeOfArg(argList[1], /* inferenceContext */ undefined).type; - - if (castFromType.props?.specialForm) { - castFromType = castFromType.props.specialForm; - } - - if (TypeBase.isInstantiable(castToType) && !isUnknown(castToType)) { - if ( - isTypeSame(convertToInstance(castToType), castFromType, { - ignorePseudoGeneric: true, - }) - ) { - addDiagnostic( - DiagnosticRule.reportUnnecessaryCast, - LocMessage.unnecessaryCast().format({ - type: printType(castFromType), - }), - errorNode - ); - } - } - - return convertToInstance(castToType); - } - - // Expands any unpacked tuples within an argument list. - function expandArgList(argList: Arg[]): Arg[] { - const expandedArgList: Arg[] = []; - - for (const arg of argList) { - if (arg.argCategory === ArgCategory.UnpackedList) { - const argType = getTypeOfArg(arg, /* inferenceContext */ undefined).type; - - // If this is a tuple with specified element types, use those - // specified types rather than using the more generic iterator - // type which will be a union of all element types. - const combinedArgType = combineSameSizedTuples( - makeTopLevelTypeVarsConcrete(argType), - prefetched?.tupleClass - ); - - if (isClassInstance(combinedArgType) && isTupleClass(combinedArgType)) { - const tupleTypeArgs = combinedArgType.priv.tupleTypeArgs ?? []; - - if (tupleTypeArgs.length !== 1 || !tupleTypeArgs[0].isUnbounded) { - for (const tupleTypeArg of tupleTypeArgs) { - if (tupleTypeArg.isUnbounded) { - expandedArgList.push({ - ...arg, - argCategory: ArgCategory.UnpackedList, - valueExpression: undefined, - typeResult: { - type: makeTupleObject(evaluatorInterface, [tupleTypeArg]), - }, - }); - } else { - expandedArgList.push({ - ...arg, - argCategory: ArgCategory.Simple, - valueExpression: undefined, - typeResult: { - type: tupleTypeArg.type, - }, - }); - } - } - continue; - } - } - } - - expandedArgList.push(arg); - } - - return expandedArgList; - } - - // Matches the arguments passed to a function to the corresponding parameters in that - // function. This matching is done based on positions and keywords. Type evaluation and - // validation is left to the caller. - // This logic is based on PEP 3102: https://www.python.org/dev/peps/pep-3102/ - function matchArgsToParams( - errorNode: ExpressionNode, - argList: Arg[], - typeResult: TypeResult, - overloadIndex: number - ): MatchArgsToParamsResult { - const overload = typeResult.type; - const paramDetails = getParamListDetails(overload, { disallowExtraKwargsForTd: true }); - const paramSpec = FunctionType.getParamSpecFromArgsKwargs(overload); - - let argIndex = 0; - let unpackedArgOfUnknownLength = false; - let unpackedArgMapsToVariadic = false; - let reportedArgError = false; - let isTypeIncomplete = !!typeResult.isIncomplete; - let isTypeVarTupleFullyMatched = false; - - // Expand any unpacked tuples in the arg list. - argList = expandArgList(argList); - - // Construct an object that racks which parameters have been assigned arguments. - const paramTracker = new ParamAssignmentTracker(paramDetails.params); - - let positionalOnlyLimitIndex = paramDetails.positionOnlyParamCount; - let positionParamLimitIndex = paramDetails.firstKeywordOnlyIndex ?? paramDetails.params.length; - - const varArgListParamIndex = paramDetails.argsIndex; - const varArgDictParamIndex = paramDetails.kwargsIndex; - - // Is this an function that uses the *args and **kwargs - // from a param spec? If so, we need to treat all positional parameters - // prior to the *args as positional-only according to PEP 612. - let paramSpecArgList: Arg[] | undefined; - let paramSpecTarget: ParamSpecType | undefined; - let hasParamSpecArgsKwargs = false; - - // Determine how many positional args are being passed before - // we see a keyword arg. - let positionalArgCount = argList.findIndex( - (arg) => arg.argCategory === ArgCategory.UnpackedDictionary || arg.name !== undefined - ); - if (positionalArgCount < 0) { - positionalArgCount = argList.length; - } - - if (varArgListParamIndex !== undefined && varArgDictParamIndex !== undefined) { - assert(paramDetails.params[varArgListParamIndex], 'varArgListParamIndex params entry is undefined'); - const varArgListParamType = paramDetails.params[varArgListParamIndex].type; - assert(paramDetails.params[varArgDictParamIndex], 'varArgDictParamIndex params entry is undefined'); - const varArgDictParamType = paramDetails.params[varArgDictParamIndex].type; - - if ( - isParamSpec(varArgListParamType) && - varArgListParamType.priv.paramSpecAccess === 'args' && - isParamSpec(varArgDictParamType) && - varArgDictParamType.priv.paramSpecAccess === 'kwargs' && - varArgListParamType.shared.name === varArgDictParamType.shared.name - ) { - hasParamSpecArgsKwargs = true; - - // Does this function define the param spec, or is it an inner - // function nested within another function that defines the param - // spec? We need to handle these two cases differently. - const paramSpecScopeId = varArgListParamType.priv.scopeId; - - if (getTypeVarScopeIds(overload).some((id) => id === paramSpecScopeId)) { - paramSpecArgList = []; - paramSpecTarget = TypeVarType.cloneForParamSpecAccess(varArgListParamType, /* access */ undefined); - } else { - positionalOnlyLimitIndex = varArgListParamIndex; - positionalArgCount = Math.min(varArgListParamIndex, positionalArgCount); - positionParamLimitIndex = varArgListParamIndex; - } - } - } else if (paramSpec) { - if (getTypeVarScopeIds(overload).some((id) => id === paramSpec.priv.scopeId)) { - hasParamSpecArgsKwargs = true; - paramSpecArgList = []; - paramSpecTarget = paramSpec; - } - } - - // If there are keyword arguments present after a *args argument, - // the keyword arguments may target one or more parameters that are positional. - // In this case, we will limit the number of positional parameters so the - // *args doesn't consume them all. - if (argList.some((arg) => arg.argCategory === ArgCategory.UnpackedList)) { - argList.forEach((arg) => { - if (arg.name) { - const keywordParamIndex = paramDetails.params.findIndex((paramInfo) => { - assert(paramInfo, 'paramInfo entry is undefined fork kwargs check'); - return ( - paramInfo.param.name === arg.name!.d.value && - paramInfo.param.category === ParamCategory.Simple - ); - }); - - // Is this a parameter that can be interpreted as either a keyword or a positional? - // If so, we'll treat it as a keyword parameter in this case because it's being - // targeted by a keyword argument. - if (keywordParamIndex >= 0 && keywordParamIndex >= positionalOnlyLimitIndex) { - if (positionParamLimitIndex < 0 || keywordParamIndex < positionParamLimitIndex) { - positionParamLimitIndex = keywordParamIndex; - } - } - } - }); - } - - // If we didn't see any special cases, then all parameters are positional. - if (positionParamLimitIndex < 0) { - positionParamLimitIndex = paramDetails.params.length; - } - - let validateArgTypeParams: ValidateArgTypeParams[] = []; - - let activeParam: FunctionParam | undefined; - function trySetActive(arg: Arg, param: FunctionParam) { - if (arg.active) { - activeParam = param; - } - } - - const foundUnpackedListArg = argList.find((arg) => arg.argCategory === ArgCategory.UnpackedList) !== undefined; - - // Map the positional args to parameters. - let paramIndex = 0; - - while (argIndex < positionalArgCount) { - if (argIndex < positionalOnlyLimitIndex && argList[argIndex].name) { - const nameNode = argList[argIndex].name; - if (nameNode) { - addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.argPositional(), nameNode); - reportedArgError = true; - } - } - - const remainingArgCount = positionalArgCount - argIndex; - const remainingParamCount = positionParamLimitIndex - paramIndex - 1; - - if (paramIndex >= positionParamLimitIndex) { - if (paramSpecArgList) { - // Push the remaining positional args onto the param spec arg list. - while (argIndex < positionalArgCount) { - paramSpecArgList.push(argList[argIndex]); - argIndex++; - } - } else { - let tooManyPositionals = false; - - if (argList[argIndex].argCategory === ArgCategory.UnpackedList) { - // If this is an unpacked iterable, we will conservatively assume that it - // might have zero iterations unless we can tell from its type that it - // definitely has at least one iterable value. - const argType = getTypeOfArg(argList[argIndex], /* inferenceContext */ undefined).type; - - if ( - isClassInstance(argType) && - isTupleClass(argType) && - !isUnboundedTupleClass(argType) && - argType.priv.tupleTypeArgs !== undefined && - argType.priv.tupleTypeArgs.length > 0 - ) { - tooManyPositionals = true; - } else { - unpackedArgOfUnknownLength = true; - } - } else { - tooManyPositionals = true; - } - - if (tooManyPositionals) { - if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - positionParamLimitIndex === 1 - ? LocMessage.argPositionalExpectedOne() - : LocMessage.argPositionalExpectedCount().format({ - expected: positionParamLimitIndex, - }), - argList[argIndex].valueExpression ?? errorNode - ); - } - reportedArgError = true; - } - } - break; - } - - if (paramIndex >= paramDetails.params.length) { - break; - } - - assert(paramDetails.params[paramIndex], 'paramIndex params entry is undefined'); - const paramInfo = paramDetails.params[paramIndex]; - const paramType = paramInfo.type; - const paramName = paramInfo.param.name; - - const isParamVariadic = paramInfo.param.category === ParamCategory.ArgsList && isUnpacked(paramType); - - if (argList[argIndex].argCategory === ArgCategory.UnpackedList) { - let isArgCompatibleWithVariadic = false; - - const argTypeResult = getTypeOfArg(argList[argIndex], /* inferenceContext */ undefined); - - let listElementType: Type | undefined; - let enforceIterable = false; - let advanceToNextArg = false; - - // Handle the case where *args is being passed to a function defined - // with a ParamSpec and a Concatenate operator. PEP 612 indicates that - // all positional parameters specified in the Concatenate must be - // filled explicitly. - if (paramIndex < positionParamLimitIndex) { - if ( - isParamSpec(argTypeResult.type) && - argTypeResult.type.priv.paramSpecAccess === 'args' && - paramInfo.param.category !== ParamCategory.ArgsList - ) { - if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - positionParamLimitIndex === 1 - ? LocMessage.argPositionalExpectedOne() - : LocMessage.argPositionalExpectedCount().format({ - expected: positionParamLimitIndex, - }), - argList[argIndex].valueExpression ?? errorNode - ); - } - reportedArgError = true; - } - } - - const argType = argTypeResult.type; - - if (isParamVariadic && isUnpackedTypeVarTuple(argType)) { - // Allow an unpacked TypeVarTuple arg to satisfy an - // unpacked TypeVarTuple param. - listElementType = argType; - isArgCompatibleWithVariadic = true; - advanceToNextArg = true; - isTypeVarTupleFullyMatched = true; - } else if ( - isClassInstance(argType) && - isTupleClass(argType) && - argType.priv.tupleTypeArgs && - argType.priv.tupleTypeArgs.length === 1 && - isUnpackedTypeVarTuple(argType.priv.tupleTypeArgs[0].type) - ) { - // Handle the case where an unpacked TypeVarTuple has - // been packaged into a tuple. - listElementType = argType.priv.tupleTypeArgs[0].type; - isArgCompatibleWithVariadic = true; - advanceToNextArg = true; - isTypeVarTupleFullyMatched = true; - } else if (isParamVariadic && isClassInstance(argType) && isTupleClass(argType)) { - // Handle the case where an unpacked tuple argument is - // matched to a TypeVarTuple parameter. - isArgCompatibleWithVariadic = true; - advanceToNextArg = true; - - // Determine whether we should treat the variadic type as fully matched. - // This depends on how many args and unmatched parameters exist. - if (remainingArgCount < remainingParamCount) { - isTypeVarTupleFullyMatched = true; - } - - listElementType = ClassType.cloneForUnpacked(argType); - } else if (isParamSpec(argType) && argType.priv.paramSpecAccess === 'args') { - listElementType = undefined; - } else { - listElementType = getTypeOfIterator( - { type: argType, isIncomplete: argTypeResult.isIncomplete }, - /* isAsync */ false, - errorNode, - /* emitNotIterableError */ false - )?.type; - - if (!listElementType) { - enforceIterable = true; - } - - unpackedArgOfUnknownLength = true; - - if (paramInfo.param.category === ParamCategory.ArgsList) { - unpackedArgMapsToVariadic = true; - } - - if (isParamVariadic && listElementType) { - isArgCompatibleWithVariadic = true; - listElementType = makeTupleObject( - evaluatorInterface, - [{ type: listElementType, isUnbounded: true }], - /* isUnpacked */ true - ); - } - } - - const funcArg: Arg | undefined = listElementType - ? { - argCategory: ArgCategory.Simple, - typeResult: { type: listElementType, isIncomplete: argTypeResult.isIncomplete }, - } - : { ...argList[argIndex], enforceIterable }; - - if (argTypeResult.isIncomplete) { - isTypeIncomplete = true; - } - - // It's not allowed to use unpacked arguments with a variadic *args - // parameter unless the argument is a variadic arg as well. - if (isParamVariadic && !isArgCompatibleWithVariadic) { - if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.unpackedArgWithVariadicParam(), - argList[argIndex].valueExpression || errorNode - ); - } - reportedArgError = true; - } else { - if (paramSpecArgList && paramInfo.param.category !== ParamCategory.Simple) { - paramSpecArgList.push(argList[argIndex]); - } - - if (funcArg) { - validateArgTypeParams.push({ - paramCategory: paramInfo.param.category, - paramType, - requiresTypeVarMatching: requiresSpecialization(paramType), - argument: funcArg, - errorNode: argList[argIndex].valueExpression ?? errorNode, - paramName, - isParamNameSynthesized: FunctionParam.isNameSynthesized(paramInfo.param), - mapsToVarArgList: isParamVariadic && remainingArgCount > remainingParamCount, - }); - } - } - - trySetActive(argList[argIndex], paramDetails.params[paramIndex].param); - - // Note that the parameter has received an argument. - if (paramName && paramDetails.params[paramIndex].param.category === ParamCategory.Simple) { - paramTracker.markArgReceived(paramInfo); - } - - if (advanceToNextArg || paramDetails.params[paramIndex].param.category === ParamCategory.ArgsList) { - argIndex++; - } - - if ( - isTypeVarTupleFullyMatched || - paramDetails.params[paramIndex].param.category !== ParamCategory.ArgsList - ) { - paramIndex++; - } - } else if (paramDetails.params[paramIndex].param.category === ParamCategory.ArgsList) { - trySetActive(argList[argIndex], paramDetails.params[paramIndex].param); - - if (paramSpecArgList) { - paramSpecArgList.push(argList[argIndex]); - argIndex++; - } else { - let paramCategory = paramDetails.params[paramIndex].param.category; - let effectiveParamType = paramType; - const paramName = paramDetails.params[paramIndex].param.name; - - if ( - isUnpackedClass(paramType) && - paramType.priv.tupleTypeArgs && - paramType.priv.tupleTypeArgs.length > 0 - ) { - effectiveParamType = paramType.priv.tupleTypeArgs[0].type; - } - - paramCategory = isUnpacked(effectiveParamType) ? ParamCategory.ArgsList : ParamCategory.Simple; - - if (remainingArgCount <= remainingParamCount) { - if (remainingArgCount < remainingParamCount) { - if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { - // Have we run out of arguments and still have parameters left to fill? - addDiagnostic( - DiagnosticRule.reportCallIssue, - remainingArgCount === 1 - ? LocMessage.argMorePositionalExpectedOne() - : LocMessage.argMorePositionalExpectedCount().format({ - expected: remainingArgCount, - }), - argList[argIndex].valueExpression || errorNode - ); - } - reportedArgError = true; - } - - paramIndex++; - } else { - validateArgTypeParams.push({ - paramCategory, - paramType: effectiveParamType, - requiresTypeVarMatching: requiresSpecialization(paramType), - argument: argList[argIndex], - errorNode: argList[argIndex].valueExpression || errorNode, - paramName, - isParamNameSynthesized: FunctionParam.isNameSynthesized( - paramDetails.params[paramIndex].param - ), - mapsToVarArgList: true, - }); - - argIndex++; - } - } - } else { - const paramInfo = paramDetails.params[paramIndex]; - const paramName = paramInfo.param.name; - - validateArgTypeParams.push({ - paramCategory: paramInfo.param.category, - paramType, - requiresTypeVarMatching: requiresSpecialization(paramType), - argument: argList[argIndex], - errorNode: argList[argIndex].valueExpression || errorNode, - paramName, - isParamNameSynthesized: FunctionParam.isNameSynthesized(paramInfo.param), - }); - trySetActive(argList[argIndex], paramInfo.param); - - // Note that the parameter has received an argument. - paramTracker.markArgReceived(paramInfo); - - argIndex++; - paramIndex++; - } - } - - // If there weren't enough positional arguments to populate all of the - // positional-only parameters and the next positional-only parameter is - // an unbounded tuple, skip past it. - let skippedArgsParam = false; - if ( - positionalOnlyLimitIndex >= 0 && - paramIndex < positionalOnlyLimitIndex && - paramIndex < paramDetails.params.length && - paramDetails.params[paramIndex].param.category === ParamCategory.ArgsList && - !isParamSpec(paramDetails.params[paramIndex].type) - ) { - paramIndex++; - skippedArgsParam = true; - } - - // Check if there weren't enough positional arguments to populate all of - // the positional-only parameters. - if ( - positionalOnlyLimitIndex >= 0 && - paramIndex < positionalOnlyLimitIndex && - (!foundUnpackedListArg || hasParamSpecArgsKwargs) - ) { - const firstParamWithDefault = paramDetails.params.findIndex((paramInfo) => !!paramInfo.defaultType); - const positionOnlyWithoutDefaultsCount = - firstParamWithDefault >= 0 && firstParamWithDefault < positionalOnlyLimitIndex - ? firstParamWithDefault - : positionalOnlyLimitIndex; - - // Calculate the number of remaining positional parameters to report. - let argsRemainingCount = positionOnlyWithoutDefaultsCount - positionalArgCount; - if (skippedArgsParam) { - // If we skipped an args parameter above, reduce the count by one - // because it's permitted to pass zero arguments to *args. - argsRemainingCount--; - } - - const firstArgsParam = paramDetails.params.findIndex( - (paramInfo) => paramInfo.param.category === ParamCategory.ArgsList && !isParamSpec(paramInfo.type) - ); - if (firstArgsParam >= paramIndex && firstArgsParam < positionalOnlyLimitIndex) { - // If there is another args parameter beyond the current param index, - // reduce the count by one because it's permitted to pass zero arguments - // to *args. - argsRemainingCount--; - } - - if (argsRemainingCount > 0) { - if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - argsRemainingCount === 1 - ? LocMessage.argMorePositionalExpectedOne() - : LocMessage.argMorePositionalExpectedCount().format({ - expected: argsRemainingCount, - }), - argList.length > positionalArgCount - ? argList[positionalArgCount].valueExpression || errorNode - : errorNode - ); - } - reportedArgError = true; - } - } - - if (!reportedArgError) { - let unpackedDictKeyNames: string[] | undefined; - let unpackedDictArgType: Type | undefined; - - // Now consume any keyword arguments. - while (argIndex < argList.length) { - if (argList[argIndex].argCategory === ArgCategory.UnpackedDictionary) { - // Verify that the type used in this expression is a SupportsKeysAndGetItem[str, T]. - const argTypeResult = getTypeOfArg( - argList[argIndex], - makeInferenceContext(paramDetails.unpackedKwargsTypedDictType) - ); - const argType = argTypeResult.type; - - if (argTypeResult.isIncomplete) { - isTypeIncomplete = true; - } - - if (isAnyOrUnknown(argType)) { - unpackedDictArgType = argType; - } else if (isClassInstance(argType) && ClassType.isTypedDictClass(argType)) { - // Handle the special case where it is a TypedDict and we know which - // keys are present. - const tdEntries = getTypedDictMembersForClass(evaluatorInterface, argType); - const diag = new DiagnosticAddendum(); - - tdEntries.knownItems.forEach((entry, name) => { - const paramEntry = paramTracker.lookupName(name); - if (paramEntry) { - if (paramEntry.argsReceived > 0) { - diag.addMessage(LocMessage.paramAlreadyAssigned().format({ name })); - } else { - paramEntry.argsReceived++; - - const paramInfoIndex = paramDetails.params.findIndex( - (paramInfo) => paramInfo.param.name === name - ); - assert(paramInfoIndex >= 0); - const paramType = paramDetails.params[paramInfoIndex].type; - - validateArgTypeParams.push({ - paramCategory: ParamCategory.Simple, - paramType, - requiresTypeVarMatching: requiresSpecialization(paramType), - argument: { - argCategory: ArgCategory.Simple, - typeResult: { type: entry.valueType }, - }, - errorNode: argList[argIndex].valueExpression ?? errorNode, - paramName: name, - }); - } - } else if (paramDetails.kwargsIndex !== undefined) { - const paramType = paramDetails.params[paramDetails.kwargsIndex].type; - validateArgTypeParams.push({ - paramCategory: ParamCategory.KwargsDict, - paramType, - requiresTypeVarMatching: requiresSpecialization(paramType), - argument: { - argCategory: ArgCategory.Simple, - typeResult: { type: entry.valueType }, - }, - errorNode: argList[argIndex].valueExpression ?? errorNode, - paramName: name, - }); - - // Remember that this parameter has already received a value. - paramTracker.addKeywordParam(name, paramDetails.params[paramDetails.kwargsIndex]); - } else { - // If the function doesn't have a **kwargs parameter, we need to emit an error. - // However, it's possible that there was a **kwargs but it was eliminated by - // getParamListDetails because it was associated with an unpacked TypedDict. - // In this case, we can skip the error. - if (!paramDetails.hasUnpackedTypedDict) { - diag.addMessage(LocMessage.paramNameMissing().format({ name })); - } - } - }); - - const extraItemsType = tdEntries.extraItems?.valueType ?? getObjectType(); - if (!isNever(extraItemsType)) { - if (paramDetails.kwargsIndex !== undefined) { - const kwargsParam = paramDetails.params[paramDetails.kwargsIndex]; - - validateArgTypeParams.push({ - paramCategory: ParamCategory.KwargsDict, - paramType: kwargsParam.type, - requiresTypeVarMatching: requiresSpecialization(kwargsParam.type), - argument: { - argCategory: ArgCategory.UnpackedDictionary, - typeResult: { type: extraItemsType }, - }, - errorNode: argList[argIndex].valueExpression ?? errorNode, - paramName: kwargsParam.param.name, - }); - } - } - - if (!diag.isEmpty()) { - if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.unpackedTypedDictArgument() + diag.getString(), - argList[argIndex].valueExpression || errorNode - ); - } - reportedArgError = true; - } - } else if (paramSpec && isParamSpecKwargs(paramSpec, argType)) { - unpackedDictArgType = AnyType.create(); - - if (!paramSpecArgList) { - validateArgTypeParams.push({ - paramCategory: ParamCategory.KwargsDict, - paramType: paramSpec, - requiresTypeVarMatching: false, - argument: argList[argIndex], - argType: isParamSpec(argType) ? undefined : AnyType.create(), - errorNode: argList[argIndex].valueExpression || errorNode, - }); - } - } else { - const strObjType = getBuiltInObject(errorNode, 'str'); - - if ( - prefetched?.supportsKeysAndGetItemClass && - isInstantiableClass(prefetched.supportsKeysAndGetItemClass) && - strObjType && - isClassInstance(strObjType) - ) { - const mappingConstraints = new ConstraintTracker(); - let isValidMappingType = false; - - // If this was a TypeVar (e.g. for pseudo-generic classes), - // don't emit this error. - if (isTypeVar(argType)) { - isValidMappingType = true; - } else if ( - assignType( - ClassType.cloneAsInstance(prefetched.supportsKeysAndGetItemClass), - argType, - /* diag */ undefined, - mappingConstraints - ) - ) { - const specializedMapping = solveAndApplyConstraints( - prefetched.supportsKeysAndGetItemClass, - mappingConstraints - ) as ClassType; - const typeArgs = specializedMapping.priv.typeArgs; - if (typeArgs && typeArgs.length >= 2) { - if (assignType(strObjType, typeArgs[0])) { - isValidMappingType = true; - } - - unpackedDictKeyNames = []; - doForEachSubtype(typeArgs[0], (keyType) => { - if (isClassInstance(keyType) && typeof keyType.priv.literalValue === 'string') { - unpackedDictKeyNames?.push(keyType.priv.literalValue); - } else { - unpackedDictKeyNames = undefined; - } - }); - - unpackedDictArgType = typeArgs[1]; - } else { - isValidMappingType = true; - unpackedDictArgType = UnknownType.create(); - } - } - - unpackedArgOfUnknownLength = true; - - if (paramDetails.kwargsIndex !== undefined && unpackedDictArgType) { - const paramType = paramDetails.params[paramDetails.kwargsIndex].type; - validateArgTypeParams.push({ - paramCategory: ParamCategory.Simple, - paramType, - requiresTypeVarMatching: requiresSpecialization(paramType), - argType: unpackedDictArgType, - argument: argList[argIndex], - errorNode: argList[argIndex].valueExpression || errorNode, - paramName: paramDetails.params[paramDetails.kwargsIndex].param.name, - }); - - unpackedArgMapsToVariadic = true; - } - - if (!isValidMappingType) { - if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.unpackedDictArgumentNotMapping(), - argList[argIndex].valueExpression || errorNode - ); - } - reportedArgError = true; - } - } - } - - if (paramSpecArgList) { - paramSpecArgList.push(argList[argIndex]); - } - } else { - // Protect against the case where a non-keyword argument appears after - // a keyword argument. This will have already been reported as a parse - // error, but we need to protect against it here. - const paramName = argList[argIndex].name; - if (paramName) { - const paramNameValue = paramName.d.value; - const paramEntry = paramTracker.lookupName(paramNameValue); - - if (paramEntry) { - if (paramEntry.argsReceived > 0) { - if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.paramAlreadyAssigned().format({ name: paramNameValue }), - paramName - ); - } - reportedArgError = true; - } else { - paramEntry.argsReceived++; - - const paramInfoIndex = paramDetails.params.findIndex( - (paramInfo) => - paramInfo.param.name === paramNameValue && - paramInfo.kind !== ParamKind.Positional - ); - assert(paramInfoIndex >= 0); - const paramType = paramDetails.params[paramInfoIndex].type; - - validateArgTypeParams.push({ - paramCategory: ParamCategory.Simple, - paramType, - requiresTypeVarMatching: requiresSpecialization(paramType), - argument: argList[argIndex], - errorNode: argList[argIndex].valueExpression ?? errorNode, - paramName: paramNameValue, - }); - trySetActive(argList[argIndex], paramDetails.params[paramInfoIndex].param); - } - } else if (paramSpecArgList) { - paramSpecArgList.push(argList[argIndex]); - } else if (paramDetails.kwargsIndex !== undefined) { - const paramType = paramDetails.params[paramDetails.kwargsIndex].type; - if (isParamSpec(paramType)) { - if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.paramNameMissing().format({ name: paramName.d.value }), - paramName - ); - } - reportedArgError = true; - } else { - validateArgTypeParams.push({ - paramCategory: ParamCategory.KwargsDict, - paramType, - requiresTypeVarMatching: requiresSpecialization(paramType), - argument: argList[argIndex], - errorNode: argList[argIndex].valueExpression ?? errorNode, - paramName: paramNameValue, - }); - - assert( - paramDetails.params[paramDetails.kwargsIndex], - 'paramDetails.kwargsIndex params entry is undefined' - ); - - // Remember that this parameter has already received a value. - paramTracker.addKeywordParam( - paramNameValue, - paramDetails.params[paramDetails.kwargsIndex] - ); - } - trySetActive(argList[argIndex], paramDetails.params[paramDetails.kwargsIndex].param); - } else { - if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.paramNameMissing().format({ name: paramName.d.value }), - paramName - ); - } - reportedArgError = true; - } - } else if (argList[argIndex].argCategory === ArgCategory.Simple) { - if (paramSpecArgList) { - paramSpecArgList.push(argList[argIndex]); - } else { - if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - positionParamLimitIndex === 1 - ? LocMessage.argPositionalExpectedOne() - : LocMessage.argPositionalExpectedCount().format({ - expected: positionParamLimitIndex, - }), - argList[argIndex].valueExpression || errorNode - ); - } - reportedArgError = true; - } - } else if (argList[argIndex].argCategory === ArgCategory.UnpackedList) { - // Handle the case where a *args: P.args (or *args: Any) is passed as an - // argument to a function that accepts a ParamSpec. - if (paramSpec) { - const argTypeResult = getTypeOfArg(argList[argIndex], /* inferenceContext */ undefined); - const argType = argTypeResult.type; - - if (argTypeResult.isIncomplete) { - isTypeIncomplete = true; - } - - if (isParamSpecArgs(paramSpec, argType)) { - validateArgTypeParams.push({ - paramCategory: ParamCategory.ArgsList, - paramType: paramSpec, - requiresTypeVarMatching: false, - argument: argList[argIndex], - argType: isParamSpec(argType) ? undefined : AnyType.create(), - errorNode: argList[argIndex].valueExpression ?? errorNode, - }); - } - } - } - } - - argIndex++; - } - - // If there are keyword-only parameters that haven't been matched but we - // have an unpacked dictionary arg, assume that it applies to them. - if (unpackedDictArgType && (!foundUnpackedListArg || paramDetails.argsIndex !== undefined)) { - // Don't consider any position-only parameters, since they cannot be matched to - // **kwargs arguments. Consider parameters that are either positional or keyword - // if there is no *args argument. - paramDetails.params.forEach((paramInfo, paramIndex) => { - const param = paramInfo.param; - if ( - paramIndex >= paramDetails.firstPositionOrKeywordIndex && - param.category === ParamCategory.Simple && - param.name && - paramTracker.lookupDetails(paramInfo).argsReceived === 0 - ) { - const paramType = paramDetails.params[paramIndex].type; - - if (!unpackedDictKeyNames || unpackedDictKeyNames.includes(param.name)) { - validateArgTypeParams.push({ - paramCategory: ParamCategory.Simple, - paramType, - requiresTypeVarMatching: requiresSpecialization(paramType), - argument: { - argCategory: ArgCategory.Simple, - typeResult: { type: unpackedDictArgType! }, - }, - errorNode: - argList.find((arg) => arg.argCategory === ArgCategory.UnpackedDictionary) - ?.valueExpression ?? errorNode, - paramName: param.name, - isParamNameSynthesized: FunctionParam.isNameSynthesized(param), - }); - - paramTracker.markArgReceived(paramDetails.params[paramIndex]); - } - } - }); - } - - // Determine whether there are any parameters that require arguments - // but have not yet received them. If we received a dictionary argument - // (i.e. an arg starting with a "**"), we will assume that all parameters - // are matched. - if (!unpackedDictArgType && !FunctionType.isDefaultParamCheckDisabled(overload)) { - const unassignedParams = paramTracker.getUnassignedParams(); - - if (unassignedParams.length > 0) { - if (!canSkipDiagnosticForNode(errorNode)) { - const missingParamNames = unassignedParams.map((p) => `"${p}"`).join(', '); - if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - unassignedParams.length === 1 - ? LocMessage.argMissingForParam().format({ name: missingParamNames }) - : LocMessage.argMissingForParams().format({ names: missingParamNames }), - errorNode - ); - } - } - reportedArgError = true; - } - - // Add any implicit (default) arguments that are needed for resolving - // generic types. For example, if the function is defined as - // def foo(v1: _T = 'default') - // and _T is a TypeVar, we need to match the TypeVar to the default - // value's type if it's not provided by the caller. - paramDetails.params.forEach((paramInfo) => { - const param = paramInfo.param; - if (param.category === ParamCategory.Simple && param.name) { - const entry = paramTracker.lookupDetails(paramInfo); - - if (entry.argsNeeded === 0 && entry.argsReceived === 0) { - const defaultArgType = paramInfo.defaultType; - - if ( - defaultArgType && - !isEllipsisType(defaultArgType) && - requiresSpecialization(paramInfo.declaredType, { ignorePseudoGeneric: true }) - ) { - validateArgTypeParams.push({ - paramCategory: param.category, - paramType: paramInfo.type, - requiresTypeVarMatching: true, - argument: { - argCategory: ArgCategory.Simple, - typeResult: { type: defaultArgType }, - }, - isDefaultArg: true, - errorNode, - paramName: param.name, - isParamNameSynthesized: FunctionParam.isNameSynthesized(param), - }); - } - } - } - }); - } - } - - // If we're in speculative mode and an arg/param mismatch has already been reported, don't - // bother doing the extra work here. This occurs frequently when attempting to find the - // correct overload. - if (!reportedArgError || !isSpeculativeModeInUse(undefined)) { - // If there are arguments that map to a variadic *args parameter that hasn't - // already been matched, see if the type of that *args parameter is a - // TypeVarTuple. If so, we'll preprocess those arguments and combine them - // into a tuple. - assert( - paramDetails.argsIndex === undefined || paramDetails.argsIndex < paramDetails.params.length, - 'paramDetails.argsIndex params entry is invalid' - ); - if ( - paramDetails.argsIndex !== undefined && - paramDetails.argsIndex >= 0 && - FunctionParam.isTypeDeclared(paramDetails.params[paramDetails.argsIndex].param) && - !isTypeVarTupleFullyMatched - ) { - const paramType = paramDetails.params[paramDetails.argsIndex].type; - const variadicArgs = validateArgTypeParams.filter((argParam) => argParam.mapsToVarArgList); - - if (isUnpacked(paramType) && (!isTypeVarTuple(paramType) || !paramType.priv.isInUnion)) { - const tupleTypeArgs: TupleTypeArg[] = variadicArgs.map((argParam) => { - const argType = getTypeOfArg(argParam.argument, /* inferenceContext */ undefined).type; - - const containsTypeVarTuple = - isUnpackedTypeVarTuple(argType) || - (isClassInstance(argType) && - isTupleClass(argType) && - argType.priv.tupleTypeArgs && - argType.priv.tupleTypeArgs.length === 1 && - isUnpackedTypeVarTuple(argType.priv.tupleTypeArgs[0].type)); - - if ( - containsTypeVarTuple && - argParam.argument.argCategory !== ArgCategory.UnpackedList && - !argParam.mapsToVarArgList - ) { - if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.typeVarTupleMustBeUnpacked(), - argParam.argument.valueExpression ?? errorNode - ); - } - reportedArgError = true; - } - - return { - type: argType, - isUnbounded: argParam.argument.argCategory === ArgCategory.UnpackedList, - }; - }); - - let specializedTuple: Type | undefined; - if (tupleTypeArgs.length === 1 && !tupleTypeArgs[0].isUnbounded) { - const entryType = tupleTypeArgs[0].type; - - if (isUnpacked(entryType)) { - specializedTuple = makePacked(entryType); - } - } - - if (!specializedTuple) { - specializedTuple = makeTupleObject(evaluatorInterface, tupleTypeArgs, /* isUnpacked */ false); - } - - const combinedArg: ValidateArgTypeParams = { - paramCategory: ParamCategory.Simple, - paramType: makePacked(paramType), - requiresTypeVarMatching: true, - argument: { - argCategory: ArgCategory.Simple, - typeResult: { type: specializedTuple }, - }, - errorNode, - paramName: paramDetails.params[paramDetails.argsIndex].param.name, - isParamNameSynthesized: FunctionParam.isNameSynthesized( - paramDetails.params[paramDetails.argsIndex].param - ), - mapsToVarArgList: true, - }; - - validateArgTypeParams = [ - ...validateArgTypeParams.filter((argParam) => !argParam.mapsToVarArgList), - combinedArg, - ]; - } - } - } - - // Special-case the builtin isinstance and issubclass functions. - if (FunctionType.isBuiltIn(overload, ['isinstance', 'issubclass']) && validateArgTypeParams.length === 2) { - validateArgTypeParams[1].isinstanceParam = true; - } - - return { - overload, - overloadIndex, - argumentErrors: reportedArgError, - isTypeIncomplete, - argParams: validateArgTypeParams, - paramSpecTarget, - paramSpecArgList, - activeParam, - unpackedArgOfUnknownLength, - unpackedArgMapsToVariadic, - argumentMatchScore: 0, - }; - } - - // After having matched arguments with parameters, this function evaluates the - // types of each argument expression and validates that the resulting type is - // compatible with the declared type of the corresponding parameter. - function validateArgTypesWithContext( - errorNode: ExpressionNode, - matchResults: MatchArgsToParamsResult, - constraints: ConstraintTracker, - skipUnknownArgCheck = false, - inferenceContext: InferenceContext | undefined - ): CallResult { - const type = matchResults.overload; - - let expectedType: Type | undefined = inferenceContext?.expectedType; - - // Can we safely ignore the inference context, either because it's not provided - // or will have no effect? If so, avoid the extra work. - const returnType = inferenceContext?.returnTypeOverride ?? getEffectiveReturnType(type); - if (!returnType || !requiresSpecialization(returnType)) { - expectedType = undefined; - } - - const tryExpectedType = (expectedSubtype: Type): number => { - const clonedConstraints = constraints.clone(); - const callResult = validateArgTypesWithExpectedType( - errorNode, - matchResults, - clonedConstraints, - /* skipUnknownArgCheck */ true, - expectedSubtype, - returnType - ); - - // Use a heuristic to pick a subtype that is most likely to be correct. - // We'll look for a subtype that produces no argument errors and has - // no Unknowns in the return type. - if (!callResult.argumentErrors && callResult.returnType) { - const returnType = inferenceContext?.returnTypeOverride - ? solveAndApplyConstraints(inferenceContext.returnTypeOverride, clonedConstraints) - : callResult.returnType; - - if ( - assignType( - expectedSubtype, - returnType, - /* diag */ undefined, - /* constraints */ undefined, - AssignTypeFlags.Default - ) - ) { - const anyOrUnknown = containsAnyOrUnknown(callResult.returnType, /* recurse */ true); - // Prefer return types that have no unknown or Any. - if (!anyOrUnknown) { - return 3; - } - - // Prefer Any over Unknown. - return isAny(anyOrUnknown) ? 2 : 1; - } - } - - return 0; - }; - - // Refine the expected type by speculatively evaluating arg types. If the - // expected type is a union, we may need to perform multiple evaluations - // to determine whether one of the subtypes works. - if (expectedType) { - expectedType = useSpeculativeMode(getSpeculativeNodeForCall(errorNode), () => { - let validExpectedSubtype: Type | undefined; - let bestSubtypeScore = -1; - - // If the expected type is a union, we don't know which type is expected. - // We may or may not be able to make use of the expected type. We'll evaluate - // speculatively to see if using one of the expected subtypes works. - if (isUnion(expectedType!)) { - doForEachSubtype( - expectedType!, - (expectedSubtype) => { - if (bestSubtypeScore < 3) { - const score = tryExpectedType(expectedSubtype); - if (score > 0 && score > bestSubtypeScore) { - validExpectedSubtype = expectedSubtype; - bestSubtypeScore = score; - } - } - }, - /* sortSubtypes */ true - ); - } - - if (bestSubtypeScore < 3) { - const score = tryExpectedType(expectedType!); - if (score > 0 && score > bestSubtypeScore) { - validExpectedSubtype = expectedType; - } - } - - return validExpectedSubtype; - }); - } - - // If there is no expected type, or the expected type is Any or Unknown, - // there's nothing left to do here. - if (!expectedType || isAnyOrUnknown(expectedType) || isNever(expectedType)) { - return validateArgTypes(errorNode, matchResults, constraints, skipUnknownArgCheck); - } - - return validateArgTypesWithExpectedType( - errorNode, - matchResults, - constraints, - skipUnknownArgCheck, - expectedType, - returnType - ); - } - - function validateArgTypesWithExpectedType( - errorNode: ExpressionNode, - matchResults: MatchArgsToParamsResult, - constraints: ConstraintTracker, - skipUnknownArgCheck = false, - expectedType: Type, - returnType: Type - ): CallResult { - const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(errorNode); - let assignFlags = AssignTypeFlags.PopulateExpectedType; - if (containsLiteralType(expectedType, /* includeTypeArgs */ true)) { - assignFlags |= AssignTypeFlags.RetainLiteralsForTypeVar; - } - - // Prepopulate the constraints based on the specialized expected type. - // This will allow us to more closely match the expected type if possible. - if (isClassInstance(returnType) && isClassInstance(expectedType) && !isTypeSame(returnType, expectedType)) { - const tempConstraints = new ConstraintTracker(); - if ( - addConstraintsForExpectedType( - evaluatorInterface, - returnType, - expectedType, - tempConstraints, - liveTypeVarScopes, - errorNode.start - ) - ) { - const genericReturnType = selfSpecializeClass(returnType, { - overrideTypeArgs: true, - }); - - expectedType = solveAndApplyConstraints(genericReturnType, tempConstraints, { - replaceUnsolved: { - scopeIds: getTypeVarScopeIds(returnType), - useUnknown: true, - tupleClassType: getTupleClassType(), - }, - }); - - assignFlags |= AssignTypeFlags.SkipPopulateUnknownExpectedType; - } - } - - expectedType = transformExpectedType(expectedType, liveTypeVarScopes, errorNode.start); - - assignType(returnType, expectedType, /* diag */ undefined, constraints, assignFlags); - - return validateArgTypes(errorNode, matchResults, constraints, skipUnknownArgCheck); - } - - function validateArgTypes( - errorNode: ExpressionNode, - matchResults: MatchArgsToParamsResult, - constraints: ConstraintTracker, - skipUnknownArgCheck: boolean | undefined - ): CallResult { - const type = matchResults.overload; - let isTypeIncomplete = matchResults.isTypeIncomplete; - let argumentErrors = false; - let argumentMatchScore = 0; - let specializedInitSelfType: Type | undefined; - let anyOrUnknownArg: UnknownType | AnyType | undefined; - const speculativeNode = getSpeculativeNodeForCall(errorNode); - const typeCondition = getTypeCondition(type); - const paramSpec = FunctionType.getParamSpecFromArgsKwargs(type); - - // Check for an attempt to invoke an unimplemented abstract method. - if (type.priv.boundToType && !type.priv.boundToType.priv.includeSubclasses && type.shared.methodClass) { - const abstractSymbolInfo = getAbstractSymbolInfo(type.shared.methodClass, type.shared.name); - - if (abstractSymbolInfo && !abstractSymbolInfo.hasImplementation) { - addDiagnostic( - DiagnosticRule.reportAbstractUsage, - LocMessage.abstractMethodInvocation().format({ - method: type.shared.name, - }), - errorNode.nodeType === ParseNodeType.Call ? errorNode.d.leftExpr : errorNode - ); - } - } - - // The type annotation for the "self" parameter in an __init__ method to - // can influence the type being constructed. - if ( - type.shared.name === '__init__' && - type.priv.strippedFirstParamType && - type.priv.boundToType && - isClassInstance(type.priv.strippedFirstParamType) && - isClassInstance(type.priv.boundToType) && - ClassType.isSameGenericClass(type.priv.strippedFirstParamType, type.priv.boundToType) && - type.priv.strippedFirstParamType.priv.typeArgs - ) { - const typeParams = type.priv.strippedFirstParamType.shared.typeParams; - specializedInitSelfType = type.priv.strippedFirstParamType; - type.priv.strippedFirstParamType.priv.typeArgs.forEach((typeArg, index) => { - if (index < typeParams.length) { - const typeParam = typeParams[index]; - if (!isTypeSame(typeParam, typeArg, { ignorePseudoGeneric: true })) { - constraints.setBounds(typeParams[index], typeArg); - } - } - }); - } - - // Special-case a few built-in calls that are often used for - // casting or checking for unknown types. - if ( - FunctionType.isBuiltIn(type, [ - 'typing.cast', - 'typing_extensions.cast', - 'builtins.isinstance', - 'builtins.issubclass', - ]) - ) { - skipUnknownArgCheck = true; - } - - // Run through all args and validate them against their matched parameter. - // We'll do two phases. The first one establishes constraints for type - // variables. The second perform type validation using the solved - // types. We can skip the first pass if there are no type vars to solve. - const typeVarCount = matchResults.argParams.filter((arg) => arg.requiresTypeVarMatching).length; - if (typeVarCount > 0) { - // Do up to two passes. - let passCount = Math.min(typeVarCount, 2); - - for (let i = 0; i < passCount; i++) { - useSpeculativeMode(speculativeNode, () => { - matchResults.argParams.forEach((argParam) => { - if (!argParam.requiresTypeVarMatching) { - return; - } - - const argResult = validateArgType( - argParam, - constraints, - { type, isIncomplete: matchResults.isTypeIncomplete }, - { - skipUnknownArgCheck, - isArgFirstPass: passCount > 1 && i === 0, - conditionFilter: typeCondition, - skipReportError: true, - } - ); - - if (argResult.isTypeIncomplete) { - isTypeIncomplete = true; - } - - // If we skipped a bare type var during the first pass, add - // another pass to ensure that we handle all of the type variables. - if (i === 0 && passCount < 2 && argResult.skippedBareTypeVarExpectedType) { - passCount++; - } - }); - }); - } - } - - let sawParamSpecArgs = false; - let sawParamSpecKwargs = false; - - let condition: TypeCondition[] = []; - const argResults: ArgResult[] = []; - - matchResults.argParams.forEach((argParam, argParamIndex) => { - const argResult = validateArgType( - argParam, - constraints, - { type, isIncomplete: matchResults.isTypeIncomplete }, - { - skipUnknownArgCheck, - conditionFilter: typeCondition, - } - ); - - argResults.push(argResult); - - if (!argResult.isCompatible) { - argumentErrors = true; - - // Add the inverse index so earlier parameters represent larger errors. - // This will help the heuristics in the overload error paths to pick the - // most likely intended overload if none of them match. - argumentMatchScore += 1 + (matchResults.argParams.length - argParamIndex); - } - - if (argResult.isTypeIncomplete) { - isTypeIncomplete = true; - } - - if (argResult.condition) { - condition = TypeCondition.combine(condition, argResult.condition) ?? []; - } - - if (isAnyOrUnknown(argResult.argType)) { - anyOrUnknownArg = anyOrUnknownArg - ? preserveUnknown(argResult.argType, anyOrUnknownArg) - : argResult.argType; - } - - if (paramSpec) { - if (argParam.argument.argCategory === ArgCategory.UnpackedList) { - if (isParamSpecArgs(paramSpec, argResult.argType)) { - if (sawParamSpecArgs) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.paramSpecArgsKwargsDuplicate().format({ type: printType(paramSpec) }), - argParam.errorNode - ); - } - - sawParamSpecArgs = true; - } - } - - if (argParam.argument.argCategory === ArgCategory.UnpackedDictionary) { - if (isParamSpecKwargs(paramSpec, argResult.argType)) { - if (sawParamSpecKwargs) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.paramSpecArgsKwargsDuplicate().format({ type: printType(paramSpec) }), - argParam.errorNode - ); - } - - sawParamSpecKwargs = true; - } - } - } - }); - - let paramSpecConstraints: (ConstraintTracker | undefined)[] = []; - - // Handle the assignment of additional arguments that map to a param spec. - if (matchResults.paramSpecArgList && matchResults.paramSpecTarget) { - const paramSpecArgResult = validateArgTypesForParamSpec( - errorNode, - matchResults.paramSpecArgList, - matchResults.paramSpecTarget, - constraints - ); - - if (paramSpecArgResult.argumentErrors) { - argumentErrors = true; - argumentMatchScore += 1; - } - - paramSpecConstraints = paramSpecArgResult.constraintTrackers; - } else if (paramSpec) { - if (!sawParamSpecArgs || !sawParamSpecKwargs) { - if (!isTypeIncomplete) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.paramSpecArgsMissing().format({ type: printType(paramSpec) }), - errorNode - ); - } - argumentErrors = true; - argumentMatchScore += 1; - } - } - - // Calculate the return type. - const returnTypeResult = getEffectiveReturnTypeResult(type, { - callSiteInfo: { args: matchResults.argParams, errorNode }, - }); - let returnType = returnTypeResult.type; - if (returnTypeResult.isIncomplete) { - isTypeIncomplete = true; - } - - if (condition.length > 0) { - returnType = TypeBase.cloneForCondition(returnType, condition); - } - - let eliminateUnsolvedInUnions = true; - - // If the function is returning a callable, don't eliminate unsolved - // type vars within a union. There are legit uses for unsolved type vars - // within a callable. - if (isFunctionOrOverloaded(returnType)) { - eliminateUnsolvedInUnions = false; - } - - let specializedReturnType = solveAndApplyConstraints(returnType, constraints, { - replaceUnsolved: { - scopeIds: getTypeVarScopeIds(type), - unsolvedExemptTypeVars: getUnknownExemptTypeVarsForReturnType(type, returnType), - tupleClassType: getTupleClassType(), - eliminateUnsolvedInUnions, - }, - }); - specializedReturnType = addConditionToType(specializedReturnType, typeCondition, { skipBoundTypeVars: true }); - - // If the function includes a ParamSpec and the captured signature(s) includes - // generic types, we may need to apply those solved TypeVars. - if (paramSpecConstraints.length > 0) { - paramSpecConstraints.forEach((paramSpecConstraints) => { - if (paramSpecConstraints) { - specializedReturnType = solveAndApplyConstraints(specializedReturnType, paramSpecConstraints); - - // It's possible that one or more of the TypeVars or ParamSpecs - // in the constraints refer to TypeVars that were solved in - // the paramSpecConstraints. Apply these solved TypeVars accordingly. - applySourceSolutionToConstraints( - constraints, - solveConstraints(evaluatorInterface, paramSpecConstraints) - ); - } - }); - } - - // If the final return type is an unpacked tuple, turn it into a normal (unpacked) tuple. - if (isUnpackedClass(specializedReturnType)) { - specializedReturnType = ClassType.cloneForPacked(specializedReturnType); - } - - const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(errorNode); - specializedReturnType = adjustCallableReturnType(errorNode, specializedReturnType, liveTypeVarScopes); - - if (specializedInitSelfType) { - specializedInitSelfType = solveAndApplyConstraints(specializedInitSelfType, constraints); - } - - matchResults.argumentMatchScore = argumentMatchScore; - - return { - argumentErrors, - argResults, - anyOrUnknownArg, - returnType: specializedReturnType, - isTypeIncomplete, - activeParam: matchResults.activeParam, - specializedInitSelfType, - overloadsUsedForCall: argumentErrors ? [] : [type], - }; - } - - // In general, all in-scope type variables left in a return type should be - // replaced with Unknown. However, if the return type is a callable that uses - // type vars that are found nowhere within the function's input parameters, - // we'll treat these as though they're scoped to the callable and leave them - // unsolved. - function getUnknownExemptTypeVarsForReturnType(functionType: FunctionType, returnType: Type): TypeVarType[] { - if (isFunction(returnType) && !returnType.shared.name) { - const returnTypeScopeId = returnType.shared.typeVarScopeId; - - // If one or more type vars found within the return type are scoped to - // the functionType but don't appear anywhere else within the functionType's - // input parameters, rescope them to the return type callable so they are - // not replaced with Unknown. - if (returnTypeScopeId && functionType.shared.typeVarScopeId) { - let typeVarsInReturnType = getTypeVarArgsRecursive(returnType); - - // Remove any type variables that appear in the function's input parameters. - functionType.shared.parameters.forEach((param, index) => { - if (FunctionParam.isTypeDeclared(param)) { - const typeVarsInInputParam = getTypeVarArgsRecursive( - FunctionType.getParamType(functionType, index) - ); - typeVarsInReturnType = typeVarsInReturnType.filter( - (returnTypeVar) => - !typeVarsInInputParam.some((inputTypeVar) => isTypeSame(returnTypeVar, inputTypeVar)) - ); - } - }); - - return typeVarsInReturnType; - } - } - - return []; - } - - // If the return type includes a generic Callable type, set the type var - // scope to the scope of the function it was originally associated with - // to allow these type vars to be solved. This won't work with overloads - // or unions of callables. It's intended for a specific use case. We may - // need to make this more sophisticated in the future. - function adjustCallableReturnType( - callNode: ExpressionNode, - returnType: Type, - liveTypeVarScopes: TypeVarScopeId[] - ): Type { - if (!isFunction(returnType)) { - return returnType; - } - - // What type variables are referenced in the callable return type? Do not include any live type variables. - const typeParams = getTypeVarArgsRecursive(returnType).filter( - (t) => !liveTypeVarScopes.some((scopeId) => t.priv.scopeId === scopeId) - ); - - // If there are no unsolved type variables, we're done. If there are - // unsolved type variables, rescope them to the callable. - if (typeParams.length === 0) { - return returnType; - } - - inferReturnTypeIfNecessary(returnType); - - // Create a new scope ID based on the caller's position. This - // will guarantee uniqueness. If another caller uses the same - // call and arguments, the type vars will not conflict. - const newScopeId = ParseTreeUtils.getScopeIdForNode(callNode); - const solution = new ConstraintSolution(); - - const newTypeParams = typeParams.map((typeVar) => { - const newTypeParam = TypeVarType.cloneForScopeId( - typeVar, - newScopeId, - typeVar.priv.scopeName, - TypeVarScopeType.Function - ); - solution.setType(typeVar, newTypeParam); - return newTypeParam; - }); - - return applySolvedTypeVars( - FunctionType.cloneWithNewTypeVarScopeId( - returnType, - newScopeId, - /* constructorTypeVarScopeId */ undefined, - newTypeParams - ), - solution - ); - } - - // Tries to assign the call arguments to the function parameter - // list and reports any mismatches in types or counts. Returns the - // specialized return type of the call. - function validateArgs( - errorNode: ExpressionNode, - argList: Arg[], - typeResult: TypeResult, - constraints: ConstraintTracker | undefined, - skipUnknownArgCheck = false, - inferenceContext: InferenceContext | undefined - ): CallResult { - const matchResults = matchArgsToParams(errorNode, argList, typeResult, 0); - - if (matchResults.argumentErrors) { - // Evaluate types of all args. This will ensure that referenced symbols are - // not reported as unaccessed. Also pass the expected parameter type as - // inference context to enable proper completions even when there are errors. - matchResults.argParams.forEach((argParam) => { - if (argParam.argument.valueExpression && !isSpeculativeModeInUse(argParam.argument.valueExpression)) { - getTypeOfExpression( - argParam.argument.valueExpression, - /* flags */ undefined, - makeInferenceContext(argParam.paramType) - ); - } - }); - - // Also evaluate any arguments that weren't matched to parameters - argList.forEach((arg) => { - if (arg.valueExpression && !isSpeculativeModeInUse(arg.valueExpression)) { - // Check if this argument was already evaluated above - const wasEvaluated = matchResults.argParams.some((argParam) => argParam.argument === arg); - if (!wasEvaluated) { - getTypeOfExpression(arg.valueExpression); - } - } - }); - // Use a return type of Unknown but attach a "possible type" to it - // so the completion provider can suggest better completions. - const possibleType = FunctionType.getEffectiveReturnType(typeResult.type); - return { - returnType: - possibleType && !isAnyOrUnknown(possibleType) - ? UnknownType.createPossibleType(possibleType, /* isIncomplete */ false) - : undefined, - argumentErrors: true, - activeParam: matchResults.activeParam, - overloadsUsedForCall: [], - }; - } - - return validateArgTypesWithContext( - errorNode, - matchResults, - constraints ?? new ConstraintTracker(), - skipUnknownArgCheck, - makeInferenceContext( - inferenceContext?.expectedType, - inferenceContext?.isTypeIncomplete, - inferenceContext?.returnTypeOverride - ) - ); - } - - // Determines whether the specified argument list satisfies the function - // signature bound to the specified ParamSpec. Return value indicates success. - function validateArgTypesForParamSpec( - errorNode: ExpressionNode, - argList: Arg[], - paramSpec: ParamSpecType, - destConstraints: ConstraintTracker - ): ParamSpecArgResult { - const sets = destConstraints.getConstraintSets(); - - // Handle the common case where there is only one signature context. - if (sets.length === 1) { - return validateArgTypesForParamSpecSignature(errorNode, argList, paramSpec, sets[0]); - } - - const filteredSets: ConstraintSet[] = []; - const constraintTrackers: (ConstraintTracker | undefined)[] = []; - const speculativeNode = getSpeculativeNodeForCall(errorNode); - - sets.forEach((context) => { - // Use speculative mode to avoid emitting errors or caching types. - useSpeculativeMode(speculativeNode, () => { - const paramSpecArgResult = validateArgTypesForParamSpecSignature( - errorNode, - argList, - paramSpec, - context - ); - - if (!paramSpecArgResult.argumentErrors) { - filteredSets.push(context); - } - - appendArray(constraintTrackers, paramSpecArgResult.constraintTrackers); - }); - }); - - // Copy back any compatible signature contexts if any were compatible. - if (filteredSets.length > 0) { - destConstraints.addConstraintSets(filteredSets); - } - - // Evaluate non-speculatively to produce a final result and cache types. - const paramSpecArgResult = validateArgTypesForParamSpecSignature( - errorNode, - argList, - paramSpec, - filteredSets.length > 0 ? filteredSets[0] : sets[0] - ); - - return { argumentErrors: paramSpecArgResult.argumentErrors, constraintTrackers: constraintTrackers }; - } - - function validateArgTypesForParamSpecSignature( - errorNode: ExpressionNode, - argList: Arg[], - paramSpec: ParamSpecType, - constraintSet: ConstraintSet - ): ParamSpecArgResult { - const solutionSet = solveConstraintSet(evaluatorInterface, constraintSet); - let paramSpecType = solutionSet.getType(paramSpec); - paramSpecType = convertTypeToParamSpecValue(paramSpecType ?? paramSpec); - - const matchResults = matchArgsToParams(errorNode, argList, { type: paramSpecType }, 0); - const functionType = matchResults.overload; - const constraints = new ConstraintTracker(); - - if (matchResults.argumentErrors) { - // Evaluate types of all args. This will ensure that referenced symbols are - // not reported as unaccessed. - argList.forEach((arg) => { - if (arg.valueExpression && !isSpeculativeModeInUse(arg.valueExpression)) { - getTypeOfExpression(arg.valueExpression); - } - }); - - return { argumentErrors: true, constraintTrackers: [constraints] }; - } - - const functionParamSpec = FunctionType.getParamSpecFromArgsKwargs(functionType); - const functionWithoutParamSpec = FunctionType.cloneRemoveParamSpecArgsKwargs(functionType); - - // Handle the recursive case where we're passing (*args: P.args, **kwargs: P.args) - // a remaining function of type (*P). - if ( - functionParamSpec && - functionWithoutParamSpec.shared.parameters.length === 0 && - isTypeSame(functionParamSpec, paramSpec) - ) { - // If there are any arguments other than *args: P.args or **kwargs: P.kwargs, - // report an error. - let argsCount = 0; - let kwargsCount = 0; - let argumentErrors = false; - let argErrorNode: ExpressionNode | undefined; - - for (const arg of argList) { - const argType = getTypeOfArg(arg, /* inferenceContext */ undefined)?.type; - - if (arg.argCategory === ArgCategory.UnpackedList) { - if (isParamSpecArgs(paramSpec, argType)) { - argsCount++; - } - } else if (arg.argCategory === ArgCategory.UnpackedDictionary) { - if (isParamSpecKwargs(paramSpec, argType)) { - kwargsCount++; - } - } else { - argErrorNode = argErrorNode ?? arg.valueExpression; - argumentErrors = true; - } - } - - if (argsCount !== 1 || kwargsCount !== 1) { - argumentErrors = true; - } - - if (argumentErrors) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.paramSpecArgsMissing().format({ - type: printType(functionParamSpec), - }), - argErrorNode ?? errorNode - ); - } - - return { argumentErrors, constraintTrackers: [constraints] }; - } - - const result = validateArgTypes(errorNode, matchResults, constraints, /* skipUnknownArgCheck */ undefined); - return { argumentErrors: !!result.argumentErrors, constraintTrackers: [constraints] }; - } - - function validateArgType( - argParam: ValidateArgTypeParams, - constraints: ConstraintTracker, - typeResult: TypeResult | undefined, - options: ValidateArgTypeOptions - ): ArgResult { - let argType: Type | undefined; - let expectedTypeDiag: DiagnosticAddendum | undefined; - let isTypeIncomplete = !!typeResult?.isIncomplete; - let isCompatible = true; - const functionName = typeResult?.type.shared.name; - let skippedBareTypeVarExpectedType = false; - - if (argParam.argument.valueExpression) { - let expectedType: Type | undefined; - - // Is the expected type a "bare" in-scope TypeVar or a union of bare in-scope TypeVars? - let isExpectedTypeBareTypeVar = true; - doForEachSubtype(argParam.paramType, (subtype) => { - if (!isTypeVar(subtype) || subtype.priv.scopeId !== typeResult?.type.shared.typeVarScopeId) { - isExpectedTypeBareTypeVar = false; - } - }); - - if (!options.isArgFirstPass || !isExpectedTypeBareTypeVar) { - expectedType = argParam.paramType; - - // If the parameter type is a function with a ParamSpec, don't apply - // the solved TypeVars if the constraint tracker has more than one signature. - // This will expand the ParamSpec into an overload, which will cause problems. - const skipApplySolvedTypeVars = - isFunction(argParam.paramType) && - FunctionType.getParamSpecFromArgsKwargs(argParam.paramType) && - constraints.getConstraintSets().length > 1; - - if (!skipApplySolvedTypeVars) { - expectedType = solveAndApplyConstraints(expectedType, constraints, /* applyOptions */ undefined, { - useLowerBoundOnly: !!options.isArgFirstPass, - }); - } - } else { - skippedBareTypeVarExpectedType = true; - } - - // If the expected type is unknown, don't use an expected type. Instead, - // use default rules for evaluating the expression type. - if (expectedType && isUnknown(expectedType)) { - expectedType = undefined; - } - - // Was the argument's type precomputed by the caller? - if (argParam.argType) { - argType = argParam.argType; - } else { - const flags = argParam.isinstanceParam - ? EvalFlags.IsInstanceArgDefaults - : EvalFlags.NoFinal | EvalFlags.NoSpecialize; - const exprTypeResult = getTypeOfExpression( - argParam.argument.valueExpression, - flags, - makeInferenceContext(expectedType, !!typeResult?.isIncomplete) - ); - - argType = exprTypeResult.type; - - // If the argument is unpacked and we are supposed to enforce - // that it's an iterator, do so now. - if (argParam.argument.argCategory === ArgCategory.UnpackedList && argParam.argument.enforceIterable) { - const iteratorType = getTypeOfIterator( - exprTypeResult, - /* isAsync */ false, - argParam.argument.valueExpression - ); - // Try to prevent cascading errors if it was not iterable. - argType = iteratorType?.type ?? UnknownType.create(); - } - - if (exprTypeResult.isIncomplete) { - isTypeIncomplete = true; - } - - if (expectedType && requiresSpecialization(expectedType)) { - // Viper 扩展:str/bytes 实参传给可迭代协议参数(Iterable[_T]/Iterator[_T] 等) - // 时,泛型参数 _T 应从 str.__iter__ 的声明返回类型(Iterator[str])推断。 - // 但 Viper 中 str 是 i8* 指针,迭代元素应为 t.CChar 而非 str。 - // 因此将 argType 替换为 Iterator[CChar] 实例,使协议匹配把 _T 绑定为 CChar, - // 修复 `for i, ch in enumerate(src)` 中 ch 被推断为 str 的问题 - // (同样覆盖 zip/map/filter/reversed/sorted/list() 等 Iterable 参数场景)。 - let argTypeForConstraint = argType; - if ( - IsViperDialect(AnalyzerNodeInfo.getFileInfo(argParam.errorNode).executionEnvironment.dialect) && - isClassInstance(argType) && - ClassType.isBuiltIn(argType, ['str', 'bytes']) - ) { - const ccharType = getViperCCharType(argParam.errorNode); - const iteratorClass = getTypingType(argParam.errorNode, 'Iterator'); - if (ccharType && iteratorClass && isInstantiableClass(iteratorClass)) { - argTypeForConstraint = ClassType.cloneAsInstance( - ClassType.specialize(iteratorClass, [ccharType]) - ); - } - } - - // Assign the argument type back to the expected type to assign - // values to any unification variables. - const clonedConstraints = constraints.clone(); - if ( - assignType( - expectedType, - argTypeForConstraint, - /* diag */ undefined, - clonedConstraints, - options?.isArgFirstPass ? AssignTypeFlags.ArgAssignmentFirstPass : AssignTypeFlags.Default - ) - ) { - constraints.copyFromClone(clonedConstraints); - } else { - isCompatible = false; - } - } - - expectedTypeDiag = exprTypeResult.expectedTypeDiagAddendum; - } - - if (argParam.argument && argParam.argument.name && !isSpeculativeModeInUse(argParam.errorNode)) { - writeTypeCache( - argParam.argument.name, - { type: expectedType ?? argType, isIncomplete: isTypeIncomplete }, - EvalFlags.None - ); - } - } else { - // Was the argument's type precomputed by the caller? - if (argParam.argType) { - argType = argParam.argType; - } else { - const argTypeResult = getTypeOfArg( - argParam.argument, - makeInferenceContext(argParam.paramType, isTypeIncomplete) - ); - argType = argTypeResult.type; - if (argTypeResult.isIncomplete) { - isTypeIncomplete = true; - } - } - - // If the argument came from a parameter's default argument value, - // we may need to specialize the type. - if (argParam.isDefaultArg) { - argType = solveAndApplyConstraints(argType, constraints); - } - } - - // If we're assigning to a var arg dictionary with a TypeVar type, - // strip literals before performing the assignment. This is used in - // places like a dict constructor. - if (argParam.paramCategory === ParamCategory.KwargsDict && isTypeVar(argParam.paramType)) { - argType = stripLiteralValue(argType); - } - - // If there's a constraint filter, apply it to top-level type variables - // if appropriate. This doesn't properly handle non-top-level constrained - // type variables. - if (options.conditionFilter) { - argType = mapSubtypesExpandTypeVars( - argType, - { conditionFilter: options.conditionFilter }, - (expandedSubtype) => { - return expandedSubtype; - } - ); - } - - const condition = argType.props?.condition; - - let diag = options?.skipReportError ? undefined : new DiagnosticAddendum(); - - if (isParamSpec(argParam.paramType)) { - // Handle the case where we're assigning a *args or **kwargs argument - // to a *P.args or **P.kwargs parameter. - if (argParam.paramType.priv.paramSpecAccess !== undefined) { - return { isCompatible, argType, isTypeIncomplete, condition }; - } - - // Handle the case where we're assigning a *P.args or **P.kwargs argument - // to a *P.args or **P.kwargs parameter. - if (isParamSpec(argType) && argType.priv.paramSpecAccess !== undefined) { - return { isCompatible, argType, isTypeIncomplete, condition }; - } - } - - let assignTypeFlags = AssignTypeFlags.Default; - - if (argParam.isinstanceParam) { - assignTypeFlags |= AssignTypeFlags.AllowIsinstanceSpecialForms; - } - - if (options?.isArgFirstPass) { - assignTypeFlags |= AssignTypeFlags.ArgAssignmentFirstPass; - } - - if (!assignType(argParam.paramType, argType, diag?.createAddendum(), constraints, assignTypeFlags)) { - if (!options?.skipReportError) { - // Mismatching parameter types are common in untyped code; don't bother spending time - // printing types if the diagnostic is disabled. - const fileInfo = AnalyzerNodeInfo.getFileInfo(argParam.errorNode); - if ( - fileInfo.diagnosticRuleSet.reportArgumentType !== 'none' && - !canSkipDiagnosticForNode(argParam.errorNode) && - !isTypeIncomplete - ) { - const argTypeText = printType(argType); - const paramTypeText = printType(argParam.paramType); - - let message: string; - if (argParam.paramName && !argParam.isParamNameSynthesized) { - if (functionName) { - message = LocMessage.argAssignmentParamFunction().format({ - argType: argTypeText, - paramType: paramTypeText, - functionName, - paramName: argParam.paramName, - }); - } else { - message = LocMessage.argAssignmentParam().format({ - argType: argTypeText, - paramType: paramTypeText, - paramName: argParam.paramName, - }); - } - } else { - if (functionName) { - message = LocMessage.argAssignmentFunction().format({ - argType: argTypeText, - paramType: paramTypeText, - functionName, - }); - } else { - message = LocMessage.argAssignment().format({ - argType: argTypeText, - paramType: paramTypeText, - }); - } - } - - // If we have an expected type diagnostic addendum, use that - // instead of the local diagnostic addendum because it will - // be more informative. - if (expectedTypeDiag) { - diag = expectedTypeDiag; - } - - addDiagnostic( - DiagnosticRule.reportArgumentType, - message + diag?.getString(), - argParam.errorNode, - diag?.getEffectiveTextRange() ?? argParam.errorNode - ); - } - } - - return { isCompatible: false, argType, isTypeIncomplete, skippedBareTypeVarExpectedType, condition }; - } - - if (!options.skipUnknownArgCheck) { - const simplifiedType = makeTopLevelTypeVarsConcrete(removeUnbound(argType)); - const fileInfo = AnalyzerNodeInfo.getFileInfo(argParam.errorNode); - - function getDiagAddendum() { - const diagAddendum = new DiagnosticAddendum(); - if (argParam.paramName) { - diagAddendum.addMessage( - (functionName - ? LocAddendum.argParamFunction().format({ - paramName: argParam.paramName, - functionName, - }) - : LocAddendum.argParam().format({ paramName: argParam.paramName })) + - diagAddendum.getString() - ); - } - return diagAddendum; - } - - // Do not check for unknown types if the expected type is "Any". - // Don't print types if reportUnknownArgumentType is disabled for performance. - if ( - fileInfo.diagnosticRuleSet.reportUnknownArgumentType !== 'none' && - !isAny(argParam.paramType) && - !isTypeIncomplete - ) { - if (isUnknown(simplifiedType)) { - const diagAddendum = getDiagAddendum(); - addDiagnostic( - DiagnosticRule.reportUnknownArgumentType, - LocMessage.argTypeUnknown() + diagAddendum.getString(), - argParam.errorNode - ); - } else if (isPartlyUnknown(simplifiedType)) { - // If the parameter type is also partially unknown, don't report - // the error because it's likely that the partially-unknown type - // arose due to bidirectional type matching. - if (!isPartlyUnknown(argParam.paramType)) { - const diagAddendum = getDiagAddendum(); - diagAddendum.addMessage( - LocAddendum.argumentType().format({ - type: printType(simplifiedType, { expandTypeAlias: true }), - }) - ); - addDiagnostic( - DiagnosticRule.reportUnknownArgumentType, - LocMessage.argTypePartiallyUnknown() + diagAddendum.getString(), - argParam.errorNode - ); - } - } - } - } - - return { isCompatible, argType, isTypeIncomplete, skippedBareTypeVarExpectedType, condition }; - } - - function createTypeVarType(errorNode: ExpressionNode, classType: ClassType, argList: Arg[]): Type | undefined { - let typeVarName = ''; - let firstConstraintArg: Arg | undefined; - let defaultValueNode: ExpressionNode | undefined; - - if (argList.length === 0) { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.typeVarFirstArg(), errorNode); - return undefined; - } - - const firstArg = argList[0]; - if (firstArg.valueExpression && firstArg.valueExpression.nodeType === ParseNodeType.StringList) { - typeVarName = firstArg.valueExpression.d.strings.map((s) => s.d.value).join(''); - } else { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarFirstArg(), - firstArg.valueExpression || errorNode - ); - } - - const typeVar = TypeBase.cloneAsSpecialForm( - TypeVarType.createInstantiable(typeVarName), - ClassType.cloneAsInstance(classType) - ); - - // Parse the remaining parameters. - const paramNameMap = new Map(); - for (let i = 1; i < argList.length; i++) { - const paramNameNode = argList[i].name; - const paramName = paramNameNode ? paramNameNode.d.value : undefined; - - if (paramName) { - if (paramNameMap.get(paramName)) { - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.duplicateParam().format({ name: paramName }), - argList[i].valueExpression || errorNode - ); - } - - if (paramName === 'bound') { - if (TypeVarType.hasConstraints(typeVar)) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarBoundAndConstrained(), - argList[i].valueExpression || errorNode - ); - } else { - const argType = - argList[i].typeResult?.type ?? - getTypeOfExpressionExpectingType(argList[i].valueExpression!, { - noNonTypeSpecialForms: true, - typeExpression: true, - parsesStringLiteral: true, - }).type; - if ( - requiresSpecialization(argType, { ignorePseudoGeneric: true, ignoreImplicitTypeArgs: true }) - ) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarBoundGeneric(), - argList[i].valueExpression || errorNode - ); - } - typeVar.shared.boundType = convertToInstance(argType); - } - } else if (paramName === 'covariant') { - if (argList[i].valueExpression && getBooleanValue(argList[i].valueExpression!)) { - if ( - typeVar.shared.declaredVariance === Variance.Contravariant || - typeVar.shared.declaredVariance === Variance.Auto - ) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarVariance(), - argList[i].valueExpression! - ); - } else { - typeVar.shared.declaredVariance = Variance.Covariant; - } - } - } else if (paramName === 'contravariant') { - if (argList[i].valueExpression && getBooleanValue(argList[i].valueExpression!)) { - if ( - typeVar.shared.declaredVariance === Variance.Covariant || - typeVar.shared.declaredVariance === Variance.Auto - ) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarVariance(), - argList[i].valueExpression! - ); - } else { - typeVar.shared.declaredVariance = Variance.Contravariant; - } - } - } else if (paramName === 'infer_variance') { - if (argList[i].valueExpression && getBooleanValue(argList[i].valueExpression!)) { - if ( - typeVar.shared.declaredVariance === Variance.Covariant || - typeVar.shared.declaredVariance === Variance.Contravariant - ) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarVariance(), - argList[i].valueExpression! - ); - } else { - typeVar.shared.declaredVariance = Variance.Auto; - } - } - } else if (paramName === 'default') { - defaultValueNode = argList[i].valueExpression; - const argType = - argList[i].typeResult?.type ?? - getTypeOfExpressionExpectingType(defaultValueNode!, { - allowTypeVarsWithoutScopeId: true, - typeExpression: true, - }).type; - typeVar.shared.defaultType = convertToInstance(argType); - typeVar.shared.isDefaultExplicit = true; - - const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode); - if ( - !fileInfo.isStubFile && - PythonVersion.isLessThan(fileInfo.executionEnvironment.pythonVersion, pythonVersion3_13) && - classType.shared.moduleName !== 'typing_extensions' - ) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarDefaultIllegal(), - defaultValueNode! - ); - } - } else { - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.typeVarUnknownParam().format({ name: paramName }), - argList[i].node?.d.name || argList[i].valueExpression || errorNode - ); - } - - paramNameMap.set(paramName, paramName); - } else { - if (TypeVarType.hasBound(typeVar)) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarBoundAndConstrained(), - argList[i].valueExpression || errorNode - ); - } else { - const argType = - argList[i].typeResult?.type ?? - getTypeOfExpressionExpectingType(argList[i].valueExpression!, { - typeExpression: true, - }).type; - - if (requiresSpecialization(argType, { ignorePseudoGeneric: true })) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarConstraintGeneric(), - argList[i].valueExpression || errorNode - ); - } - TypeVarType.addConstraint(typeVar, convertToInstance(argType)); - if (firstConstraintArg === undefined) { - firstConstraintArg = argList[i]; - } - } - } - } - - if (typeVar.shared.constraints.length === 1 && firstConstraintArg) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarSingleConstraint(), - firstConstraintArg.valueExpression || errorNode - ); - } - - // If a default is provided, make sure it is compatible with the bound - // or constraint. - if (typeVar.shared.isDefaultExplicit && defaultValueNode) { - verifyTypeVarDefaultIsCompatible(typeVar, defaultValueNode); - } - - return typeVar; - } - - function verifyTypeVarDefaultIsCompatible(typeVar: TypeVarType, defaultValueNode: ExpressionNode) { - assert(typeVar.shared.isDefaultExplicit); - - const constraints = new ConstraintTracker(); - const concreteDefaultType = makeTopLevelTypeVarsConcrete( - solveAndApplyConstraints(typeVar.shared.defaultType, constraints, { - replaceUnsolved: { - scopeIds: getTypeVarScopeIds(typeVar), - tupleClassType: getTupleClassType(), - }, - }) - ); - - if (typeVar.shared.boundType) { - if (!assignType(typeVar.shared.boundType, concreteDefaultType)) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarDefaultBoundMismatch(), - defaultValueNode - ); - } - } else if (TypeVarType.hasConstraints(typeVar)) { - let isConstraintCompatible = true; - - // If the default type is a constrained TypeVar, make sure all of its constraints - // are also constraints in typeVar. If the default type is not a constrained TypeVar, - // use its concrete type to compare against the constraints. - if (isTypeVar(typeVar.shared.defaultType) && TypeVarType.hasConstraints(typeVar.shared.defaultType)) { - for (const constraint of typeVar.shared.defaultType.shared.constraints) { - if (!typeVar.shared.constraints.some((c) => isTypeSame(c, constraint))) { - isConstraintCompatible = false; - } - } - } else if ( - !typeVar.shared.constraints.some((constraint) => - isTypeSame(constraint, concreteDefaultType, { ignoreConditions: true }) - ) - ) { - isConstraintCompatible = false; - } - - if (!isConstraintCompatible) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarDefaultConstraintMismatch(), - defaultValueNode - ); - } - } - } - - function createTypeVarTupleType(errorNode: ExpressionNode, classType: ClassType, argList: Arg[]): Type | undefined { - let typeVarName = ''; - - if (argList.length === 0) { - addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.typeVarFirstArg(), errorNode); - return undefined; - } - - const firstArg = argList[0]; - if (firstArg.valueExpression && firstArg.valueExpression.nodeType === ParseNodeType.StringList) { - typeVarName = firstArg.valueExpression.d.strings.map((s) => s.d.value).join(''); - } else { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarFirstArg(), - firstArg.valueExpression || errorNode - ); - } - - const typeVar = TypeBase.cloneAsSpecialForm( - TypeVarType.createInstantiable(typeVarName, TypeVarKind.TypeVarTuple), - ClassType.cloneAsInstance(classType) - ); - typeVar.shared.defaultType = makeTupleObject(evaluatorInterface, [ - { type: UnknownType.create(), isUnbounded: true }, - ]); - - // Parse the remaining parameters. - for (let i = 1; i < argList.length; i++) { - const paramNameNode = argList[i].name; - const paramName = paramNameNode ? paramNameNode.d.value : undefined; - - if (paramName) { - if (paramName === 'default') { - const expr = argList[i].valueExpression; - if (expr) { - const defaultType = getTypeVarTupleDefaultType(expr, /* isPep695Syntax */ false); - if (defaultType) { - typeVar.shared.defaultType = defaultType; - typeVar.shared.isDefaultExplicit = true; - } - } - - const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode); - if ( - !fileInfo.isStubFile && - PythonVersion.isLessThan(fileInfo.executionEnvironment.pythonVersion, pythonVersion3_13) && - classType.shared.moduleName !== 'typing_extensions' - ) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarDefaultIllegal(), - expr! - ); - } - } else { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarTupleUnknownParam().format({ name: argList[i].name?.d.value || '?' }), - argList[i].node?.d.name || argList[i].valueExpression || errorNode - ); - } - } else { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarTupleConstraints(), - argList[i].valueExpression || errorNode - ); - } - } - - return typeVar; - } - - function getTypeVarTupleDefaultType(node: ExpressionNode, isPep695Syntax: boolean): Type | undefined { - const argType = getTypeOfExpressionExpectingType(node, { - allowUnpackedTuple: true, - allowTypeVarsWithoutScopeId: true, - forwardRefs: isPep695Syntax, - typeExpression: true, - }).type; - const isUnpackedTuple = isClass(argType) && isTupleClass(argType) && argType.priv.isUnpacked; - const isUnpackedTypeVar = isUnpackedTypeVarTuple(argType); - - if (!isUnpackedTuple && !isUnpackedTypeVar) { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.typeVarTupleDefaultNotUnpacked(), node); - return undefined; - } - - return convertToInstance(argType); - } - - function createParamSpecType(errorNode: ExpressionNode, classType: ClassType, argList: Arg[]): Type | undefined { - if (argList.length === 0) { - addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.paramSpecFirstArg(), errorNode); - return undefined; - } - - const firstArg = argList[0]; - let paramSpecName = ''; - if (firstArg.valueExpression && firstArg.valueExpression.nodeType === ParseNodeType.StringList) { - paramSpecName = firstArg.valueExpression.d.strings.map((s) => s.d.value).join(''); - } else { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.paramSpecFirstArg(), - firstArg.valueExpression || errorNode - ); - } - - const paramSpec = TypeBase.cloneAsSpecialForm( - TypeVarType.createInstantiable(paramSpecName, TypeVarKind.ParamSpec), - ClassType.cloneAsInstance(classType) - ); - - paramSpec.shared.defaultType = ParamSpecType.getUnknown(); - - // Parse the remaining parameters. - for (let i = 1; i < argList.length; i++) { - const paramNameNode = argList[i].name; - const paramName = paramNameNode ? paramNameNode.d.value : undefined; - - if (paramName) { - if (paramName === 'default') { - const expr = argList[i].valueExpression; - if (expr) { - const defaultType = getParamSpecDefaultType(expr, /* isPep695Syntax */ false); - if (defaultType) { - paramSpec.shared.defaultType = defaultType; - paramSpec.shared.isDefaultExplicit = true; - } - } - - const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode); - if ( - !fileInfo.isStubFile && - PythonVersion.isLessThan(fileInfo.executionEnvironment.pythonVersion, pythonVersion3_13) && - classType.shared.moduleName !== 'typing_extensions' - ) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarDefaultIllegal(), - expr! - ); - } - } else { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.paramSpecUnknownParam().format({ name: paramName }), - paramNameNode || argList[i].valueExpression || errorNode - ); - } - } else { - addDiagnostic( - DiagnosticRule.reportCallIssue, - LocMessage.paramSpecUnknownArg(), - argList[i].valueExpression || errorNode - ); - break; - } - } - - return paramSpec; - } - - function getParamSpecDefaultType(node: ExpressionNode, isPep695Syntax: boolean): Type | undefined { - const functionType = FunctionType.createSynthesizedInstance('', FunctionTypeFlags.ParamSpecValue); - - if (node.nodeType === ParseNodeType.Ellipsis) { - FunctionType.addDefaultParams(functionType); - functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm; - return functionType; - } - - if (node.nodeType === ParseNodeType.List) { - node.d.items.forEach((paramExpr, index) => { - const typeResult = getTypeOfExpressionExpectingType(paramExpr, { - allowTypeVarsWithoutScopeId: true, - forwardRefs: isPep695Syntax, - typeExpression: true, - }); - - FunctionType.addParam( - functionType, - FunctionParam.create( - ParamCategory.Simple, - convertToInstance(typeResult.type), - FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared, - `__p${index}` - ) - ); - }); - - if (node.d.items.length > 0) { - FunctionType.addPositionOnlyParamSeparator(functionType); - } - - // Update the type cache so we don't attempt to re-evaluate this node. - // The type doesn't matter, so use Any. - writeTypeCache(node, { type: AnyType.create() }, /* flags */ undefined); - return functionType; - } else { - const typeResult = getTypeOfExpressionExpectingType(node, { - allowParamSpec: true, - allowTypeVarsWithoutScopeId: true, - allowEllipsis: true, - typeExpression: true, - }); - - if (typeResult.typeErrors) { - return undefined; - } - - if (isParamSpec(typeResult.type)) { - FunctionType.addParamSpecVariadics(functionType, typeResult.type); - return functionType; - } - - if ( - isClassInstance(typeResult.type) && - ClassType.isBuiltIn(typeResult.type, ['EllipsisType', 'ellipsis']) - ) { - FunctionType.addDefaultParams(functionType); - return functionType; - } - } - - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.paramSpecDefaultNotTuple(), node); - - return undefined; - } - - // Handles a call to TypeAliasType(). This special form allows a caller to programmatically - // create a type alias as defined in PEP 695. If one or more of the arguments is incorrect, - // it returns undefined so the normal constructor evaluation can be performed (and type errors - // emitted). - function createTypeAliasType(errorNode: ExpressionNode, argList: Arg[]): Type | undefined { - if (errorNode.nodeType !== ParseNodeType.Call || !errorNode.parent || argList.length < 2) { - return undefined; - } - - if ( - errorNode.parent.nodeType !== ParseNodeType.Assignment || - errorNode.parent.d.rightExpr !== errorNode || - errorNode.parent.d.leftExpr.nodeType !== ParseNodeType.Name - ) { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.typeAliasTypeMustBeAssigned(), errorNode); - return undefined; - } - - const scope = ScopeUtils.getScopeForNode(errorNode); - if (scope) { - if (scope.type !== ScopeType.Class && scope.type !== ScopeType.Module && scope.type !== ScopeType.Builtin) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeAliasTypeBadScope(), - errorNode.parent.d.leftExpr - ); - } - } - - const nameNode = errorNode.parent.d.leftExpr; - - const firstArg = argList[0]; - if (firstArg.valueExpression && firstArg.valueExpression.nodeType === ParseNodeType.StringList) { - const typeAliasName = firstArg.valueExpression.d.strings.map((s) => s.d.value).join(''); - if (typeAliasName !== nameNode.d.value) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeAliasTypeNameMismatch(), - firstArg.valueExpression - ); - } - } else { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeAliasTypeNameArg(), - firstArg.valueExpression || errorNode - ); - return undefined; - } - - let valueExpr: ExpressionNode | undefined; - let typeParamsExpr: ExpressionNode | undefined; - - // Parse the remaining parameters. - for (let i = 1; i < argList.length; i++) { - const paramNameNode = argList[i].name; - const paramName = paramNameNode ? paramNameNode.d.value : undefined; - - if (paramName) { - if (paramName === 'type_params' && !typeParamsExpr) { - typeParamsExpr = argList[i].valueExpression; - } else if (paramName === 'value' && !valueExpr) { - valueExpr = argList[i].valueExpression; - } else { - return undefined; - } - } else if (i === 1) { - valueExpr = argList[i].valueExpression; - } else { - return undefined; - } - } - - // The value expression is not optional, so bail if it's not present. - if (!valueExpr) { - return undefined; - } - - let typeParams: TypeVarType[] | undefined; - if (typeParamsExpr) { - if (typeParamsExpr.nodeType !== ParseNodeType.Tuple) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeAliasTypeParamInvalid(), - typeParamsExpr - ); - return undefined; - } - - typeParams = []; - let isTypeParamListValid = true; - typeParamsExpr.d.items.map((expr) => { - let entryType = getTypeOfExpression( - expr, - EvalFlags.InstantiableType | EvalFlags.AllowTypeVarWithoutScopeId - ).type; - - if (isTypeVar(entryType)) { - if (entryType.priv.scopeId || (isTypeVarTuple(entryType) && entryType.priv.isUnpacked)) { - isTypeParamListValid = false; - } else { - entryType = TypeVarType.cloneForScopeId( - entryType, - ParseTreeUtils.getScopeIdForNode(nameNode), - nameNode.d.value, - TypeVarScopeType.TypeAlias - ); - } - - typeParams!.push(entryType); - } else { - isTypeParamListValid = false; - } - }); - - if (!isTypeParamListValid) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeAliasTypeParamInvalid(), - typeParamsExpr - ); - return undefined; - } - } - - return getTypeOfTypeAliasCommon( - nameNode, - nameNode, - valueExpr, - /* isPep695Syntax */ false, - /* typeParamNodes */ undefined, - () => typeParams - ); - } - - function getBooleanValue(node: ExpressionNode): boolean { - if (node.nodeType === ParseNodeType.Constant) { - if (node.d.constType === KeywordType.False) { - return false; - } else if (node.d.constType === KeywordType.True) { - return true; - } - } - - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.expectedBoolLiteral(), node); - return false; - } - - function getFunctionFullName(functionNode: ParseNode, moduleName: string, functionName: string): string { - const nameParts: string[] = [functionName]; - - let curNode: ParseNode | undefined = functionNode; - - // Walk the parse tree looking for classes or functions. - while (curNode) { - curNode = ParseTreeUtils.getEnclosingClassOrFunction(curNode); - if (curNode) { - nameParts.push(curNode.d.name.d.value); - } - } - - nameParts.push(moduleName); - - return nameParts.reverse().join('.'); - } - - // Implements the semantics of the NewType call as documented - // in the Python specification: The static type checker will treat - // the new type as if it were a subclass of the original type. - function createNewType(errorNode: ExpressionNode, argList: Arg[]): ClassType | undefined { - const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode); - let className = ''; - - if (argList.length !== 2) { - addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.newTypeParamCount(), errorNode); - return undefined; - } - - const nameArg = argList[0]; - if ( - nameArg.argCategory === ArgCategory.Simple && - nameArg.valueExpression && - nameArg.valueExpression.nodeType === ParseNodeType.StringList - ) { - className = nameArg.valueExpression.d.strings.map((s) => s.d.value).join(''); - } - - if (!className) { - addDiagnostic(DiagnosticRule.reportArgumentType, LocMessage.newTypeBadName(), argList[0].node ?? errorNode); - return undefined; - } - - if ( - errorNode.parent?.nodeType === ParseNodeType.Assignment && - errorNode.parent.d.leftExpr.nodeType === ParseNodeType.Name && - errorNode.parent.d.leftExpr.d.value !== className - ) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.newTypeNameMismatch(), - errorNode.parent.d.leftExpr - ); - return undefined; - } - - let baseClass = getTypeOfArgExpectingType(argList[1]).type; - let isBaseClassAny = false; - - if (isAnyOrUnknown(baseClass)) { - baseClass = prefetched?.objectClass ?? UnknownType.create(); - - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.newTypeAnyOrUnknown(), - argList[1].node ?? errorNode - ); - - isBaseClassAny = true; - } - - // Specifically disallow Annotated. - if ( - baseClass.props?.specialForm && - isClassInstance(baseClass.props.specialForm) && - ClassType.isBuiltIn(baseClass.props.specialForm, 'Annotated') - ) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.newTypeNotAClass(), - argList[1].node || errorNode - ); - return undefined; - } - - if (!isInstantiableClass(baseClass)) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.newTypeNotAClass(), - argList[1].node || errorNode - ); - return undefined; - } - - if (ClassType.isProtocolClass(baseClass) || ClassType.isTypedDictClass(baseClass)) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.newTypeProtocolClass(), - argList[1].node || errorNode - ); - } else if (baseClass.priv.literalValue !== undefined) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.newTypeLiteral(), - argList[1].node || errorNode - ); - } - - const classType = ClassType.createInstantiable( - className, - ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className), - fileInfo.moduleName, - fileInfo.fileUri, - ClassTypeFlags.Final | ClassTypeFlags.NewTypeClass | ClassTypeFlags.ValidTypeAliasClass, - ParseTreeUtils.getTypeSourceId(errorNode), - /* declaredMetaclass */ undefined, - baseClass.shared.effectiveMetaclass - ); - classType.shared.baseClasses.push(isBaseClassAny ? AnyType.create() : baseClass); - computeMroLinearization(classType); - - if (!isBaseClassAny) { - // Synthesize an __init__ method that accepts only the specified type. - const initType = FunctionType.createSynthesizedInstance('__init__'); - FunctionType.addParam( - initType, - FunctionParam.create(ParamCategory.Simple, AnyType.create(), FunctionParamFlags.TypeDeclared, 'self') - ); - FunctionType.addParam( - initType, - FunctionParam.create( - ParamCategory.Simple, - ClassType.cloneAsInstance(baseClass), - FunctionParamFlags.TypeDeclared, - '_x' - ) - ); - initType.shared.declaredReturnType = getNoneType(); - ClassType.getSymbolTable(classType).set( - '__init__', - Symbol.createWithType(SymbolFlags.ClassMember, initType) - ); - - // Synthesize a trivial __new__ method. - const newType = FunctionType.createSynthesizedInstance('__new__', FunctionTypeFlags.ConstructorMethod); - FunctionType.addParam( - newType, - FunctionParam.create(ParamCategory.Simple, AnyType.create(), FunctionParamFlags.TypeDeclared, 'cls') - ); - FunctionType.addDefaultParams(newType); - newType.shared.declaredReturnType = ClassType.cloneAsInstance(classType); - newType.priv.constructorTypeVarScopeId = getTypeVarScopeId(classType); - ClassType.getSymbolTable(classType).set('__new__', Symbol.createWithType(SymbolFlags.ClassMember, newType)); - } - - return classType; - } - - // Implements the semantics of the multi-parameter variant of the "type" call. - function createClassFromMetaclass( - errorNode: ExpressionNode, - argList: Arg[], - metaclass: ClassType - ): ClassType | undefined { - const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode); - const arg0Type = getTypeOfArg(argList[0], /* inferenceContext */ undefined).type; - if (!isClassInstance(arg0Type) || !ClassType.isBuiltIn(arg0Type, 'str')) { - return undefined; - } - const className = (arg0Type.priv.literalValue as string) || '_'; - - const arg1Type = getTypeOfArg(argList[1], /* inferenceContext */ undefined).type; - - // TODO - properly handle case where tuple of base classes is provided. - if (!isClassInstance(arg1Type) || !isTupleClass(arg1Type) || arg1Type.priv.tupleTypeArgs === undefined) { - return undefined; - } - - const classType = ClassType.createInstantiable( - className, - ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className), - fileInfo.moduleName, - fileInfo.fileUri, - ClassTypeFlags.ValidTypeAliasClass, - ParseTreeUtils.getTypeSourceId(errorNode), - metaclass, - arg1Type.shared.effectiveMetaclass - ); - arg1Type.priv.tupleTypeArgs.forEach((typeArg) => { - const specializedType = makeTopLevelTypeVarsConcrete(typeArg.type); - - if (isEffectivelyInstantiable(specializedType)) { - classType.shared.baseClasses.push(specializedType); - } else { - classType.shared.baseClasses.push(UnknownType.create()); - } - }); - - if (!computeMroLinearization(classType)) { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.methodOrdering(), errorNode); - } - - return classType; - } - - function getTypeOfConstant(node: ConstantNode, flags: EvalFlags): TypeResult { - let type: Type | undefined; - - if (node.d.constType === KeywordType.None) { - if (prefetched?.noneTypeClass) { - type = - (flags & EvalFlags.InstantiableType) !== 0 - ? prefetched.noneTypeClass - : convertToInstance(prefetched.noneTypeClass); - - type = TypeBase.cloneWithTypeForm(type, convertToInstance(type)); - } - } else if ( - node.d.constType === KeywordType.True || - node.d.constType === KeywordType.False || - node.d.constType === KeywordType.Debug - ) { - type = getBuiltInObject(node, 'bool'); - - // For True and False, we can create truthy and falsy - // versions of 'bool'. - if (type && isClassInstance(type)) { - if (node.d.constType === KeywordType.True) { - type = ClassType.cloneWithLiteral(type, /* value */ true); - } else if (node.d.constType === KeywordType.False) { - type = ClassType.cloneWithLiteral(type, /* value */ false); - } - } - } - - return { type: type ?? UnknownType.create() }; - } - - function getTypeOfMagicMethodCall( - objType: Type, - methodName: string, - argList: TypeResult[], - errorNode: ExpressionNode, - inferenceContext?: InferenceContext, - diag?: DiagnosticAddendum - ): TypeResult | undefined { - let magicMethodSupported = true; - let isIncomplete = false; - let deprecationInfo: MagicMethodDeprecationInfo | undefined; - const overloadsUsedForCall: FunctionType[] = []; - - // Create a helper lambda for object subtypes. - const handleSubtype = (subtype: ClassType | TypeVarType) => { - let magicMethodType: Type | undefined; - const concreteSubtype = makeTopLevelTypeVarsConcrete(subtype); - - if (isClass(concreteSubtype)) { - magicMethodType = getBoundMagicMethod(concreteSubtype, methodName, subtype, errorNode, diag); - } - - if (magicMethodType) { - const functionArgs: Arg[] = argList.map((arg) => { - return { - argCategory: ArgCategory.Simple, - typeResult: arg, - }; - }); - - let callResult: CallResult | undefined; - - callResult = useSpeculativeMode(errorNode, () => { - assert(magicMethodType !== undefined); - return validateCallArgs( - errorNode, - functionArgs, - { type: magicMethodType }, - /* constraints */ undefined, - /* skipUnknownArgCheck */ true, - inferenceContext - ); - }); - - // If there were errors with the expected type, try - // to evaluate without the expected type. - if (callResult.argumentErrors && inferenceContext) { - callResult = useSpeculativeMode(errorNode, () => { - assert(magicMethodType !== undefined); - return validateCallArgs( - errorNode, - functionArgs, - { type: magicMethodType }, - /* constraints */ undefined, - /* skipUnknownArgCheck */ true, - /* inferenceContext */ undefined - ); - }); - } - - if (callResult.argumentErrors) { - magicMethodSupported = false; - } else if (callResult.overloadsUsedForCall) { - callResult.overloadsUsedForCall.forEach((overload) => { - overloadsUsedForCall.push(overload); - - // If one of the overloads is deprecated, note the message. - if (overload.shared.deprecatedMessage && isClass(concreteSubtype)) { - deprecationInfo = { - deprecatedMessage: overload.shared.deprecatedMessage, - className: concreteSubtype.shared.name, - methodName, - }; - } - }); - } - - if (callResult.isTypeIncomplete) { - isIncomplete = true; - } - - return callResult.returnType; - } - - magicMethodSupported = false; - return undefined; - }; - - const returnType = mapSubtypes(objType, (subtype) => { - if (isAnyOrUnknown(subtype)) { - return subtype; - } - - if (isClassInstance(subtype) || isInstantiableClass(subtype) || isTypeVar(subtype)) { - return handleSubtype(subtype); - } - - if (isNoneInstance(subtype)) { - if (prefetched?.objectClass && isInstantiableClass(prefetched.objectClass)) { - // Use 'object' for 'None'. - return handleSubtype(ClassType.cloneAsInstance(prefetched.objectClass)); - } - } - - if (isNoneTypeClass(subtype)) { - if (prefetched?.typeClass && isInstantiableClass(prefetched.typeClass)) { - // Use 'type' for 'type[None]'. - return handleSubtype(ClassType.cloneAsInstance(prefetched.typeClass)); - } - } - - magicMethodSupported = false; - return undefined; - }); - - if (!magicMethodSupported) { - return undefined; - } - - return { type: returnType, isIncomplete, magicMethodDeprecationInfo: deprecationInfo, overloadsUsedForCall }; - } - - function getTypeOfDictionary( - node: DictionaryNode, - flags: EvalFlags, - inferenceContext: InferenceContext | undefined - ): TypeResult { - if ((flags & EvalFlags.TypeExpression) !== 0 && node.parent?.nodeType !== ParseNodeType.Argument) { - const diag = new DiagnosticAddendum(); - diag.addMessage(LocAddendum.useDictInstead()); - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.dictInAnnotation() + diag.getString(), node); - } - - // If the expected type is a union, analyze for each of the subtypes - // to find one that matches. - let expectedType = inferenceContext?.expectedType; - - if (inferenceContext && isUnion(inferenceContext.expectedType)) { - let matchingSubtype: Type | undefined; - let matchingSubtypeResult: TypeResult | undefined; - - doForEachSubtype( - inferenceContext.expectedType, - (subtype) => { - // Use shortcut if we've already found a match. - if (matchingSubtypeResult && !matchingSubtypeResult.typeErrors) { - return; - } - - const subtypeResult = useSpeculativeMode(node, () => { - return getTypeOfDictionaryWithContext(node, flags, makeInferenceContext(subtype)); - }); - - if (subtypeResult && assignType(subtype, subtypeResult.type)) { - // If this is the first result we're seeing or it's the first result - // without errors, select it as the match. - if (!matchingSubtypeResult || (matchingSubtypeResult.typeErrors && !subtypeResult.typeErrors)) { - matchingSubtype = subtype; - matchingSubtypeResult = subtypeResult; - } - } - }, - /* sortSubtypes */ true - ); - - expectedType = matchingSubtype; - } - - let expectedTypeDiagAddendum = undefined; - if (expectedType) { - expectedTypeDiagAddendum = new DiagnosticAddendum(); - const result = getTypeOfDictionaryWithContext( - node, - flags, - makeInferenceContext(expectedType), - expectedTypeDiagAddendum - ); - if (result) { - return result; - } - } - - // Don't force strict inference when the expected type is a TypeVar that - // couldn't constrain the dict's key/value structure. A TypeVar like _T - // from sorted(Iterable[_T], key=...) doesn't provide useful type - // information for dict inference, and forcing strict inference would - // widen heterogeneous value types into a union that can cause false - // positives (e.g., dict[str, Path | str | dict[Any, Any]] when the - // user meant each key to have a specific type). - // Note: bounded/constrained TypeVars with dict-like bounds are handled - // by getTypeOfDictionaryWithContext above (via makeTopLevelTypeVarsConcrete), - // so they don't reach this fallback. - const hasUsefulExpectedType = !!inferenceContext?.expectedType && !isTypeVar(inferenceContext.expectedType); - const result = getTypeOfDictionaryInferred(node, flags, /* hasExpectedType */ hasUsefulExpectedType); - return { ...result, expectedTypeDiagAddendum }; - } - - function getTypeOfDictionaryWithContext( - node: DictionaryNode, - flags: EvalFlags, - inferenceContext: InferenceContext, - expectedDiagAddendum?: DiagnosticAddendum - ): TypeResult | undefined { - inferenceContext.expectedType = transformPossibleRecursiveTypeAlias(inferenceContext.expectedType); - let concreteExpectedType = makeTopLevelTypeVarsConcrete(inferenceContext.expectedType); - - if (!isClassInstance(concreteExpectedType)) { - return undefined; - } - - const keyTypes: TypeResultWithNode[] = []; - const valueTypes: TypeResultWithNode[] = []; - let isIncomplete = false; - let typeErrors = false; - - // Handle TypedDict's as a special case. - if (ClassType.isTypedDictClass(concreteExpectedType)) { - // Remove any conditions associated with the type so the resulting type isn't - // considered compatible with a bound TypeVar. - concreteExpectedType = TypeBase.cloneForCondition(concreteExpectedType, undefined); - - const expectedTypedDictEntries = getTypedDictMembersForClass(evaluatorInterface, concreteExpectedType); - - // Infer the key and value types if possible. - const keyValueTypeResult = getKeyAndValueTypesFromDictionary( - node, - flags, - keyTypes, - valueTypes, - /* forceStrictInference */ true, - /* isValueTypeInvariant */ true, - /* expectedKeyType */ undefined, - /* expectedValueType */ undefined, - expectedTypedDictEntries, - expectedDiagAddendum - ); - - if (keyValueTypeResult.isIncomplete) { - isIncomplete = true; - } - - if (keyValueTypeResult.typeErrors) { - typeErrors = true; - } - - const resultTypedDict = assignToTypedDict( - evaluatorInterface, - concreteExpectedType, - keyTypes, - valueTypes, - // Don't overwrite existing expectedDiagAddendum messages if they were - // already provided by getKeyValueTypesFromDictionary. - expectedDiagAddendum?.isEmpty() ? expectedDiagAddendum : undefined - ); - if (resultTypedDict) { - return { - type: resultTypedDict, - isIncomplete, - }; - } - - return undefined; - } - - let expectedKeyType: Type; - let expectedValueType: Type; - - if (isAnyOrUnknown(inferenceContext.expectedType)) { - expectedKeyType = inferenceContext.expectedType; - expectedValueType = inferenceContext.expectedType; - } else { - const builtInDict = getBuiltInObject(node, 'dict'); - if (!isClassInstance(builtInDict)) { - return undefined; - } - - const dictConstraints = new ConstraintTracker(); - if ( - !addConstraintsForExpectedType( - evaluatorInterface, - builtInDict, - inferenceContext.expectedType, - dictConstraints, - ParseTreeUtils.getTypeVarScopesForNode(node), - node.start - ) - ) { - return undefined; - } - - const specializedDict = solveAndApplyConstraints( - ClassType.cloneAsInstantiable(builtInDict), - dictConstraints - ) as ClassType; - if (!specializedDict.priv.typeArgs || specializedDict.priv.typeArgs.length !== 2) { - return undefined; - } - - expectedKeyType = specializedDict.priv.typeArgs[0]; - expectedValueType = specializedDict.priv.typeArgs[1]; - } - - // Dict and MutableMapping types have invariant value types, so they - // cannot be narrowed further. Other super-types like Mapping, Collection, - // and Iterable use covariant value types, so they can be narrowed. - let isValueTypeInvariant = false; - if (isClassInstance(inferenceContext.expectedType)) { - if (inferenceContext.expectedType.shared.typeParams.length >= 2) { - const valueTypeParam = inferenceContext.expectedType.shared.typeParams[1]; - if (TypeVarType.getVariance(valueTypeParam) === Variance.Invariant) { - isValueTypeInvariant = true; - } - } - } - - // Infer the key and value types if possible. - const keyValueResult = getKeyAndValueTypesFromDictionary( - node, - flags, - keyTypes, - valueTypes, - /* forceStrictInference */ true, - isValueTypeInvariant, - expectedKeyType, - expectedValueType, - undefined, - expectedDiagAddendum - ); - - if (keyValueResult.isIncomplete) { - isIncomplete = true; - } - - if (keyValueResult.typeErrors) { - typeErrors = true; - } - - const specializedKeyType = inferTypeArgFromExpectedEntryType( - makeInferenceContext(expectedKeyType), - keyTypes.map((result) => result.type), - /* isNarrowable */ false - ); - const specializedValueType = inferTypeArgFromExpectedEntryType( - makeInferenceContext(expectedValueType), - valueTypes.map((result) => result.type), - !isValueTypeInvariant - ); - if (!specializedKeyType || !specializedValueType) { - return undefined; - } - - const type = getBuiltInObject(node, 'dict', [specializedKeyType, specializedValueType]); - return { type, isIncomplete, typeErrors }; - } - - // Attempts to infer the type of a dictionary statement. If hasExpectedType - // is true, strict inference is used for the subexpressions. - function getTypeOfDictionaryInferred(node: DictionaryNode, flags: EvalFlags, hasExpectedType: boolean): TypeResult { - const fallbackType = hasExpectedType ? AnyType.create() : UnknownType.create(); - let keyType: Type = fallbackType; - let valueType: Type = fallbackType; - - const keyTypeResults: TypeResultWithNode[] = []; - const valueTypeResults: TypeResultWithNode[] = []; - - let isEmptyContainer = false; - let isIncomplete = false; - let typeErrors = false; - - // Infer the key and value types if possible. - const keyValueResult = getKeyAndValueTypesFromDictionary( - node, - flags, - keyTypeResults, - valueTypeResults, - /* forceStrictInference */ hasExpectedType, - /* isValueTypeInvariant */ false - ); - - if (keyValueResult.isIncomplete) { - isIncomplete = true; - } - - if (keyValueResult.typeErrors) { - typeErrors = true; - } - - // Strip any literal values and TypeForm types. - const keyTypes = keyTypeResults.map((t) => - stripTypeForm(convertSpecialFormToRuntimeValue(stripLiteralValue(t.type), flags, !hasExpectedType)) - ); - const valueTypes = valueTypeResults.map((t) => - stripTypeForm(convertSpecialFormToRuntimeValue(stripLiteralValue(t.type), flags, !hasExpectedType)) - ); - - if (keyTypes.length > 0) { - if (AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.strictDictionaryInference || hasExpectedType) { - keyType = combineTypes(keyTypes); - } else { - keyType = areTypesSame(keyTypes, { ignorePseudoGeneric: true }) ? keyTypes[0] : fallbackType; - } - } else { - keyType = fallbackType; - } - - // If the value type differs and we're not using "strict inference mode", - // we need to back off because we can't properly represent the mappings - // between different keys and associated value types. If all the values - // are the same type, we'll assume that all values in this dictionary should - // be the same. - if (valueTypes.length > 0) { - if (AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.strictDictionaryInference || hasExpectedType) { - valueType = combineTypes(valueTypes); - } else { - valueType = areTypesSame(valueTypes, { ignorePseudoGeneric: true }) ? valueTypes[0] : fallbackType; - } - } else { - valueType = fallbackType; - isEmptyContainer = true; - } - - const dictClass = getBuiltInType(node, 'dict'); - const type = isInstantiableClass(dictClass) - ? ClassType.cloneAsInstance( - ClassType.specialize( - dictClass, - [keyType, valueType], - /* isTypeArgExplicit */ true, - /* includeSubclasses */ undefined, - /* tupleTypeArgs */ undefined, - isEmptyContainer - ) - ) - : UnknownType.create(); - - if (isIncomplete) { - if (getContainerDepth(type) > maxInferredContainerDepth) { - return { type: UnknownType.create() }; - } - } - - return { type, isIncomplete, typeErrors }; - } - - function getKeyAndValueTypesFromDictionary( - node: DictionaryNode, - flags: EvalFlags, - keyTypes: TypeResultWithNode[], - valueTypes: TypeResultWithNode[], - forceStrictInference: boolean, - isValueTypeInvariant: boolean, - expectedKeyType?: Type, - expectedValueType?: Type, - expectedTypedDictEntries?: TypedDictEntries, - expectedDiagAddendum?: DiagnosticAddendum - ): TypeResult { - let isIncomplete = false; - let typeErrors = false; - - // Mask out some of the flags that are not applicable for a dictionary key - // even if it appears within an inlined TypedDict annotation. - const keyFlags = flags & ~(EvalFlags.TypeExpression | EvalFlags.StrLiteralAsType | EvalFlags.InstantiableType); - - // Infer the key and value types if possible. - node.d.items.forEach((entryNode, index) => { - let addUnknown = true; - - if (entryNode.nodeType === ParseNodeType.DictionaryKeyEntry) { - const keyTypeResult = getTypeOfExpression( - entryNode.d.keyExpr, - keyFlags | EvalFlags.StripTupleLiterals, - makeInferenceContext( - expectedKeyType ?? (forceStrictInference ? NeverType.createNever() : undefined) - ) - ); - - if (keyTypeResult.isIncomplete) { - isIncomplete = true; - } - - if (keyTypeResult.typeErrors) { - typeErrors = true; - } - - const keyType = keyTypeResult.type; - - if (!keyTypeResult.isIncomplete && !keyTypeResult.typeErrors) { - verifySetEntryOrDictKeyIsHashable(entryNode.d.keyExpr, keyType, /* isDictKey */ true); - } - - if (expectedDiagAddendum && keyTypeResult.expectedTypeDiagAddendum) { - expectedDiagAddendum.addAddendum(keyTypeResult.expectedTypeDiagAddendum); - } - - let valueTypeResult: TypeResult; - let entryInferenceContext: InferenceContext | undefined; - - if ( - expectedTypedDictEntries && - isClassInstance(keyType) && - ClassType.isBuiltIn(keyType, 'str') && - isLiteralType(keyType) && - (expectedTypedDictEntries.knownItems.has(keyType.priv.literalValue as string) || - expectedTypedDictEntries.extraItems) - ) { - let effectiveValueType = - expectedTypedDictEntries.knownItems.get(keyType.priv.literalValue as string)?.valueType ?? - expectedTypedDictEntries.extraItems?.valueType; - if (effectiveValueType) { - const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(node); - effectiveValueType = transformExpectedType(effectiveValueType, liveTypeVarScopes, node.start); - } - entryInferenceContext = makeInferenceContext(effectiveValueType); - valueTypeResult = getTypeOfExpression( - entryNode.d.valueExpr, - flags | EvalFlags.StripTupleLiterals, - entryInferenceContext - ); - } else { - let effectiveValueType = - expectedValueType ?? (forceStrictInference ? NeverType.createNever() : undefined); - if (effectiveValueType) { - const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(node); - effectiveValueType = transformExpectedType(effectiveValueType, liveTypeVarScopes, node.start); - } - entryInferenceContext = makeInferenceContext(effectiveValueType); - valueTypeResult = getTypeOfExpression( - entryNode.d.valueExpr, - flags | EvalFlags.StripTupleLiterals, - entryInferenceContext - ); - } - - if (entryInferenceContext && !valueTypeResult.typeErrors) { - const fromExpectedType = inferTypeArgFromExpectedEntryType( - entryInferenceContext, - [valueTypeResult.type], - !isValueTypeInvariant - ); - - if (fromExpectedType) { - valueTypeResult = { ...valueTypeResult, type: fromExpectedType }; - } - } - - if (expectedDiagAddendum && valueTypeResult.expectedTypeDiagAddendum) { - expectedDiagAddendum.addAddendum(valueTypeResult.expectedTypeDiagAddendum); - } - - const valueType = valueTypeResult.type; - if (valueTypeResult.isIncomplete) { - isIncomplete = true; - } - - if (valueTypeResult.typeErrors) { - typeErrors = true; - } - - if (forceStrictInference || index < maxEntriesToUseForInference) { - // If an existing key has the same literal type, delete the previous - // key since we're overwriting it here. - if (isClass(keyType) && isLiteralType(keyType)) { - const existingIndex = keyTypes.findIndex((kt) => isTypeSame(keyType, kt.type)); - if (existingIndex >= 0) { - keyTypes.splice(existingIndex, 1); - valueTypes.splice(existingIndex, 1); - } - } - - keyTypes.push({ node: entryNode.d.keyExpr, type: keyType }); - valueTypes.push({ node: entryNode.d.valueExpr, type: valueType }); - } - - addUnknown = false; - } else if (entryNode.nodeType === ParseNodeType.DictionaryExpandEntry) { - let expectedType: Type | undefined; - if (expectedKeyType && expectedValueType) { - if ( - prefetched?.supportsKeysAndGetItemClass && - isInstantiableClass(prefetched.supportsKeysAndGetItemClass) - ) { - expectedType = ClassType.cloneAsInstance( - ClassType.specialize(prefetched.supportsKeysAndGetItemClass, [ - expectedKeyType, - expectedValueType, - ]) - ); - } - } - - const entryInferenceContext = makeInferenceContext(expectedType); - let unexpandedTypeResult = getTypeOfExpression( - entryNode.d.expr, - flags | EvalFlags.StripTupleLiterals, - entryInferenceContext - ); - - if (entryInferenceContext && !unexpandedTypeResult.typeErrors) { - const fromExpectedType = inferTypeArgFromExpectedEntryType( - entryInferenceContext, - [unexpandedTypeResult.type], - !isValueTypeInvariant - ); - - if (fromExpectedType) { - unexpandedTypeResult = { ...unexpandedTypeResult, type: fromExpectedType }; - } - } - - if (unexpandedTypeResult.isIncomplete) { - isIncomplete = true; - } - - if (unexpandedTypeResult.typeErrors) { - typeErrors = true; - } - - const unexpandedType = unexpandedTypeResult.type; - - if (isAnyOrUnknown(unexpandedType)) { - if (forceStrictInference || index < maxEntriesToUseForInference) { - keyTypes.push({ node: entryNode, type: unexpandedType }); - valueTypes.push({ node: entryNode, type: unexpandedType }); - } - addUnknown = false; - } else if (isClassInstance(unexpandedType) && ClassType.isTypedDictClass(unexpandedType)) { - // Handle dictionary expansion for a TypedDict. - if (prefetched?.strClass && isInstantiableClass(prefetched.strClass)) { - const strObject = ClassType.cloneAsInstance(prefetched.strClass); - const tdEntries = getTypedDictMembersForClass( - evaluatorInterface, - unexpandedType, - /* allowNarrowed */ true - ); - - tdEntries.knownItems.forEach((entry, name) => { - if (entry.isRequired || entry.isProvided) { - keyTypes.push({ - node: entryNode, - type: ClassType.cloneWithLiteral(strObject, name), - }); - valueTypes.push({ node: entryNode, type: entry.valueType }); - } - }); - - if (!expectedTypedDictEntries) { - keyTypes.push({ node: entryNode, type: ClassType.cloneAsInstance(strObject) }); - valueTypes.push({ - node: entryNode, - type: tdEntries.extraItems?.valueType ?? getObjectType(), - }); - } - - addUnknown = false; - } - } else if ( - prefetched?.supportsKeysAndGetItemClass && - isInstantiableClass(prefetched.supportsKeysAndGetItemClass) - ) { - const mappingConstraints = new ConstraintTracker(); - - const supportsKeysAndGetItemClass = selfSpecializeClass(prefetched.supportsKeysAndGetItemClass); - - if ( - assignType( - ClassType.cloneAsInstance(supportsKeysAndGetItemClass), - unexpandedType, - /* diag */ undefined, - mappingConstraints, - AssignTypeFlags.RetainLiteralsForTypeVar - ) - ) { - const specializedMapping = solveAndApplyConstraints( - supportsKeysAndGetItemClass, - mappingConstraints - ) as ClassType; - const typeArgs = specializedMapping.priv.typeArgs; - if (typeArgs && typeArgs.length >= 2) { - if (forceStrictInference || index < maxEntriesToUseForInference) { - keyTypes.push({ node: entryNode, type: typeArgs[0] }); - valueTypes.push({ node: entryNode, type: typeArgs[1] }); - } - addUnknown = false; - } - } else { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.dictUnpackIsNotMapping(), - entryNode - ); - } - } - } else if (entryNode.nodeType === ParseNodeType.Comprehension) { - const dictEntryTypeResult = getElementTypeFromComprehension( - entryNode, - flags | EvalFlags.StripTupleLiterals, - expectedValueType, - expectedKeyType - ); - const dictEntryType = dictEntryTypeResult.type; - if (dictEntryTypeResult.isIncomplete) { - isIncomplete = true; - } - - if (dictEntryTypeResult.typeErrors) { - typeErrors = true; - } - - // The result should be a tuple. - if (isClassInstance(dictEntryType) && isTupleClass(dictEntryType)) { - const typeArgs = dictEntryType.priv.tupleTypeArgs?.map((t) => t.type); - if (typeArgs && typeArgs.length === 2) { - if (forceStrictInference || index < maxEntriesToUseForInference) { - keyTypes.push({ node: entryNode, type: typeArgs[0] }); - valueTypes.push({ node: entryNode, type: typeArgs[1] }); - } - addUnknown = false; - } - } - } - - if (addUnknown) { - if (forceStrictInference || index < maxEntriesToUseForInference) { - keyTypes.push({ node: entryNode, type: UnknownType.create() }); - valueTypes.push({ node: entryNode, type: UnknownType.create() }); - } - } - }); - - return { type: AnyType.create(), isIncomplete, typeErrors }; - } - - function getTypeOfListOrSet( - node: ListNode | SetNode, - flags: EvalFlags, - inferenceContext: InferenceContext | undefined - ): TypeResult { - if ( - (flags & EvalFlags.TypeExpression) !== 0 && - node.nodeType === ParseNodeType.List && - node.parent?.nodeType !== ParseNodeType.Argument - ) { - const diag = new DiagnosticAddendum(); - diag.addMessage(LocAddendum.useListInstead()); - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.listInAnnotation() + diag.getString(), node); - } - - flags &= ~(EvalFlags.TypeExpression | EvalFlags.StrLiteralAsType | EvalFlags.InstantiableType); - - // If the expected type is a union, recursively call for each of the subtypes - // to find one that matches. - let expectedType = inferenceContext?.expectedType; - - if (inferenceContext && isUnion(inferenceContext.expectedType)) { - let matchingSubtype: Type | undefined; - let matchingSubtypeResult: TypeResult | undefined; - - doForEachSubtype( - inferenceContext.expectedType, - (subtype) => { - // Use shortcut if we've already found a match. - if (matchingSubtypeResult && !matchingSubtypeResult.typeErrors) { - return; - } - - const subtypeResult = useSpeculativeMode(node, () => { - return getTypeOfListOrSetWithContext(node, flags, makeInferenceContext(subtype)); - }); - - if (subtypeResult && assignType(subtype, subtypeResult.type)) { - // If this is the first result we're seeing or it's the first result - // without errors, select it as the match. - if (!matchingSubtypeResult || (matchingSubtypeResult.typeErrors && !subtypeResult.typeErrors)) { - matchingSubtype = subtype; - matchingSubtypeResult = subtypeResult; - } - } - }, - /* sortSubtypes */ true - ); - - expectedType = matchingSubtype; - } - - let expectedTypeDiagAddendum: DiagnosticAddendum | undefined; - if (expectedType) { - const result = getTypeOfListOrSetWithContext(node, flags, makeInferenceContext(expectedType)); - if (result && !result.typeErrors) { - return result; - } - - expectedTypeDiagAddendum = result?.expectedTypeDiagAddendum; - } - - const typeResult = getTypeOfListOrSetInferred( - node, - flags, - /* hasExpectedType */ !!inferenceContext?.expectedType - ); - return { ...typeResult, expectedTypeDiagAddendum }; - } - - // Attempts to determine the type of a list or set statement based on an expected type. - // Returns undefined if that type cannot be honored. - function getTypeOfListOrSetWithContext( - node: ListNode | SetNode, - flags: EvalFlags, - inferenceContext: InferenceContext - ): TypeResult | undefined { - const builtInClassName = node.nodeType === ParseNodeType.List ? 'list' : 'set'; - inferenceContext.expectedType = transformPossibleRecursiveTypeAlias(inferenceContext.expectedType); - - let isIncomplete = false; - let typeErrors = false; - const verifyHashable = node.nodeType === ParseNodeType.Set; - - const expectedEntryType = getExpectedEntryTypeForIterable( - node, - getBuiltInType(node, builtInClassName), - inferenceContext - ); - if (!expectedEntryType) { - return undefined; - } - - const entryTypes: Type[] = []; - const expectedTypeDiagAddendum = new DiagnosticAddendum(); - node.d.items.forEach((entry) => { - let entryTypeResult: TypeResult; - - if (entry.nodeType === ParseNodeType.Comprehension) { - entryTypeResult = getElementTypeFromComprehension( - entry, - flags | EvalFlags.StripTupleLiterals, - expectedEntryType - ); - } else { - entryTypeResult = getTypeOfExpression( - entry, - flags | EvalFlags.StripTupleLiterals, - makeInferenceContext(expectedEntryType) - ); - } - - entryTypes.push(entryTypeResult.type); - - if (entryTypeResult.isIncomplete) { - isIncomplete = true; - } - - if (entryTypeResult.typeErrors) { - typeErrors = true; - } - - if (entryTypeResult.expectedTypeDiagAddendum) { - expectedTypeDiagAddendum.addAddendum(entryTypeResult.expectedTypeDiagAddendum); - } - - if (verifyHashable && !entryTypeResult.isIncomplete && !entryTypeResult.typeErrors) { - verifySetEntryOrDictKeyIsHashable(entry, entryTypeResult.type, /* isDictKey */ false); - } - }); - - let isTypeInvariant = false; - - if (isClassInstance(inferenceContext.expectedType)) { - inferVarianceForClass(inferenceContext.expectedType); - - if ( - inferenceContext.expectedType.shared.typeParams.some( - (t) => TypeVarType.getVariance(t) === Variance.Invariant - ) - ) { - isTypeInvariant = true; - } - } - - const specializedEntryType = inferTypeArgFromExpectedEntryType( - makeInferenceContext(expectedEntryType), - entryTypes, - !isTypeInvariant - ); - if (!specializedEntryType) { - return { type: UnknownType.create(), isIncomplete, typeErrors: true, expectedTypeDiagAddendum }; - } - - const type = getBuiltInObject(node, builtInClassName, [specializedEntryType]); - return { type, isIncomplete, typeErrors, expectedTypeDiagAddendum }; - } - - function getExpectedEntryTypeForIterable( - node: ListNode | SetNode | ComprehensionNode, - expectedClassType: Type | undefined, - inferenceContext?: InferenceContext - ): Type | undefined { - if (!inferenceContext) { - return undefined; - } - - if (!expectedClassType || !isInstantiableClass(expectedClassType)) { - return undefined; - } - - if (isAnyOrUnknown(inferenceContext.expectedType)) { - return inferenceContext.expectedType; - } - - if (!isClassInstance(inferenceContext.expectedType)) { - return undefined; - } - - const constraints = new ConstraintTracker(); - if ( - !addConstraintsForExpectedType( - evaluatorInterface, - ClassType.cloneAsInstance(expectedClassType), - inferenceContext.expectedType, - constraints, - ParseTreeUtils.getTypeVarScopesForNode(node), - node.start - ) - ) { - return undefined; - } - - const specializedListOrSet = solveAndApplyConstraints(expectedClassType, constraints) as ClassType; - if (!specializedListOrSet.priv.typeArgs) { - return undefined; - } - - return specializedListOrSet.priv.typeArgs[0]; - } - - // Attempts to infer the type of a list or set statement with no "expected type". - function getTypeOfListOrSetInferred( - node: ListNode | SetNode, - flags: EvalFlags, - hasExpectedType: boolean - ): TypeResult { - const builtInClassName = node.nodeType === ParseNodeType.List ? 'list' : 'set'; - const verifyHashable = node.nodeType === ParseNodeType.Set; - let isEmptyContainer = false; - let isIncomplete = false; - let typeErrors = false; - - let entryTypes: Type[] = []; - node.d.items.forEach((entry, index) => { - let entryTypeResult: TypeResult; - - if (entry.nodeType === ParseNodeType.Comprehension && !entry.d.isGenerator) { - entryTypeResult = getElementTypeFromComprehension(entry, flags | EvalFlags.StripTupleLiterals); - } else { - entryTypeResult = getTypeOfExpression(entry, flags | EvalFlags.StripTupleLiterals); - } - - entryTypeResult.type = stripTypeForm( - convertSpecialFormToRuntimeValue(entryTypeResult.type, flags, !hasExpectedType) - ); - - if (entryTypeResult.isIncomplete) { - isIncomplete = true; - } - - if (entryTypeResult.typeErrors) { - typeErrors = true; - } - - if (hasExpectedType || index < maxEntriesToUseForInference) { - entryTypes.push(entryTypeResult.type); - } - - if (verifyHashable && !entryTypeResult.isIncomplete && !entryTypeResult.typeErrors) { - verifySetEntryOrDictKeyIsHashable(entry, entryTypeResult.type, /* isDictKey */ false); - } - }); - - entryTypes = entryTypes.map((t) => stripLiteralValue(t)); - - let inferredEntryType: Type = hasExpectedType ? AnyType.create() : UnknownType.create(); - if (entryTypes.length > 0) { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - // If there was an expected type or we're using strict list inference, - // combine the types into a union. - if ( - (builtInClassName === 'list' && fileInfo.diagnosticRuleSet.strictListInference) || - (builtInClassName === 'set' && fileInfo.diagnosticRuleSet.strictSetInference) || - hasExpectedType - ) { - inferredEntryType = combineTypes(entryTypes, { maxSubtypeCount: maxSubtypesForInferredType }); - } else { - // Is the list or set homogeneous? If so, use stricter rules. Otherwise relax the rules. - inferredEntryType = areTypesSame(entryTypes, { ignorePseudoGeneric: true }) - ? entryTypes[0] - : inferredEntryType; - } - } else { - isEmptyContainer = true; - } - - const listOrSetClass = getBuiltInType(node, builtInClassName); - const type = isInstantiableClass(listOrSetClass) - ? ClassType.cloneAsInstance( - ClassType.specialize( - listOrSetClass, - [inferredEntryType], - /* isTypeArgExplicit */ true, - /* includeSubclasses */ undefined, - /* tupleTypeArgs */ undefined, - isEmptyContainer - ) - ) - : UnknownType.create(); - - if (isIncomplete) { - if (getContainerDepth(type) > maxInferredContainerDepth) { - return { type: UnknownType.create() }; - } - } - - return { type, isIncomplete, typeErrors }; - } - - function verifySetEntryOrDictKeyIsHashable(entry: ExpressionNode, type: Type, isDictKey: boolean) { - // Verify that the type is hashable. - if (!isTypeHashable(type)) { - const diag = new DiagnosticAddendum(); - diag.addMessage(LocAddendum.unhashableType().format({ type: printType(type) })); - - const message = isDictKey ? LocMessage.unhashableDictKey() : LocMessage.unhashableSetEntry(); - - addDiagnostic(DiagnosticRule.reportUnhashable, message + diag.getString(), entry); - } - } - - function inferTypeArgFromExpectedEntryType( - inferenceContext: InferenceContext, - entryTypes: Type[], - isNarrowable: boolean - ): Type | undefined { - // If the expected type is Any, the resulting type becomes Any. - if (isAny(inferenceContext.expectedType)) { - return inferenceContext.expectedType; - } - - const constraints = new ConstraintTracker(); - const expectedType = inferenceContext.expectedType; - let isCompatible = true; - - entryTypes.forEach((entryType) => { - if (isCompatible && !assignType(expectedType, entryType, /* diag */ undefined, constraints)) { - isCompatible = false; - } - }); - - if (!isCompatible) { - return undefined; - } - - if (isNarrowable && entryTypes.length > 0) { - const combinedTypes = combineTypes(entryTypes); - return containsLiteralType(inferenceContext.expectedType) - ? combinedTypes - : stripLiteralValue(combinedTypes); - } - - return mapSubtypes( - solveAndApplyConstraints(inferenceContext.expectedType, constraints, { - replaceUnsolved: { - scopeIds: [], - tupleClassType: getTupleClassType(), - }, - }), - (subtype) => { - if (entryTypes.length !== 1) { - return subtype; - } - const entryType = entryTypes[0]; - - // If the entry type is a TypedDict instance, clone it with additional information. - if ( - isTypeSame(subtype, entryType, { ignoreTypedDictNarrowEntries: true }) && - isClass(subtype) && - isClass(entryType) && - ClassType.isTypedDictClass(entryType) - ) { - return ClassType.cloneForNarrowedTypedDictEntries(subtype, entryType.priv.typedDictNarrowedEntries); - } - - return subtype; - } - ); - } - - function getTypeOfYield(node: YieldNode): TypeResult { - let expectedYieldType: Type | undefined; - let sentType: Type | undefined; - let isIncomplete = false; - - const enclosingFunction = ParseTreeUtils.getEnclosingFunction(node); - if (enclosingFunction) { - const functionTypeInfo = getTypeOfFunction(enclosingFunction); - if (functionTypeInfo) { - let returnType = FunctionType.getEffectiveReturnType(functionTypeInfo.functionType); - if (returnType) { - const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(node); - returnType = makeTypeVarsBound(returnType, liveScopeIds); - - expectedYieldType = getGeneratorYieldType(returnType, !!enclosingFunction.d.isAsync); - - const generatorTypeArgs = getGeneratorTypeArgs(returnType); - if (generatorTypeArgs && generatorTypeArgs.length >= 2) { - sentType = makeTypeVarsBound(generatorTypeArgs[1], liveScopeIds); - } - } - } - } - - if (node.d.expr) { - const exprResult = getTypeOfExpression( - node.d.expr, - /* flags */ undefined, - makeInferenceContext(expectedYieldType) - ); - if (exprResult.isIncomplete) { - isIncomplete = true; - } - } - - return { type: sentType || UnknownType.create(), isIncomplete }; - } - - function getTypeOfYieldFrom(node: YieldFromNode): TypeResult { - const yieldFromTypeResult = getTypeOfExpression(node.d.expr); - const yieldFromType = yieldFromTypeResult.type; - - const returnedType = mapSubtypes(yieldFromType, (yieldFromSubtype) => { - // Is the expression a Generator type? - let generatorTypeArgs = getGeneratorTypeArgs(yieldFromSubtype); - if (generatorTypeArgs) { - return generatorTypeArgs.length >= 2 ? generatorTypeArgs[2] : UnknownType.create(); - } - - // Handle old-style (pre-await) Coroutines as a special case. - if ( - isClassInstance(yieldFromSubtype) && - ClassType.isBuiltIn(yieldFromSubtype, ['Coroutine', 'CoroutineType']) - ) { - return UnknownType.create(); - } - - // Handle simple iterables. - const iterableType = - getTypeOfIterable(yieldFromTypeResult, /* isAsync */ false, node)?.type ?? UnknownType.create(); - - // Does the iterable return a Generator? - generatorTypeArgs = getGeneratorTypeArgs(iterableType); - return generatorTypeArgs && generatorTypeArgs.length >= 2 ? generatorTypeArgs[2] : UnknownType.create(); - }); - - return { type: returnedType }; - } - - function getTypeOfLambda(node: LambdaNode, inferenceContext: InferenceContext | undefined): TypeResult { - let expectedFunctionTypes: FunctionType[] = []; - if (inferenceContext) { - mapSubtypes(inferenceContext.expectedType, (subtype) => { - if (isFunction(subtype)) { - expectedFunctionTypes.push(subtype); - } - - if (isClassInstance(subtype)) { - const boundMethod = getBoundMagicMethod(subtype, '__call__'); - if (boundMethod && isFunction(boundMethod)) { - expectedFunctionTypes.push(boundMethod as FunctionType); - } - } - - return undefined; - }); - } - - let expectedSubtype: FunctionType | undefined; - - // If there's more than one type, try each in turn until we find one that works. - if (expectedFunctionTypes.length > 1) { - // Sort the expected types for deterministic results. - expectedFunctionTypes = sortTypes(expectedFunctionTypes) as FunctionType[]; - - for (const subtype of expectedFunctionTypes) { - const result = getTypeOfLambdaWithExpectedType( - node, - subtype, - inferenceContext, - /* forceSpeculative */ true - ); - - if (!result.typeErrors) { - expectedSubtype = subtype; - break; - } - } - } - - if (!expectedSubtype && expectedFunctionTypes.length > 0) { - expectedSubtype = expectedFunctionTypes[0]; - } - - return getTypeOfLambdaWithExpectedType(node, expectedSubtype, inferenceContext, /* forceSpeculative */ false); - } - - function getTypeOfLambdaWithExpectedType( - node: LambdaNode, - expectedType: FunctionType | undefined, - inferenceContext: InferenceContext | undefined, - forceSpeculative: boolean - ): TypeResult { - let isIncomplete = !!inferenceContext?.isTypeIncomplete; - let paramsArePositionOnly = true; - - let expectedReturnType: Type | undefined; - let expectedParamDetails: ParamListDetails | undefined; - - if (expectedType) { - const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(node); - expectedType = transformExpectedType(expectedType, liveTypeVarScopes, node.start) as FunctionType; - - expectedParamDetails = getParamListDetails(expectedType); - expectedReturnType = getEffectiveReturnType(expectedType); - } - - let functionType = FunctionType.createInstance('', '', '', FunctionTypeFlags.PartiallyEvaluated); - functionType.shared.typeVarScopeId = ParseTreeUtils.getScopeIdForNode(node); - - return invalidateTypeCacheIfCanceled(() => { - // Pre-cache the incomplete function type in case the evaluation of the - // lambda depends on itself. - writeTypeCache(node, { type: functionType, isIncomplete: true }, EvalFlags.None); - - // We assume for simplicity that the parameter signature of the lambda is - // the same as the expected type. If this isn't the case, we'll use - // object for any lambda parameters that don't match. We could make this - // more sophisticated in the future, but it becomes very complex to handle - // all of the permutations. - let sawParamMismatch = false; - - node.d.params.forEach((param, index) => { - let paramType: Type | undefined; - - if (expectedParamDetails && !sawParamMismatch) { - if (index < expectedParamDetails.params.length) { - const expectedParam = expectedParamDetails.params[index]; - - // If the parameter category matches and both of the parameters are - // either separators (/ or *) or not separators, copy the type - // from the expected parameter. - if ( - expectedParam.param.category === param.d.category && - !param.d.name === !expectedParam.param.name - ) { - paramType = expectedParam.type; - } else { - sawParamMismatch = true; - } - } else if (param.d.defaultValue) { - // If the lambda param has a default value but there is no associated - // parameter in the expected type, assume that the default value is - // being used to explicitly capture a value from an outer scope. Infer - // its type from the default value expression. - paramType = getTypeOfExpression(param.d.defaultValue, undefined, inferenceContext).type; - } - } else if (param.d.defaultValue) { - // If there is no inference context but we have a default value, - // use the default value to infer the parameter's type. - paramType = inferParamTypeFromDefaultValue(param.d.defaultValue); - } - - if (param.d.name) { - writeTypeCache( - param.d.name, - { - type: transformVariadicParamType(node, param.d.category, paramType ?? UnknownType.create()), - }, - EvalFlags.None - ); - } - - if (param.d.defaultValue) { - // Evaluate the default value if it's present. - getTypeOfExpression(param.d.defaultValue, EvalFlags.ConvertEllipsisToAny); - } - - // Determine whether we need to insert an implied position-only parameter. - // This is needed when a function's parameters are named using the old-style - // way of specifying position-only parameters. - if (index >= 0) { - let isImplicitPositionOnlyParam = false; - - if (param.d.category === ParamCategory.Simple && param.d.name) { - if (isPrivateName(param.d.name.d.value)) { - isImplicitPositionOnlyParam = true; - } - } else { - paramsArePositionOnly = false; - } - - if ( - paramsArePositionOnly && - !isImplicitPositionOnlyParam && - functionType.shared.parameters.length > 0 - ) { - FunctionType.addPositionOnlyParamSeparator(functionType); - } - - if (!isImplicitPositionOnlyParam) { - paramsArePositionOnly = false; - } - } - - const functionParam = FunctionParam.create( - param.d.category, - paramType ?? UnknownType.create(), - FunctionParamFlags.TypeDeclared, - param.d.name ? param.d.name.d.value : undefined, - param.d.defaultValue ? AnyType.create(/* isEllipsis */ true) : undefined, - param.d.defaultValue - ); - - FunctionType.addParam(functionType, functionParam); - }); - - if (paramsArePositionOnly && functionType.shared.parameters.length > 0) { - FunctionType.addPositionOnlyParamSeparator(functionType); - } - - let typeErrors = false; - - // If we're speculatively evaluating the lambda, create another speculative - // evaluation scope for the return expression and do not allow retention - // of the cached types. - // We need to set allowCacheRetention to false because we don't want to - // cache the type of the lambda return expression because it depends on - // the parameter types that we set above, and the speculative type cache - // doesn't know about that context. - useSpeculativeMode( - forceSpeculative || isSpeculativeModeInUse(node) || inferenceContext?.isTypeIncomplete - ? node.d.expr - : undefined, - () => { - const returnTypeResult = getTypeOfExpression( - node.d.expr, - /* flags */ undefined, - makeInferenceContext(expectedReturnType) - ); - - functionType.shared.inferredReturnType = { - type: returnTypeResult.type, - }; - if (returnTypeResult.isIncomplete) { - isIncomplete = true; - } - - if (returnTypeResult.typeErrors) { - typeErrors = true; - } else if (expectedReturnType) { - // If the expectedReturnType is generic, see if the actual return type - // provides types for some or all type variables. - if (requiresSpecialization(expectedReturnType)) { - const constraints = new ConstraintTracker(); - if ( - assignType(expectedReturnType, returnTypeResult.type, /* diag */ undefined, constraints) - ) { - functionType = solveAndApplyConstraints(functionType, constraints, { - replaceUnsolved: { - scopeIds: [], - tupleClassType: getTupleClassType(), - }, - }) as FunctionType; - } - } - } - }, - { - dependentType: inferenceContext?.expectedType, - allowDiagnostics: - !forceSpeculative && !canSkipDiagnosticForNode(node) && !inferenceContext?.isTypeIncomplete, - } - ); - - // Mark the function type as no longer being evaluated. - functionType.shared.flags &= ~FunctionTypeFlags.PartiallyEvaluated; - - // Is the resulting function compatible with the expected type? - if (expectedType && !assignType(expectedType, functionType)) { - typeErrors = true; - } - - return { type: functionType, isIncomplete, typeErrors }; - }); - } - - function getTypeOfComprehension( - node: ComprehensionNode, - flags: EvalFlags, - inferenceContext?: InferenceContext - ): TypeResult { - let isIncomplete = false; - let typeErrors = false; - - // If any of the "for" clauses are marked async or any of the "if" clauses - // or any clause other than the leftmost "for" contain an "await" operator, - // it is treated as an async generator. - let isAsync = node.d.forIfNodes.some((comp, index) => { - if (comp.nodeType === ParseNodeType.ComprehensionFor && comp.d.isAsync) { - return true; - } - return index > 0 && ParseTreeUtils.containsAwaitNode(comp); - }); - let type: Type = UnknownType.create(); - - if (ParseTreeUtils.containsAwaitNode(node.d.expr)) { - isAsync = true; - } - - const builtInIteratorType = getTypingType(node, isAsync ? 'AsyncGenerator' : 'Generator'); - - const expectedEntryType = getExpectedEntryTypeForIterable(node, builtInIteratorType, inferenceContext); - const elementTypeResult = getElementTypeFromComprehension( - node, - flags | EvalFlags.StripTupleLiterals, - expectedEntryType - ); - - if (elementTypeResult.isIncomplete) { - isIncomplete = true; - } - - if (elementTypeResult.typeErrors) { - typeErrors = true; - } - - let elementType = elementTypeResult.type; - if (!expectedEntryType || !containsLiteralType(expectedEntryType)) { - elementType = stripLiteralValue(elementType); - } - - if (builtInIteratorType && isInstantiableClass(builtInIteratorType)) { - type = ClassType.cloneAsInstance( - ClassType.specialize( - builtInIteratorType, - isAsync ? [elementType, getNoneType()] : [elementType, getNoneType(), getNoneType()] - ) - ); - } - - return { type, isIncomplete, typeErrors }; - } - - function reportPossibleUnknownAssignment( - diagLevel: DiagnosticLevel, - rule: DiagnosticRule, - target: NameNode, - type: Type, - errorNode: ExpressionNode, - ignoreEmptyContainers: boolean - ) { - // Don't bother if the feature is disabled. - if (diagLevel === 'none') { - return; - } - - const nameValue = target.d.value; - - // Sometimes variables contain an "unbound" type if they're - // assigned only within conditional statements. Remove this - // to avoid confusion. - const simplifiedType = removeUnbound(type); - - if (isUnknown(simplifiedType)) { - addDiagnostic(rule, LocMessage.typeUnknown().format({ name: nameValue }), errorNode); - } else if (isPartlyUnknown(simplifiedType)) { - // If ignoreEmptyContainers is true, don't report the problem for - // empty containers (lists or dictionaries). We'll report the problem - // only if the assigned value is used later. - if (!ignoreEmptyContainers || !isClassInstance(type) || !type.priv.isEmptyContainer) { - const diagAddendum = new DiagnosticAddendum(); - diagAddendum.addMessage( - LocAddendum.typeOfSymbol().format({ - name: nameValue, - type: printType(simplifiedType, { expandTypeAlias: true }), - }) - ); - addDiagnostic( - rule, - LocMessage.typePartiallyUnknown().format({ name: nameValue }) + diagAddendum.getString(), - errorNode - ); - } - } - } - - function evaluateComprehensionForIf(node: ComprehensionForIfNode) { - let isIncomplete = false; - - if (node.nodeType === ParseNodeType.ComprehensionFor) { - const iterableTypeResult = getTypeOfExpression(node.d.iterableExpr); - if (iterableTypeResult.isIncomplete) { - isIncomplete = true; - } - const iterableType = stripLiteralValue(iterableTypeResult.type); - const itemTypeResult = getTypeOfIterator( - { type: iterableType, isIncomplete: iterableTypeResult.isIncomplete }, - !!node.d.isAsync, - node.d.iterableExpr - ) ?? { type: UnknownType.create(), isIncomplete: iterableTypeResult.isIncomplete }; - - const targetExpr = node.d.targetExpr; - assignTypeToExpression(targetExpr, itemTypeResult, node.d.iterableExpr); - } else { - assert(node.nodeType === ParseNodeType.ComprehensionIf); - - // Evaluate the test expression to validate it and mark symbols - // as referenced. This doesn't affect the type of the evaluated - // comprehension, but it is important for evaluating intermediate - // expressions such as assignment expressions that can affect other - // subexpressions. - getTypeOfExpression(node.d.testExpr); - } - - return isIncomplete; - } - - // Returns the type of one entry returned by the comprehension. - function getElementTypeFromComprehension( - node: ComprehensionNode, - flags: EvalFlags, - expectedValueOrElementType?: Type, - expectedKeyType?: Type - ): TypeResult { - let isIncomplete = false; - let typeErrors = false; - - // "Execute" the list comprehensions from start to finish. - for (const forIfNode of node.d.forIfNodes) { - if (evaluateComprehensionForIf(forIfNode)) { - isIncomplete = true; - } - } - - let type: Type = UnknownType.create(); - if (node.d.expr.nodeType === ParseNodeType.DictionaryKeyEntry) { - // Create a tuple with the key/value types. - const keyTypeResult = getTypeOfExpression( - node.d.expr.d.keyExpr, - flags, - makeInferenceContext(expectedKeyType) - ); - if (keyTypeResult.isIncomplete) { - isIncomplete = true; - } - if (keyTypeResult.typeErrors) { - typeErrors = true; - } - let keyType = keyTypeResult.type; - if (!expectedKeyType || !containsLiteralType(expectedKeyType)) { - keyType = stripLiteralValue(keyType); - } - - const valueTypeResult = getTypeOfExpression( - node.d.expr.d.valueExpr, - flags, - makeInferenceContext(expectedValueOrElementType) - ); - if (valueTypeResult.isIncomplete) { - isIncomplete = true; - } - if (valueTypeResult.typeErrors) { - typeErrors = true; - } - let valueType = valueTypeResult.type; - if (!expectedValueOrElementType || !containsLiteralType(expectedValueOrElementType)) { - valueType = stripLiteralValue(valueType); - } - - type = makeTupleObject(evaluatorInterface, [ - { type: keyType, isUnbounded: false }, - { type: valueType, isUnbounded: false }, - ]); - } else if (node.d.expr.nodeType === ParseNodeType.DictionaryExpandEntry) { - // The parser should have reported an error in this case because it's not allowed. - getTypeOfExpression(node.d.expr.d.expr, flags, makeInferenceContext(expectedValueOrElementType)); - } else if (isExpressionNode(node)) { - const exprTypeResult = getTypeOfExpression( - node.d.expr as ExpressionNode, - flags, - makeInferenceContext(expectedValueOrElementType) - ); - if (exprTypeResult.isIncomplete) { - isIncomplete = true; - } - if (exprTypeResult.typeErrors) { - typeErrors = true; - } - type = exprTypeResult.type; - } - - return { type, isIncomplete, typeErrors }; - } - - function getTypeOfSlice(node: SliceNode): TypeResult { - const noneType = getNoneType(); - let startType = noneType; - let endType = noneType; - let stepType = noneType; - let isIncomplete = false; - - // Evaluate the expressions to report errors and record symbol - // references. - if (node.d.startValue) { - const startTypeResult = getTypeOfExpression(node.d.startValue); - startType = startTypeResult.type; - if (startTypeResult.isIncomplete) { - isIncomplete = true; - } - } - - if (node.d.endValue) { - const endTypeResult = getTypeOfExpression(node.d.endValue); - endType = endTypeResult.type; - if (endTypeResult.isIncomplete) { - isIncomplete = true; - } - } - - if (node.d.stepValue) { - const stepTypeResult = getTypeOfExpression(node.d.stepValue); - stepType = stepTypeResult.type; - if (stepTypeResult.isIncomplete) { - isIncomplete = true; - } - } - - const sliceType = getBuiltInObject(node, 'slice'); - - if (!isClassInstance(sliceType)) { - return { type: sliceType }; - } - - return { type: ClassType.specialize(sliceType, [startType, endType, stepType]), isIncomplete }; - } - - // Verifies that a type argument's type is not disallowed. - function validateTypeArg(argResult: TypeResultWithNode, options?: ValidateTypeArgsOptions): boolean { - if (argResult.typeList) { - if (!options?.allowTypeArgList) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeArgListNotAllowed(), argResult.node); - return false; - } else { - argResult.typeList.forEach((typeArg) => { - validateTypeArg(typeArg); - }); - } - } - - if (isEllipsisType(argResult.type)) { - if (!options?.allowTypeArgList) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.ellipsisContext(), argResult.node); - return false; - } - } - - if (isModule(argResult.type)) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.moduleAsType(), argResult.node); - return false; - } - - if (isParamSpec(argResult.type)) { - if (!options?.allowParamSpec) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.paramSpecContext(), argResult.node); - return false; - } - } - - if (isTypeVarTuple(argResult.type) && !argResult.type.priv.isInUnion) { - if (!options?.allowTypeVarTuple) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeVarTupleContext(), argResult.node); - return false; - } else { - validateTypeVarTupleIsUnpacked(argResult.type, argResult.node); - } - } - - if (!options?.allowEmptyTuple && argResult.isEmptyTupleShorthand) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.zeroLengthTupleNotAllowed(), argResult.node); - return false; - } - - if (isUnpackedClass(argResult.type)) { - if (!options?.allowUnpackedTuples) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.unpackedArgInTypeArgument(), - argResult.node - ); - return false; - } - } - - return true; - } - - // Evaluates the type arguments for a Callable type. It should have zero - // to two arguments.The first argument, if present, should be an ellipsis, - // a ParamSpec, a Concatenate, or a list of positional parameter types. - // The second argument, if present, should specify the return type. - function createCallableType( - classType: ClassType, - typeArgs: TypeResultWithNode[] | undefined, - errorNode: ParseNode - ): FunctionType { - let functionType = FunctionType.createInstantiable(FunctionTypeFlags.None); - let paramSpec: ParamSpecType | undefined; - let isValidTypeForm = true; - - TypeBase.setSpecialForm(functionType, ClassType.cloneAsInstance(classType)); - functionType.shared.declaredReturnType = UnknownType.create(); - functionType.shared.typeVarScopeId = ParseTreeUtils.getScopeIdForNode(errorNode); - - if (typeArgs && typeArgs.length > 0) { - functionType.priv.isCallableWithTypeArgs = true; - - if (typeArgs[0].typeList) { - const typeList = typeArgs[0].typeList; - let sawUnpacked = false; - let reportedUnpackedError = false; - const noteSawUnpacked = (entry: TypeResultWithNode) => { - // Make sure we have at most one unpacked TypeVarTuple. - if (sawUnpacked) { - if (!reportedUnpackedError) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.variadicTypeArgsTooMany(), - entry.node - ); - reportedUnpackedError = true; - isValidTypeForm = false; - } - } - sawUnpacked = true; - }; - - typeList.forEach((entry, index) => { - let entryType = entry.type; - let paramCategory: ParamCategory = ParamCategory.Simple; - const paramName = `__p${index.toString()}`; - - if (isTypeVarTuple(entryType)) { - validateTypeVarTupleIsUnpacked(entryType, entry.node); - paramCategory = ParamCategory.ArgsList; - noteSawUnpacked(entry); - } else if (validateTypeArg(entry, { allowUnpackedTuples: true })) { - if (isUnpackedClass(entryType)) { - paramCategory = ParamCategory.ArgsList; - - if ( - entryType.priv.tupleTypeArgs?.some( - (typeArg) => isTypeVarTuple(typeArg.type) || typeArg.isUnbounded - ) - ) { - noteSawUnpacked(entry); - } - } - } else { - entryType = UnknownType.create(); - } - - FunctionType.addParam( - functionType, - FunctionParam.create( - paramCategory, - convertToInstance(entryType), - FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared, - paramName - ) - ); - }); - - if (typeList.length > 0) { - FunctionType.addPositionOnlyParamSeparator(functionType); - } - } else if (isEllipsisType(typeArgs[0].type)) { - FunctionType.addDefaultParams(functionType); - functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm; - } else if (isParamSpec(typeArgs[0].type)) { - paramSpec = typeArgs[0].type; - } else { - if (isInstantiableClass(typeArgs[0].type) && ClassType.isBuiltIn(typeArgs[0].type, 'Concatenate')) { - const concatTypeArgs = typeArgs[0].type.priv.typeArgs; - if (concatTypeArgs && concatTypeArgs.length > 0) { - concatTypeArgs.forEach((typeArg, index) => { - if (index === concatTypeArgs.length - 1) { - FunctionType.addPositionOnlyParamSeparator(functionType); - - if (isParamSpec(typeArg)) { - paramSpec = typeArg; - } else if (isEllipsisType(typeArg)) { - FunctionType.addDefaultParams(functionType); - functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm; - } - } else { - FunctionType.addParam( - functionType, - FunctionParam.create( - ParamCategory.Simple, - typeArg, - FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared, - `__p${index}` - ) - ); - } - }); - } - } else { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.callableFirstArg(), - typeArgs[0].node - ); - isValidTypeForm = false; - } - } - - if (typeArgs.length > 1) { - let typeArg1Type = typeArgs[1].type; - if (!validateTypeArg(typeArgs[1])) { - typeArg1Type = UnknownType.create(); - } - functionType.shared.declaredReturnType = convertToInstance(typeArg1Type); - } else { - addDiagnostic(DiagnosticRule.reportMissingTypeArgument, LocMessage.callableSecondArg(), errorNode); - - functionType.shared.declaredReturnType = UnknownType.create(); - isValidTypeForm = false; - } - - if (typeArgs.length > 2) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.callableExtraArgs(), typeArgs[2].node); - isValidTypeForm = false; - } - } else { - FunctionType.addDefaultParams(functionType, /* useUnknown */ true); - functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm; - - if (typeArgs && typeArgs.length === 0) { - isValidTypeForm = false; - } - } - - if (paramSpec) { - FunctionType.addParamSpecVariadics(functionType, convertToInstance(paramSpec)); - } - - if (isValidTypeForm) { - functionType = TypeBase.cloneWithTypeForm(functionType, convertToInstance(functionType)); - } - - return functionType; - } - - // Creates an Optional[X] type. - function createOptionalType( - classType: ClassType, - errorNode: ParseNode, - typeArgs: TypeResultWithNode[] | undefined, - flags: EvalFlags - ): Type { - if (!typeArgs) { - // If no type arguments are provided, the resulting type - // depends on whether we're evaluating a type annotation or - // we're in some other context. - if ((flags & EvalFlags.TypeExpression) !== 0) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.optionalExtraArgs(), errorNode); - return UnknownType.create(); - } - - return classType; - } - - if (typeArgs.length !== 1) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.optionalExtraArgs(), errorNode); - return UnknownType.create(); - } - - let typeArg0Type = typeArgs[0].type; - if (!validateTypeArg(typeArgs[0])) { - typeArg0Type = UnknownType.create(); - } - - let optionalType = combineTypes([typeArg0Type, prefetched?.noneTypeClass ?? UnknownType.create()]); - if (prefetched?.unionTypeClass && isInstantiableClass(prefetched.unionTypeClass)) { - optionalType = TypeBase.cloneAsSpecialForm( - optionalType, - ClassType.cloneAsInstance(prefetched.unionTypeClass) - ); - } - - if (typeArg0Type.props?.typeForm) { - const typeFormType = combineTypes([ - typeArg0Type.props.typeForm, - convertToInstance(prefetched?.noneTypeClass ?? UnknownType.create()), - ]); - optionalType = TypeBase.cloneWithTypeForm(optionalType, typeFormType); - } - - return optionalType; - } - - function cloneBuiltinObjectWithLiteral(node: ParseNode, builtInName: string, value: LiteralValue): Type { - const type = getBuiltInObject(node, builtInName); - if (isClassInstance(type)) { - return ClassType.cloneWithLiteral(ClassType.cloneRemoveTypePromotions(type), value); - } - - return UnknownType.create(); - } - - function cloneBuiltinClassWithLiteral( - node: ParseNode, - literalClassType: ClassType, - builtInName: string, - value: LiteralValue - ): Type { - const type = getBuiltInType(node, builtInName); - if (isInstantiableClass(type)) { - const literalType = ClassType.cloneWithLiteral(type, value); - TypeBase.setSpecialForm(literalType, literalClassType); - return literalType; - } - - return UnknownType.create(); - } - - // Creates a type that represents a Literal. - function createLiteralType(classType: ClassType, node: IndexNode, flags: EvalFlags): Type { - if (node.d.items.length === 0) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.literalEmptyArgs(), node.d.leftExpr); - return UnknownType.create(); - } - - // As per the specification, we support None, int, bool, str, bytes literals - // plus enum values. - const literalTypes: Type[] = []; - let isValidTypeForm = true; - - for (const item of node.d.items) { - let type: Type | undefined; - const itemExpr = item.d.valueExpr; - - if (item.d.argCategory !== ArgCategory.Simple) { - if ((flags & EvalFlags.TypeExpression) !== 0) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.unpackedArgInTypeArgument(), - itemExpr - ); - type = UnknownType.create(); - isValidTypeForm = false; - } - } else if (item.d.name) { - if ((flags & EvalFlags.TypeExpression) !== 0) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.keywordArgInTypeArgument(), - itemExpr - ); - type = UnknownType.create(); - isValidTypeForm = false; - } - } else if (itemExpr.nodeType === ParseNodeType.StringList) { - const isBytes = (itemExpr.d.strings[0].d.token.flags & StringTokenFlags.Bytes) !== 0; - const value = itemExpr.d.strings.map((s) => s.d.value).join(''); - if (isBytes) { - type = cloneBuiltinClassWithLiteral(node, classType, 'bytes', value); - } else { - type = cloneBuiltinClassWithLiteral(node, classType, 'str', value); - } - - if ((flags & EvalFlags.TypeExpression) !== 0) { - itemExpr.d.strings.forEach((stringNode) => { - if ((stringNode.d.token.flags & StringTokenFlags.NamedUnicodeEscape) !== 0) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.literalNamedUnicodeEscape(), - stringNode - ); - isValidTypeForm = false; - } - }); - } - } else if (itemExpr.nodeType === ParseNodeType.Number) { - if (!itemExpr.d.isImaginary && itemExpr.d.isInteger) { - type = cloneBuiltinClassWithLiteral(node, classType, 'int', itemExpr.d.value); - } - } else if (itemExpr.nodeType === ParseNodeType.Constant) { - if (itemExpr.d.constType === KeywordType.True) { - type = cloneBuiltinClassWithLiteral(node, classType, 'bool', true); - } else if (itemExpr.d.constType === KeywordType.False) { - type = cloneBuiltinClassWithLiteral(node, classType, 'bool', false); - } else if (itemExpr.d.constType === KeywordType.None) { - type = prefetched?.noneTypeClass ?? UnknownType.create(); - } - } else if (itemExpr.nodeType === ParseNodeType.UnaryOperation) { - if (itemExpr.d.operator === OperatorType.Subtract || itemExpr.d.operator === OperatorType.Add) { - if (itemExpr.d.expr.nodeType === ParseNodeType.Number) { - if (!itemExpr.d.expr.d.isImaginary && itemExpr.d.expr.d.isInteger) { - type = cloneBuiltinClassWithLiteral( - node, - classType, - 'int', - itemExpr.d.operator === OperatorType.Subtract - ? -itemExpr.d.expr.d.value - : itemExpr.d.expr.d.value - ); - } - } - } - } - - if (!type) { - const exprType = getTypeOfExpression( - itemExpr, - (flags & (EvalFlags.ForwardRefs | EvalFlags.TypeExpression)) | EvalFlags.NoConvertSpecialForm - ); - - // Is this an enum type? - if ( - isClassInstance(exprType.type) && - ClassType.isEnumClass(exprType.type) && - exprType.type.priv.literalValue !== undefined - ) { - type = ClassType.cloneAsInstantiable(exprType.type); - } else { - // Is this a type alias to an existing literal type? - let isLiteralType = true; - - doForEachSubtype(exprType.type, (subtype) => { - if (!isInstantiableClass(subtype) || subtype.priv.literalValue === undefined) { - if (!isNoneTypeClass(subtype)) { - isLiteralType = false; - } - } - }); - - if (isLiteralType) { - type = exprType.type; - } - } - } - - if (!type) { - if ((flags & EvalFlags.TypeExpression) !== 0) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.literalUnsupportedType(), item); - type = UnknownType.create(); - isValidTypeForm = false; - } else { - return ClassType.cloneAsInstance(classType); - } - } - - literalTypes.push(type); - } - - let result = combineTypes(literalTypes, { skipElideRedundantLiterals: true }); - - if (isUnion(result) && prefetched?.unionTypeClass && isInstantiableClass(prefetched.unionTypeClass)) { - result = TypeBase.cloneAsSpecialForm(result, ClassType.cloneAsInstance(prefetched.unionTypeClass)); - } - - if (isValidTypeForm) { - result = TypeBase.cloneWithTypeForm(result, convertToInstance(result)); - } - - return result; - } - - // Creates a ClassVar type. - function createClassVarType( - classType: ClassType, - errorNode: ParseNode, - typeArgs: TypeResultWithNode[] | undefined, - flags: EvalFlags - ): Type { - if (flags & EvalFlags.NoClassVar) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.classVarNotAllowed(), errorNode); - return AnyType.create(); - } - - if (!typeArgs) { - return classType; - } else if (typeArgs.length === 0) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.classVarFirstArgMissing(), errorNode); - return UnknownType.create(); - } else if (typeArgs.length > 1) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.classVarTooManyArgs(), typeArgs[1].node); - return UnknownType.create(); - } - - const type = typeArgs[0].type; - - // A ClassVar should not allow TypeVars or generic types parameterized - // by TypeVars. - if (requiresSpecialization(type, { ignorePseudoGeneric: true, ignoreSelf: true })) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.classVarWithTypeVar(), - typeArgs[0].node ?? errorNode - ); - } - - return type; - } - - function createTypeFormType( - classType: ClassType, - errorNode: ExpressionNode, - typeArgs: TypeResultWithNode[] | undefined - ): Type { - if (!typeArgs || typeArgs.length === 0) { - return ClassType.specialize(classType, [UnknownType.create()]); - } - - if (typeArgs.length > 1) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.typeArgsTooMany().format({ - name: classType.priv.aliasName || classType.shared.name, - expected: 1, - received: typeArgs.length, - }), - typeArgs[1].node - ); - return UnknownType.create(); - } - - const convertedTypeArgs = typeArgs.map((typeArg) => { - return convertToInstance(validateTypeArg(typeArg) ? typeArg.type : UnknownType.create()); - }); - let resultType = ClassType.specialize(classType, convertedTypeArgs); - - resultType = TypeBase.cloneWithTypeForm(resultType, convertToInstance(resultType)); - - return resultType; - } - - // Creates a "TypeGuard" and "TypeIs" type. - function createTypeGuardType( - classType: ClassType, - errorNode: ParseNode, - typeArgs: TypeResultWithNode[] | undefined, - flags: EvalFlags - ): Type { - // If no type arguments are provided, the resulting type - // depends on whether we're evaluating a type annotation or - // we're in some other context. - if (!typeArgs) { - if ((flags & EvalFlags.TypeExpression) !== 0) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeGuardArgCount(), errorNode); - } - - return classType; - } else if (typeArgs.length !== 1) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeGuardArgCount(), errorNode); - return UnknownType.create(); - } - - const convertedTypeArgs = typeArgs.map((typeArg) => { - return convertToInstance(validateTypeArg(typeArg) ? typeArg.type : UnknownType.create()); - }); - - let resultType = ClassType.specialize(classType, convertedTypeArgs); - - resultType = TypeBase.cloneWithTypeForm(resultType, convertToInstance(resultType)); - - return resultType; - } - - function createSelfType( - classType: ClassType, - errorNode: ParseNode, - typeArgs: TypeResultWithNode[] | undefined, - flags: EvalFlags - ) { - // Self doesn't support any type arguments. - if (typeArgs && typeArgs.length > 0) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeArguments, - LocMessage.typeArgsExpectingNone().format({ - name: classType.shared.name, - }), - typeArgs[0].node ?? errorNode - ); - } - - let enclosingClass = ParseTreeUtils.getEnclosingClass(errorNode); - - // If `Self` appears anywhere outside of the class body (e.g. a decorator, - // base class list, metaclass argument, type parameter list), it is - // considered illegal. - if (enclosingClass && !ParseTreeUtils.isNodeContainedWithin(errorNode, enclosingClass.d.suite)) { - enclosingClass = undefined; - } - - const enclosingClassTypeResult = enclosingClass ? getTypeOfClass(enclosingClass) : undefined; - if (!enclosingClassTypeResult) { - if ((flags & (EvalFlags.TypeExpression | EvalFlags.InstantiableType)) !== 0) { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.selfTypeContext(), errorNode); - } - - return UnknownType.create(); - } else if (isInstantiableMetaclass(enclosingClassTypeResult.classType)) { - // If `Self` appears within a metaclass, it is considered illegal. - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.selfTypeMetaclass(), errorNode); - - return UnknownType.create(); - } - - const enclosingFunction = ParseTreeUtils.getEnclosingFunction(errorNode); - if (enclosingFunction) { - const functionInfo = getFunctionInfoFromDecorators( - evaluatorInterface, - enclosingFunction, - /* isInClass */ true - ); - - const isInnerFunction = !!ParseTreeUtils.getEnclosingFunction(enclosingFunction); - if (!isInnerFunction) { - // Check for static methods. - if (functionInfo.flags & FunctionTypeFlags.StaticMethod) { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.selfTypeContext(), errorNode); - - return UnknownType.create(); - } - - if (enclosingFunction.d.params.length > 0) { - const firstParamTypeAnnotation = ParseTreeUtils.getTypeAnnotationForParam(enclosingFunction, 0); - if ( - firstParamTypeAnnotation && - !ParseTreeUtils.isNodeContainedWithin(errorNode, firstParamTypeAnnotation) - ) { - const annotationType = getTypeOfAnnotation(firstParamTypeAnnotation, { - typeVarGetsCurScope: true, - }); - if (!isTypeVar(annotationType) || !TypeVarType.isSelf(annotationType)) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.selfTypeWithTypedSelfOrCls(), - errorNode - ); - } - } - } - } - } - - let result = synthesizeTypeVarForSelfCls(enclosingClassTypeResult.classType, /* isClsParam */ true); - - if (enclosingClass) { - // If "Self" is used as a type expression within a function suite, it needs - // to be marked as bound. - const enclosingSuite = ParseTreeUtils.getEnclosingClassOrFunctionSuite(errorNode); - - if (enclosingSuite && ParseTreeUtils.isNodeContainedWithin(enclosingSuite, enclosingClass)) { - if (enclosingClass.d.suite !== enclosingSuite) { - result = TypeVarType.cloneAsBound(result); - } - } - } - - return result; - } - - function createRequiredOrReadOnlyType( - classType: ClassType, - errorNode: ParseNode, - typeArgs: TypeResultWithNode[] | undefined, - flags: EvalFlags - ): TypeResult { - // If no type arguments are provided, the resulting type - // depends on whether we're evaluating a type annotation or - // we're in some other context. - if (!typeArgs && (flags & EvalFlags.TypeExpression) === 0) { - return { type: classType }; - } - - if (!typeArgs || typeArgs.length !== 1) { - if ((flags & EvalFlags.TypeExpression) !== 0) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - classType.shared.name === 'ReadOnly' - ? LocMessage.readOnlyArgCount() - : classType.shared.name === 'Required' - ? LocMessage.requiredArgCount() - : LocMessage.notRequiredArgCount(), - errorNode - ); - } - - return { type: classType }; - } - - const typeArgType = typeArgs[0].type; - - // Make sure this is used only in a dataclass. - const containingClassNode = ParseTreeUtils.getEnclosingClass(errorNode, /* stopAtFunction */ true); - const classTypeInfo = containingClassNode ? getTypeOfClass(containingClassNode) : undefined; - - let isUsageLegal = false; - - if ( - classTypeInfo && - isInstantiableClass(classTypeInfo.classType) && - ClassType.isTypedDictClass(classTypeInfo.classType) - ) { - // The only legal usage is when used in a type annotation statement. - if (ParseTreeUtils.isNodeContainedWithinNodeType(errorNode, ParseNodeType.TypeAnnotation)) { - isUsageLegal = true; - } - } - - let isReadOnly = typeArgs[0].isReadOnly; - let isRequired = typeArgs[0].isRequired; - let isNotRequired = typeArgs[0].isNotRequired; - - if (classType.shared.name === 'ReadOnly') { - if ((flags & EvalFlags.AllowReadOnly) !== 0) { - isUsageLegal = true; - } - - // Nested ReadOnly are not allowed. - if (typeArgs[0].isReadOnly) { - isUsageLegal = false; - } - - isReadOnly = true; - } else { - if ((flags & EvalFlags.AllowRequired) !== 0) { - isUsageLegal = true; - } - - // Nested Required/NotRequired are not allowed. - if (typeArgs[0].isRequired || typeArgs[0].isNotRequired) { - isUsageLegal = false; - } - - isRequired = classType.shared.name === 'Required'; - isNotRequired = classType.shared.name === 'NotRequired'; - } - - if (!isUsageLegal) { - if ((flags & EvalFlags.TypeExpression) !== 0) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - classType.shared.name === 'ReadOnly' - ? LocMessage.readOnlyNotInTypedDict() - : classType.shared.name === 'Required' - ? LocMessage.requiredNotInTypedDict() - : LocMessage.notRequiredNotInTypedDict(), - errorNode - ); - } - - return { type: classType }; - } - - return { type: typeArgType, isReadOnly, isRequired, isNotRequired }; - } - - function createUnpackType( - classType: ClassType, - errorNode: ParseNode, - typeArgs: TypeResultWithNode[] | undefined, - flags: EvalFlags - ): Type { - if (!typeArgs || typeArgs.length !== 1) { - if ((flags & EvalFlags.TypeExpression) !== 0) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.unpackArgCount(), errorNode); - } - return classType; - } - - const typeArgType = typeArgs[0].type; - - if ((flags & EvalFlags.AllowUnpackedTuple) !== 0) { - const unpackedType = applyUnpackToTupleLike(typeArgType); - if (unpackedType) { - return unpackedType; - } - - if ((flags & EvalFlags.TypeExpression) === 0) { - return classType; - } - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.unpackExpectedTypeVarTuple(), errorNode); - return UnknownType.create(); - } - - if ((flags & EvalFlags.AllowUnpackedTypedDict) !== 0) { - if (isInstantiableClass(typeArgType) && ClassType.isTypedDictClass(typeArgType)) { - return ClassType.cloneForUnpacked(typeArgType); - } - - if ((flags & EvalFlags.TypeExpression) === 0) { - return classType; - } - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.unpackExpectedTypedDict(), errorNode); - return UnknownType.create(); - } - - if ((flags & EvalFlags.TypeExpression) === 0) { - return classType; - } - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.unpackNotAllowed(), errorNode); - return UnknownType.create(); - } - - // Creates a "Final" type. - function createFinalType( - classType: ClassType, - errorNode: ParseNode, - typeArgs: TypeResultWithNode[] | undefined, - flags: EvalFlags - ): Type { - if (flags & EvalFlags.NoFinal) { - if ((flags & EvalFlags.TypeExpression) !== 0) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.finalContext(), errorNode); - } - return classType; - } - - if ((flags & EvalFlags.TypeExpression) === 0 || !typeArgs || typeArgs.length === 0) { - return classType; - } - - if (typeArgs.length > 1) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.finalTooManyArgs(), errorNode); - } - - return TypeBase.cloneAsSpecialForm(typeArgs[0].type, classType); - } - - function createConcatenateType( - classType: ClassType, - errorNode: ParseNode, - typeArgs: TypeResultWithNode[] | undefined, - flags: EvalFlags - ): Type { - if ((flags & EvalFlags.AllowConcatenate) === 0) { - if ((flags & EvalFlags.TypeExpression) !== 0) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.concatenateContext(), errorNode); - } - return classType; - } - - if (!typeArgs || typeArgs.length === 0) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.concatenateTypeArgsMissing(), errorNode); - } else { - typeArgs.forEach((typeArg, index) => { - if (index === typeArgs.length - 1) { - if (!isParamSpec(typeArg.type) && !isEllipsisType(typeArg.type)) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.concatenateParamSpecMissing(), - typeArg.node - ); - } - } else { - if (isParamSpec(typeArg.type)) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.paramSpecContext(), - typeArg.node - ); - } else if (isUnpackedTypeVarTuple(typeArg.type)) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.typeVarTupleContext(), - typeArg.node - ); - } else if (isUnpackedClass(typeArg.type)) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.unpackedArgInTypeArgument(), - typeArg.node - ); - } - } - }); - } - - return createSpecialType(classType, typeArgs, /* paramLimit */ undefined, /* allowParamSpec */ true); - } - - function createAnnotatedType( - classType: ClassType, - errorNode: ExpressionNode, - typeArgs: TypeResultWithNode[] | undefined, - flags: EvalFlags - ): TypeResult { - let type: Type | undefined; - - const typeExprFlags = EvalFlags.TypeExpression | EvalFlags.NoConvertSpecialForm; - if ((flags & typeExprFlags) === 0) { - type = ClassType.cloneAsInstance(classType); - - if (typeArgs && typeArgs.length >= 1 && typeArgs[0].type.props?.typeForm) { - type = TypeBase.cloneWithTypeForm(type, typeArgs[0].type.props.typeForm); - } - - return { type }; - } - - if (typeArgs && typeArgs.length > 0) { - type = typeArgs[0].type; - - if (typeArgs.length < 2) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.annotatedTypeArgMissing(), errorNode); - } else { - type = validateAnnotatedMetadata(errorNode, typeArgs[0].type, typeArgs.slice(1)); - } - } - - if (!type || !typeArgs || typeArgs.length === 0) { - return { type: AnyType.create() }; - } - - if (typeArgs[0].typeList) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeArgListNotAllowed(), typeArgs[0].node); - } - - return { - type: TypeBase.cloneAsSpecialForm(type, ClassType.cloneAsInstance(classType)), - isReadOnly: typeArgs[0].isReadOnly, - isRequired: typeArgs[0].isRequired, - isNotRequired: typeArgs[0].isNotRequired, - }; - } - - // Enforces metadata consistency as specified in PEP 746. - function validateAnnotatedMetadata( - errorNode: ExpressionNode, - baseType: Type, - metaArgs: TypeResultWithNode[] - ): Type { - for (const metaArg of metaArgs) { - validateTypeMetadata(errorNode, baseType, metaArg); - } - - return baseType; - } - - // Determines whether the metadata object is compatible with the base type. - function validateTypeMetadata(errorNode: ExpressionNode, baseType: Type, metaArg: TypeResultWithNode): boolean { - // This function was added for draft PEP 746, but the functionality - // has been removed for now while the PEP is being revised. - return true; - } - - // Creates one of several "special" types that are defined in typing.pyi - // but not declared in their entirety. This includes the likes of "Tuple", - // "Dict", etc. - function createSpecialType( - classType: ClassType, - typeArgs: TypeResultWithNode[] | undefined, - paramLimit?: number, - allowParamSpec = false, - isSpecialForm = true - ): Type { - const isTupleTypeParam = ClassType.isTupleClass(classType); - - if (typeArgs) { - if (isTupleTypeParam && typeArgs.length === 1 && typeArgs[0].isEmptyTupleShorthand) { - typeArgs = []; - } else { - let sawUnpacked = false; - const noteSawUnpacked = (typeArg: TypeResultWithNode) => { - if (sawUnpacked) { - if (!reportedUnpackedError) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.variadicTypeArgsTooMany(), - typeArg.node - ); - reportedUnpackedError = true; - } - } - sawUnpacked = true; - }; - let reportedUnpackedError = false; - - // Verify that we didn't receive any inappropriate types. - typeArgs.forEach((typeArg, index) => { - assert(typeArgs !== undefined); - if (isEllipsisType(typeArg.type)) { - if (!isTupleTypeParam) { - if (!allowParamSpec) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.ellipsisContext(), - typeArg.node - ); - } - } else if (typeArgs.length !== 2 || index !== 1) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.ellipsisSecondArg(), - typeArg.node - ); - } else { - if (isTypeVarTuple(typeArgs[0].type) && !typeArgs[0].type.priv.isInUnion) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.typeVarTupleContext(), - typeArgs[0].node - ); - } else if (isUnpackedClass(typeArgs[0].type)) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.ellipsisAfterUnpacked(), - typeArg.node - ); - } - } - } else if (isParamSpec(typeArg.type) && allowParamSpec) { - // Nothing to do - this is allowed. - } else if (paramLimit === undefined && isTypeVarTuple(typeArg.type)) { - if (!typeArg.type.priv.isInUnion) { - noteSawUnpacked(typeArg); - } - validateTypeVarTupleIsUnpacked(typeArg.type, typeArg.node); - } else if (paramLimit === undefined && isUnpackedClass(typeArg.type)) { - if (isUnboundedTupleClass(typeArg.type)) { - noteSawUnpacked(typeArg); - } - validateTypeArg(typeArg, { allowUnpackedTuples: true }); - } else { - validateTypeArg(typeArg); - } - }); - } - } - - let typeArgTypes = typeArgs ? typeArgs.map((t) => convertToInstance(t.type)) : []; - - // Make sure the argument list count is correct. - if (paramLimit !== undefined) { - if (typeArgs && typeArgTypes.length > paramLimit) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.typeArgsTooMany().format({ - name: classType.priv.aliasName || classType.shared.name, - expected: paramLimit, - received: typeArgTypes.length, - }), - typeArgs[paramLimit].node - ); - typeArgTypes = typeArgTypes.slice(0, paramLimit); - } else if (typeArgTypes.length < paramLimit) { - // Fill up the remainder of the slots with unknown types. - while (typeArgTypes.length < paramLimit) { - typeArgTypes.push(UnknownType.create()); - } - } - } - - // Handle tuple type params as a special case. - let returnType: Type; - if (isTupleTypeParam) { - const tupleTypeArgTypes: TupleTypeArg[] = []; - - // If no type args are provided and it's a tuple, default to [Unknown, ...]. - if (!typeArgs) { - tupleTypeArgTypes.push({ type: UnknownType.create(), isUnbounded: true }); - } else { - typeArgs.forEach((typeArg, index) => { - if (index === 1 && isEllipsisType(typeArgTypes[index])) { - if (tupleTypeArgTypes.length === 1 && !tupleTypeArgTypes[0].isUnbounded) { - tupleTypeArgTypes[0] = { type: tupleTypeArgTypes[0].type, isUnbounded: true }; - } - } else if (isUnpackedClass(typeArg.type) && typeArg.type.priv.tupleTypeArgs) { - appendArray(tupleTypeArgTypes, typeArg.type.priv.tupleTypeArgs); - } else { - tupleTypeArgTypes.push({ type: typeArgTypes[index], isUnbounded: false }); - } - }); - } - - returnType = specializeTupleClass(classType, tupleTypeArgTypes, typeArgs !== undefined); - } else { - returnType = ClassType.specialize(classType, typeArgTypes, typeArgs !== undefined); - } - - if (isSpecialForm) { - returnType = TypeBase.cloneAsSpecialForm(returnType, classType); - } - - return returnType; - } - - // Unpacks the index expression for a "Union[X, Y, Z]" type annotation. - function createUnionType( - classType: ClassType, - errorNode: ParseNode, - typeArgs: TypeResultWithNode[] | undefined, - flags: EvalFlags - ): Type { - const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode); - const types: Type[] = []; - let allowSingleTypeArg = false; - let isValidTypeForm = true; - - if (!typeArgs) { - // If no type arguments are provided, the resulting type - // depends on whether we're evaluating a type annotation or - // we're in some other context. - if ((flags & EvalFlags.TypeExpression) !== 0) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.unionTypeArgCount(), errorNode); - return NeverType.createNever(); - } - - return classType; - } - - for (const typeArg of typeArgs) { - let typeArgType = typeArg.type; - - // This is an experimental feature because Unions of unpacked TypeVarTuples are not officially supported. - if ( - !validateTypeArg(typeArg, { - allowTypeVarTuple: fileInfo.diagnosticRuleSet.enableExperimentalFeatures, - }) - ) { - typeArgType = UnknownType.create(); - } - - if (isTypeVar(typeArgType) && isUnpackedTypeVarTuple(typeArgType)) { - // This is an experimental feature because Unions of unpacked TypeVarTuples are not officially supported. - if (fileInfo.diagnosticRuleSet.enableExperimentalFeatures) { - // If this is an unpacked TypeVar, note that it is in a union so we can - // differentiate between Unpack[Vs] and Union[Unpack[Vs]]. - typeArgType = TypeVarType.cloneForUnpacked(typeArgType, /* isInUnion */ true); - allowSingleTypeArg = true; - } else { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.unionUnpackedTypeVarTuple(), - errorNode - ); - - typeArgType = UnknownType.create(); - isValidTypeForm = false; - } - } - - types.push(typeArgType); - } - - // Validate that we received at least two type arguments. One type argument - // is allowed if it's an unpacked TypeVarTuple or tuple. None is also allowed - // since it is used to define NoReturn in typeshed stubs). - if (types.length === 1 && !allowSingleTypeArg && !isNoneInstance(types[0])) { - if ((flags & EvalFlags.TypeExpression) !== 0) { - addDiagnostic(DiagnosticRule.reportInvalidTypeArguments, LocMessage.unionTypeArgCount(), errorNode); - } - isValidTypeForm = false; - } - - let unionType = combineTypes(types, { skipElideRedundantLiterals: true }); - if (prefetched?.unionTypeClass && isInstantiableClass(prefetched.unionTypeClass)) { - unionType = TypeBase.cloneAsSpecialForm(unionType, ClassType.cloneAsInstance(prefetched.unionTypeClass)); - } - - if (!isValidTypeForm || types.some((t) => !t.props?.typeForm)) { - if (unionType.props?.typeForm) { - unionType = TypeBase.cloneWithTypeForm(unionType, undefined); - } - } else { - const typeFormType = combineTypes(types.map((t) => t.props!.typeForm!)); - unionType = TypeBase.cloneWithTypeForm(unionType, typeFormType); - } - - return unionType; - } - - // Creates a type that represents "Generic[T1, T2, ...]", used in the - // definition of a generic class. - function createGenericType( - classType: ClassType, - errorNode: ParseNode, - typeArgs: TypeResultWithNode[] | undefined, - flags: EvalFlags - ): Type { - if (!typeArgs) { - // If no type arguments are provided, the resulting type - // depends on whether we're evaluating a type annotation or - // we're in some other context. - if ((flags & (EvalFlags.TypeExpression | EvalFlags.NoNakedGeneric)) !== 0) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.genericTypeArgMissing(), errorNode); - } - - return classType; - } - - const uniqueTypeVars: TypeVarType[] = []; - if (typeArgs) { - // Make sure there's at least one type arg. - if (typeArgs.length === 0) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.genericTypeArgMissing(), errorNode); - } - - // Make sure that all of the type args are typeVars and are unique. - typeArgs.forEach((typeArg) => { - if (!isTypeVar(typeArg.type)) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.genericTypeArgTypeVar(), - typeArg.node - ); - } else { - if (uniqueTypeVars.some((t) => isTypeSame(t, typeArg.type))) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.genericTypeArgUnique(), - typeArg.node - ); - } - - uniqueTypeVars.push(typeArg.type); - } - }); - } - - return createSpecialType(classType, typeArgs, /* paramLimit */ undefined, /* allowParamSpec */ true); - } - - function transformTypeForTypeAlias( - type: Type, - errorNode: ExpressionNode, - typeAliasPlaceholder: TypeVarType, - isPep695TypeVarType: boolean, - typeParamNodes?: TypeParameterNode[] - ): Type { - // If this is a recursive type alias that hasn't yet been fully resolved - // (i.e. there is no boundType associated with it), don't apply the transform. - if (isTypeAliasPlaceholder(type)) { - return type; - } - - const sharedInfo = typeAliasPlaceholder.shared.recursiveAlias; - assert(sharedInfo !== undefined); - - let typeParams: TypeVarType[] | undefined = sharedInfo.typeParams; - if (!typeParams) { - // Determine if there are any generic type parameters associated - // with this type alias. - typeParams = []; - - addTypeVarsToListIfUnique(typeParams, getTypeVarArgsRecursive(type)); - - // Don't include any synthesized type variables. - typeParams = typeParams.filter((typeVar) => !typeVar.shared.isSynthesized); - } - - // Convert all type variables to instances. - typeParams = typeParams.map((typeVar) => { - if (TypeBase.isInstance(typeVar)) { - return typeVar; - } - return convertToInstance(typeVar); - }); - - // See if the type alias includes a TypeVarTuple followed by a TypeVar - // with a default value. This isn't allowed. - const firstTypeVarTupleIndex = typeParams.findIndex((typeVar) => isTypeVarTuple(typeVar)); - if (firstTypeVarTupleIndex >= 0) { - const typeVarWithDefaultIndex = typeParams.findIndex( - (typeVar, index) => - index > firstTypeVarTupleIndex && !isParamSpec(typeVar) && typeVar.shared.isDefaultExplicit - ); - - if (typeVarWithDefaultIndex >= 0) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarWithDefaultFollowsVariadic().format({ - typeVarName: typeParams[typeVarWithDefaultIndex].shared.name, - variadicName: typeParams[firstTypeVarTupleIndex].shared.name, - }), - typeParamNodes ? typeParamNodes[typeVarWithDefaultIndex].d.name : errorNode - ); - } - } - - // Validate the default types for all type parameters. - typeParams.forEach((typeParam, index) => { - assert(typeParams !== undefined); - let bestErrorNode = errorNode; - if (typeParamNodes && index < typeParamNodes.length) { - bestErrorNode = typeParamNodes[index].d.defaultExpr ?? typeParamNodes[index].d.name; - } - validateTypeParamDefault(bestErrorNode, typeParam, typeParams.slice(0, index), sharedInfo.typeVarScopeId); - }); - - // Verify that we have at most one TypeVarTuple. - const variadics = typeParams.filter((param) => isTypeVarTuple(param)); - if (variadics.length > 1) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.variadicTypeParamTooManyAlias().format({ - names: variadics.map((v) => `"${v.shared.name}"`).join(', '), - }), - errorNode - ); - } - - if (!sharedInfo.isTypeAliasType && !isPep695TypeVarType) { - const boundTypeVars = typeParams.filter( - (typeVar) => - typeVar.priv.scopeId !== sharedInfo.typeVarScopeId && - typeVar.priv.scopeType === TypeVarScopeType.Class - ); - - if (boundTypeVars.length > 0) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.genericTypeAliasBoundTypeVar().format({ - names: boundTypeVars.map((t) => `${t.shared.name}`).join(', '), - }), - errorNode - ); - } - } - - if (!TypeBase.isInstantiable(type)) { - return type; - } - - sharedInfo.typeParams = typeParams.length > 0 ? typeParams : undefined; - - let typeAlias = TypeBase.cloneForTypeAlias(type, { - shared: sharedInfo, - typeArgs: undefined, - }); - - // All PEP 695 type aliases are special forms because they are - // TypeAliasType objects at runtime. - if (sharedInfo.isTypeAliasType || isPep695TypeVarType) { - const typeAliasTypeClass = getTypingType(errorNode, 'TypeAliasType'); - if (typeAliasTypeClass && isInstantiableClass(typeAliasTypeClass)) { - typeAlias = TypeBase.cloneAsSpecialForm(typeAlias, ClassType.cloneAsInstance(typeAliasTypeClass)); - } - } - - // Delete the TypeForm info. The type alias serves as its own TypeForm info. - if (typeAlias.props?.typeForm) { - typeAlias = TypeBase.cloneWithTypeForm(typeAlias, undefined); - } - - return typeAlias; - } - - function createSpecialBuiltInClass(node: ParseNode, assignedName: string, aliasMapEntry: AliasMapEntry): ClassType { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - let specialClassType = ClassType.createInstantiable( - assignedName, - ParseTreeUtils.getClassFullName(node, fileInfo.moduleName, assignedName), - fileInfo.moduleName, - fileInfo.fileUri, - ClassTypeFlags.BuiltIn | ClassTypeFlags.SpecialBuiltIn, - /* typeSourceId */ 0, - /* declaredMetaclass */ undefined, - /* effectiveMetaclass */ undefined - ); - - if (aliasMapEntry.isSpecialForm) { - specialClassType.shared.flags |= ClassTypeFlags.SpecialFormClass; - } - - if (aliasMapEntry.isIllegalInIsinstance) { - specialClassType.shared.flags |= ClassTypeFlags.IllegalIsinstanceClass; - } - - // Synthesize a single type parameter with the specified variance if - // specified in the alias map entry. - if (aliasMapEntry.typeParamVariance !== undefined) { - let typeParam = TypeVarType.createInstance('T'); - typeParam = TypeVarType.cloneForScopeId( - typeParam, - ParseTreeUtils.getScopeIdForNode(node), - assignedName, - TypeVarScopeType.Class - ); - typeParam.shared.declaredVariance = aliasMapEntry.typeParamVariance; - specialClassType.shared.typeParams.push(typeParam); - } - - const specialBuiltInClassDeclaration = (AnalyzerNodeInfo.getDeclaration(node) ?? - (node.parent ? AnalyzerNodeInfo.getDeclaration(node.parent) : undefined)) as - | SpecialBuiltInClassDeclaration - | undefined; - - specialClassType.shared.declaration = specialBuiltInClassDeclaration; - - if (fileInfo.isTypingExtensionsStubFile) { - specialClassType.shared.flags |= ClassTypeFlags.TypingExtensionClass; - } - - const baseClassName = aliasMapEntry.implicitBaseClass || aliasMapEntry.alias || 'object'; - - let baseClass: Type | undefined; - if (aliasMapEntry.module === 'builtins') { - baseClass = getBuiltInType(node, baseClassName); - } else if (aliasMapEntry.module === 'collections') { - // The typing.pyi file imports collections. - baseClass = getTypeOfModule(node, baseClassName, ['collections']); - } else if (aliasMapEntry.module === 'internals') { - // Handle TypedDict specially. - assert(baseClassName === 'TypedDictFallback'); - baseClass = prefetched?.typedDictPrivateClass; - if (baseClass) { - // The TypedDictFallback class is marked as abstract, but the - // methods that are abstract are overridden and shouldn't - // cause the TypedDict to be marked as abstract. - if ( - isInstantiableClass(baseClass) && - ClassType.isBuiltIn(baseClass, ['_TypedDict', 'TypedDictFallback']) - ) { - baseClass = ClassType.cloneWithNewFlags( - baseClass, - baseClass.shared.flags & - ~(ClassTypeFlags.SupportsAbstractMethods | ClassTypeFlags.TypeCheckOnly) - ); - } - } - } - - if (baseClass && isInstantiableClass(baseClass)) { - if (aliasMapEntry.alias) { - specialClassType = ClassType.cloneForTypingAlias(baseClass, assignedName); - } else { - specialClassType.shared.baseClasses.push(baseClass); - specialClassType.shared.effectiveMetaclass = baseClass.shared.effectiveMetaclass; - computeMroLinearization(specialClassType); - } - } else { - specialClassType.shared.baseClasses.push(UnknownType.create()); - specialClassType.shared.effectiveMetaclass = UnknownType.create(); - computeMroLinearization(specialClassType); - } - - return specialClassType; - } - - // Handles some special-case type annotations that are found - // within the typings.pyi file. - function handleTypingStubTypeAnnotation(node: ExpressionNode): Type | undefined { - if (!node.parent || node.parent.nodeType !== ParseNodeType.TypeAnnotation) { - return undefined; - } - - if (node.parent.d.valueExpr.nodeType !== ParseNodeType.Name) { - return undefined; - } - - const nameNode = node.parent.d.valueExpr; - const assignedName = nameNode.d.value; - - const specialTypes: Map = new Map([ - ['Tuple', { alias: 'tuple', module: 'builtins' }], - ['Generic', { alias: '', module: 'builtins', isSpecialForm: true }], - ['Protocol', { alias: '', module: 'builtins', isSpecialForm: true }], - ['Callable', { alias: '', module: 'builtins', isSpecialForm: true }], - ['Type', { alias: 'type', module: 'builtins' }], - ['ClassVar', { alias: '', module: 'builtins', isSpecialForm: true }], - ['Final', { alias: '', module: 'builtins', isSpecialForm: true }], - ['Literal', { alias: '', module: 'builtins', isSpecialForm: true }], - ['TypedDict', { alias: 'TypedDictFallback', module: 'internals' }], - ['Union', { alias: '', module: 'builtins', isSpecialForm: true }], - ['Optional', { alias: '', module: 'builtins', isSpecialForm: true }], - ['Annotated', { alias: '', module: 'builtins', isSpecialForm: true, isIllegalInIsinstance: true }], - ['TypeAlias', { alias: '', module: 'builtins', isSpecialForm: true }], - ['Concatenate', { alias: '', module: 'builtins', isSpecialForm: true }], - [ - 'TypeGuard', - { - alias: '', - module: 'builtins', - implicitBaseClass: 'bool', - isSpecialForm: true, - typeParamVariance: Variance.Covariant, - }, - ], - ['Unpack', { alias: '', module: 'builtins', isSpecialForm: true }], - ['Required', { alias: '', module: 'builtins', isSpecialForm: true }], - ['NotRequired', { alias: '', module: 'builtins', isSpecialForm: true }], - ['Self', { alias: '', module: 'builtins', isSpecialForm: true }], - ['NoReturn', { alias: '', module: 'builtins', isSpecialForm: true }], - ['Never', { alias: '', module: 'builtins', isSpecialForm: true }], - ['LiteralString', { alias: '', module: 'builtins', isSpecialForm: true }], - ['ReadOnly', { alias: '', module: 'builtins', isSpecialForm: true }], - [ - 'TypeIs', - { - alias: '', - module: 'builtins', - implicitBaseClass: 'bool', - isSpecialForm: true, - typeParamVariance: Variance.Invariant, - }, - ], - [ - 'TypeForm', - { - alias: '', - module: 'builtins', - isSpecialForm: true, - typeParamVariance: Variance.Covariant, - isIllegalInIsinstance: true, - }, - ], - ]); - - const aliasMapEntry = specialTypes.get(assignedName); - - if (aliasMapEntry) { - const cachedType = readTypeCache(node, EvalFlags.None); - if (cachedType) { - return cachedType; - } - - let specialType: Type = createSpecialBuiltInClass(node, assignedName, aliasMapEntry); - - // Handle 'LiteralString' specially because we want it to act as - // though it derives from 'str'. - if (assignedName === 'LiteralString') { - specialType.shared.baseClasses.push(prefetched?.strClass ?? AnyType.create()); - computeMroLinearization(specialType); - - specialType = TypeBase.cloneWithTypeForm(specialType, convertToInstance(specialType)); - } - - // Handle 'Never' and 'NoReturn' specially. - if (assignedName === 'Never' || assignedName === 'NoReturn') { - specialType = TypeBase.cloneAsSpecialForm( - assignedName === 'Never' ? NeverType.createNever() : NeverType.createNoReturn(), - specialType - ); - - specialType = TypeBase.cloneWithTypeForm(specialType, convertToInstance(specialType)); - } - - writeTypeCache(node, { type: specialType }, EvalFlags.None); - return specialType; - } - - return undefined; - } - - // Handles some special-case assignment statements that are found - // within the typings.pyi file. - function handleTypingStubAssignment(node: AssignmentNode): Type | undefined { - if (node.d.leftExpr.nodeType !== ParseNodeType.Name) { - return undefined; - } - - const nameNode = node.d.leftExpr; - const assignedName = nameNode.d.value; - - if (assignedName === 'Any') { - return AnyType.createSpecialForm(); - } - - const specialTypes: Map = new Map([ - ['overload', { alias: '', module: 'builtins' }], - ['TypeVar', { alias: '', module: 'builtins' }], - ['_promote', { alias: '', module: 'builtins' }], - ['no_type_check', { alias: '', module: 'builtins' }], - ['NoReturn', { alias: '', module: 'builtins' }], - ['Never', { alias: '', module: 'builtins' }], - ['Counter', { alias: 'Counter', module: 'collections' }], - ['List', { alias: 'list', module: 'builtins' }], - ['Dict', { alias: 'dict', module: 'builtins' }], - ['DefaultDict', { alias: 'defaultdict', module: 'collections' }], - ['Set', { alias: 'set', module: 'builtins' }], - ['FrozenSet', { alias: 'frozenset', module: 'builtins' }], - ['Deque', { alias: 'deque', module: 'collections' }], - ['ChainMap', { alias: 'ChainMap', module: 'collections' }], - ['OrderedDict', { alias: 'OrderedDict', module: 'collections' }], - ]); - - const aliasMapEntry = specialTypes.get(assignedName); - if (aliasMapEntry) { - // Evaluate the expression so symbols are marked as accessed. - getTypeOfExpression(node.d.rightExpr); - return createSpecialBuiltInClass(node, assignedName, aliasMapEntry); - } - - return undefined; - } - - function evaluateTypesForAssignmentStatement(node: AssignmentNode): void { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - - // If the entire statement has already been evaluated, don't - // re-evaluate it. - if (isTypeCached(node)) { - return; - } - - let flags: EvalFlags = EvalFlags.None; - if (fileInfo.isStubFile) { - // An assignment of ellipsis means "Any" within a type stub file. - flags |= EvalFlags.ConvertEllipsisToAny; - } - - if ( - node.d.rightExpr.nodeType === ParseNodeType.Name || - node.d.rightExpr.nodeType === ParseNodeType.MemberAccess - ) { - // Don't specialize a generic class on assignment (e.g. "x = list" - // or "x = collections.OrderedDict") because we may want to later - // specialize it (e.g. "x[int]"). - flags |= EvalFlags.NoSpecialize; - } - - // Is this type already cached? - let rightHandType = readTypeCache(node.d.rightExpr, /* flags */ undefined); - let isIncomplete = false; - let expectedTypeDiagAddendum: DiagnosticAddendum | undefined; - - if (!rightHandType) { - // Special-case the typing.pyi file, which contains some special - // types that the type analyzer needs to interpret differently. - if (fileInfo.isTypingStubFile || fileInfo.isTypingExtensionsStubFile) { - rightHandType = handleTypingStubAssignment(node); - if (rightHandType) { - writeTypeCache(node.d.rightExpr, { type: rightHandType }, EvalFlags.None); - } - } - } - - if (!rightHandType) { - let typeAliasNameNode: NameNode | undefined; - let typeAliasPlaceholder: TypeVarType | undefined; - let isSpeculativeTypeAlias = false; - - if (isDeclaredTypeAlias(node.d.leftExpr)) { - flags = - EvalFlags.InstantiableType | - EvalFlags.TypeExpression | - EvalFlags.StrLiteralAsType | - EvalFlags.NoParamSpec | - EvalFlags.NoTypeVarTuple | - EvalFlags.NoClassVar; - - typeAliasNameNode = (node.d.leftExpr as TypeAnnotationNode).d.valueExpr as NameNode; - - if (!isLegalTypeAliasExpressionForm(node.d.rightExpr, /* allowStrLiteral */ true)) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.typeAliasIllegalExpressionForm(), - node.d.rightExpr - ); - } - } else if (node.d.leftExpr.nodeType === ParseNodeType.Name) { - const symbolWithScope = lookUpSymbolRecursive( - node.d.leftExpr, - node.d.leftExpr.d.value, - /* honorCodeFlow */ false - ); - - if (symbolWithScope) { - const decls = symbolWithScope.symbol.getDeclarations(); - - if (decls.length === 1) { - if (isPossibleTypeAliasDeclaration(decls[0])) { - typeAliasNameNode = node.d.leftExpr; - isSpeculativeTypeAlias = true; - flags |= EvalFlags.NoConvertSpecialForm; - } else if (isPossibleTypeDictFactoryCall(decls[0])) { - // Handle calls to TypedDict factory functions like type - // aliases to support recursive field type definitions. - typeAliasNameNode = node.d.leftExpr; - } - } - } - } - - if (typeAliasNameNode) { - typeAliasPlaceholder = synthesizeTypeAliasPlaceholder(typeAliasNameNode); - - writeTypeCache(node, { type: typeAliasPlaceholder }, /* flags */ undefined); - writeTypeCache(node.d.leftExpr, { type: typeAliasPlaceholder }, /* flags */ undefined); - - if (node.d.leftExpr.nodeType === ParseNodeType.TypeAnnotation) { - writeTypeCache(node.d.leftExpr.d.valueExpr, { type: typeAliasPlaceholder }, /* flags */ undefined); - } - } - - let declaredType = getDeclaredTypeForExpression(node.d.leftExpr, { method: 'set' }); - - if (declaredType) { - const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(node); - declaredType = makeTypeVarsBound(declaredType, liveTypeVarScopes); - } - - const srcTypeResult = getTypeOfExpression(node.d.rightExpr, flags, makeInferenceContext(declaredType)); - - rightHandType = srcTypeResult.type; - expectedTypeDiagAddendum = srcTypeResult.expectedTypeDiagAddendum; - if (srcTypeResult.isIncomplete) { - isIncomplete = true; - } - - // If this was a speculative type alias, it becomes a real type alias - // only if the evaluated type is an instantiable type. - if (isSpeculativeTypeAlias && !isLegalImplicitTypeAliasType(rightHandType)) { - typeAliasNameNode = undefined; - } - - if (typeAliasNameNode) { - assert(typeAliasPlaceholder !== undefined); - - // If this is a type alias, record its name based on the assignment target. - rightHandType = transformTypeForTypeAlias( - rightHandType, - typeAliasNameNode, - typeAliasPlaceholder, - /* isPep695TypeVarType */ false - ); - - if (isTypeAliasRecursive(typeAliasPlaceholder, rightHandType)) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeAliasIsRecursiveDirect().format({ - name: typeAliasNameNode.d.value, - }), - node.d.rightExpr - ); - - rightHandType = UnknownType.create(); - } - - // Set the resulting type to the boundType of the original type alias - // to support recursive type aliases. - typeAliasPlaceholder.shared.boundType = rightHandType; - - // Record the type parameters within the recursive type alias so it - // can be specialized. - typeAliasPlaceholder.shared.recursiveAlias!.typeParams = - rightHandType.props?.typeAliasInfo?.shared.typeParams; - } else { - // If the RHS is a constant boolean expression, assign it a literal type. - const constExprValue = evaluateStaticBoolExpression( - node.d.rightExpr, - fileInfo.executionEnvironment, - fileInfo.definedConstants - ); - - if (constExprValue !== undefined) { - const boolType = getBuiltInObject(node, 'bool'); - if (isClassInstance(boolType)) { - rightHandType = ClassType.cloneWithLiteral(boolType, constExprValue); - } - } - } - } - - // Viper 扩展:TypeSet 语义——带类型注解的赋值保持声明类型。 - // Viper 是 AOT 静态语言,注解位置的 `|` 表示类型组合(如 CConst | str - // 表示 const char*),而非 Python 的 TypeUnion(任选其一)。变量类型 - // 在声明时确定,不应被右侧推断类型收窄(如 `a: CConst | str = "Hello"` - // 应保持 `CConst | str`,而非 `Literal['Hello']`)。 - // 必须在 assignTypeToExpression 之前覆盖 rightHandType,因为 - // assignTypeToExpression 会将类型写入 NameNode 缓存(hover 读取的就是这个缓存)。 - // 这与 getTypeOfName(第 5028 行)的保持声明类型逻辑对应。 - // - // 例外:t.CTypedef 注解的变量(如 `UINT32PTR: t.CTypedef = t.CUInt32T | t.CPtr`) - // 属于类型别名声明,其右侧表达式才是真实类型组合。此时不应覆盖为声明类型 - // (t.CTypedef 本质是 TypeAlias 类实例,会被解析为基础类型 int),而应保持 - // 右侧推断出的类型组合(t.CUInt32T | t.CPtr)。 - if (IsViperDialect(fileInfo.executionEnvironment.dialect) && node.d.leftExpr.nodeType === ParseNodeType.TypeAnnotation) { - const typeAnnotNode = node.d.leftExpr as TypeAnnotationNode; - const annotationNode = typeAnnotNode.d.annotation; - const isCTypedefDecl = - annotationNode.nodeType === ParseNodeType.MemberAccess && - (annotationNode as MemberAccessNode).d.member.d.value === 'CTypedef'; - - if (!isCTypedefDecl) { - const viperDeclaredType = getDeclaredTypeForExpression(node.d.leftExpr, { method: 'set' }); - if (viperDeclaredType) { - rightHandType = viperDeclaredType; - } - } - } - - assignTypeToExpression( - node.d.leftExpr, - { type: rightHandType, isIncomplete }, - node.d.rightExpr, - /* ignoreEmptyContainers */ true, - /* allowAssignmentToFinalVar */ true, - expectedTypeDiagAddendum - ); - - writeTypeCache(node, { type: rightHandType, isIncomplete }, EvalFlags.None); - } - - // Synthesize a TypeVar that acts as a placeholder for a type alias. This allows - // the type alias definition to refer to itself. - function synthesizeTypeAliasPlaceholder(nameNode: NameNode, isTypeAliasType: boolean = false): TypeVarType { - const placeholder = TypeVarType.createInstantiable(`__type_alias_${nameNode.d.value}`); - placeholder.shared.isSynthesized = true; - const typeVarScopeId = ParseTreeUtils.getScopeIdForNode(nameNode); - const fileInfo = AnalyzerNodeInfo.getFileInfo(nameNode); - - placeholder.shared.recursiveAlias = { - name: nameNode.d.value, - fullName: ParseTreeUtils.getClassFullName(nameNode, fileInfo.moduleName, nameNode.d.value), - moduleName: fileInfo.moduleName, - fileUri: fileInfo.fileUri, - typeVarScopeId, - isTypeAliasType, - typeParams: undefined, - computedVariance: undefined, - }; - placeholder.priv.scopeId = typeVarScopeId; - - return placeholder; - } - - // Evaluates the type of a type alias (i.e. "type") statement. This code - // path does not handle traditional type aliases, which are treated as - // variables since they use normal variable assignment syntax. - function getTypeOfTypeAlias(node: TypeAliasNode): Type { - return getTypeOfTypeAliasCommon( - node, - node.d.name, - node.d.expr, - /* isPep695Syntax */ true, - node.d.typeParams?.d.params, - () => { - if (node.d.typeParams) { - return evaluateTypeParamList(node.d.typeParams); - } - return undefined; - } - ); - } - - // This function is common to the handling of "type" statements and explicit - // calls to the TypeAliasType constructor. - function getTypeOfTypeAliasCommon( - declNode: ParseNode, - nameNode: NameNode, - valueNode: ExpressionNode, - isPep695Syntax: boolean, - typeParamNodes: TypeParameterNode[] | undefined, - getTypeParamCallback: () => TypeVarType[] | undefined - ) { - const cachedType = readTypeCache(nameNode, EvalFlags.None); - if (cachedType) { - return cachedType; - } - - // Synthesize a type variable that represents the type alias while we're - // evaluating it. This allows us to handle recursive definitions. - const typeAliasTypeVar = synthesizeTypeAliasPlaceholder(nameNode, /* isTypeAliasType */ true); - - // Write the type to the type cache to support recursive type alias definitions. - writeTypeCache(nameNode, { type: typeAliasTypeVar }, /* flags */ undefined); - - // Set a partial type to handle recursive (self-referential) type aliases. - const scope = ScopeUtils.getScopeForNode(declNode); - const typeAliasSymbol = scope?.lookUpSymbolRecursive(nameNode.d.value); - const typeAliasDecl = AnalyzerNodeInfo.getDeclaration(declNode); - if (typeAliasDecl && typeAliasSymbol) { - setSymbolResolutionPartialType(typeAliasSymbol.symbol, typeAliasDecl, typeAliasTypeVar); - } - - const typeParams = getTypeParamCallback(); - if (typeAliasTypeVar.shared.recursiveAlias) { - typeAliasTypeVar.shared.recursiveAlias.typeParams = typeParams ?? []; - } - - let aliasTypeResult: TypeResult; - if (isPep695Syntax) { - aliasTypeResult = getTypeOfExpressionExpectingType(valueNode, { - forwardRefs: true, - typeExpression: true, - }); - } else { - const flags = - EvalFlags.InstantiableType | - EvalFlags.TypeExpression | - EvalFlags.StrLiteralAsType | - EvalFlags.NoParamSpec | - EvalFlags.NoTypeVarTuple | - EvalFlags.NoClassVar; - aliasTypeResult = getTypeOfExpression(valueNode, flags); - } - - let isIncomplete = false; - let aliasType = aliasTypeResult.type; - if (aliasTypeResult.isIncomplete) { - isIncomplete = true; - } - - aliasType = transformTypeForTypeAlias( - aliasType, - nameNode, - typeAliasTypeVar, - /* isPep695TypeVarType */ true, - typeParamNodes - ); - - // See if the type alias relies on itself in a way that cannot be resolved. - if (isTypeAliasRecursive(typeAliasTypeVar, aliasType)) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeAliasIsRecursiveDirect().format({ - name: nameNode.d.value, - }), - valueNode - ); - - aliasType = UnknownType.create(); - } - - // Set the resulting type to the boundType of the original type alias - // to support recursive type aliases. - typeAliasTypeVar.shared.boundType = aliasType; - - writeTypeCache(nameNode, { type: aliasType, isIncomplete }, EvalFlags.None); - - return aliasType; - } - - function evaluateTypesForAugmentedAssignment(node: AugmentedAssignmentNode): void { - if (isTypeCached(node)) { - return; - } - - const destTypeResult = getTypeOfAugmentedAssignment(evaluatorInterface, node, /* inferenceContext */ undefined); - - writeTypeCache(node, destTypeResult, EvalFlags.None); - } - - function getPseudoGenericTypeVarName(paramName: string) { - return `__type_of_${paramName}`; - } - - // Creates a new class type that is a subclass of two other specified classes. - function createSubclass(errorNode: ExpressionNode, type1: ClassType, type2: ClassType): ClassType { - assert(isInstantiableClass(type1) && isInstantiableClass(type2)); - - // If both classes are class objects (type[A] and type[B]), create a new - // class object (type[A & B]) rather than "type[A] & type[B]". - let createClassObject = false; - if (TypeBase.getInstantiableDepth(type1) > 0 && TypeBase.getInstantiableDepth(type2) > 0) { - type1 = ClassType.cloneAsInstance(type1); - type2 = ClassType.cloneAsInstance(type2); - createClassObject = true; - } - - const className = ``; - const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode); - - // The effective metaclass of the intersection is the narrower of the two metaclasses. - let effectiveMetaclass = type1.shared.effectiveMetaclass; - if (type2.shared.effectiveMetaclass) { - if (!effectiveMetaclass || assignType(effectiveMetaclass, type2.shared.effectiveMetaclass)) { - effectiveMetaclass = type2.shared.effectiveMetaclass; - } - } - - let newClassType = ClassType.createInstantiable( - className, - ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className), - fileInfo.moduleName, - fileInfo.fileUri, - ClassTypeFlags.None, - ParseTreeUtils.getTypeSourceId(errorNode), - /* declaredMetaclass */ undefined, - effectiveMetaclass, - type1.shared.docString - ); - - newClassType.shared.baseClasses = [type1, type2]; - computeMroLinearization(newClassType); - - newClassType = addConditionToType(newClassType, type1.props?.condition); - newClassType = addConditionToType(newClassType, type2.props?.condition); - - if (createClassObject) { - newClassType = ClassType.cloneAsInstantiable(newClassType); - } - - return newClassType; - } - - function getTypeOfClass(node: ClassNode): ClassTypeResult | undefined { - initializePrefetchedTypes(node); - - // Is this type already cached? - const cachedClassType = readTypeCache(node.d.name, EvalFlags.None); - - if (cachedClassType) { - if (!isInstantiableClass(cachedClassType)) { - // This can happen in rare circumstances where the class declaration - // is located in an unreachable code block. - return undefined; - } - return { - classType: cachedClassType, - decoratedType: readTypeCache(node, EvalFlags.None) || UnknownType.create(), - }; - } - - // The type wasn't cached, so we need to create a new one. - const scope = ScopeUtils.getScopeForNode(node); - - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - let classFlags = ClassTypeFlags.None; - if ( - scope?.type === ScopeType.Builtin || - fileInfo.isTypingStubFile || - fileInfo.isTypingExtensionsStubFile || - fileInfo.isBuiltInStubFile || - fileInfo.isTypeshedStubFile - ) { - classFlags |= ClassTypeFlags.BuiltIn; - - if (fileInfo.isTypingExtensionsStubFile) { - classFlags |= ClassTypeFlags.TypingExtensionClass; - } - - if (node.d.name.d.value === 'property') { - classFlags |= ClassTypeFlags.PropertyClass; - } - - if (node.d.name.d.value === 'tuple') { - classFlags |= ClassTypeFlags.TupleClass; - } - } - - if (fileInfo.isStubFile) { - classFlags |= ClassTypeFlags.DefinedInStub; - } - - const classType = ClassType.createInstantiable( - node.d.name.d.value, - ParseTreeUtils.getClassFullName(node, fileInfo.moduleName, node.d.name.d.value), - fileInfo.moduleName, - fileInfo.fileUri, - classFlags, - ParseTreeUtils.getTypeSourceId(node), - /* declaredMetaclass */ undefined, - /* effectiveMetaclass */ undefined, - ParseTreeUtils.getDocString(node.d.suite.d.statements) - ); - - classType.shared.typeVarScopeId = ParseTreeUtils.getScopeIdForNode(node); - - // Is this a special type that supports type promotions according to PEP 484? - if (typePromotions.has(classType.shared.fullName)) { - classType.priv.includePromotions = true; - } - - // Some classes refer to themselves within type arguments used within - // base classes. We'll register the partially-constructed class type - // to allow these to be resolved. - const classSymbol = scope?.lookUpSymbol(node.d.name.d.value); - let classDecl: ClassDeclaration | undefined; - const decl = AnalyzerNodeInfo.getDeclaration(node); - if (decl) { - classDecl = decl as ClassDeclaration; - } - if (classDecl && classSymbol) { - setSymbolResolutionPartialType(classSymbol, classDecl, classType); - } - classType.shared.flags |= ClassTypeFlags.PartiallyEvaluated; - classType.shared.declaration = classDecl; - - return invalidateTypeCacheIfCanceled(() => { - writeTypeCache(node, { type: classType }, /* flags */ undefined); - writeTypeCache(node.d.name, { type: classType }, /* flags */ undefined); - - // Keep a list of unique type parameters that are used in the - // base class arguments. - let typeParams: TypeVarType[] = []; - - if (node.d.typeParams) { - typeParams = evaluateTypeParamList(node.d.typeParams).map((t) => TypeVarType.cloneAsInstance(t)); - } - - // If the class derives from "Generic" directly, it will provide - // all of the type parameters in the specified order. - let genericTypeParams: TypeVarType[] | undefined; - let protocolTypeParams: TypeVarType[] | undefined; - let isNamedTupleSubclass = false; - - const initSubclassArgs: Arg[] = []; - let metaclassNode: ExpressionNode | undefined; - let exprFlags = - EvalFlags.InstantiableType | - EvalFlags.AllowGeneric | - EvalFlags.NoNakedGeneric | - EvalFlags.NoTypeVarWithScopeId | - EvalFlags.TypeVarGetsCurScope | - EvalFlags.EnforceVarianceConsistency; - if (fileInfo.isStubFile) { - exprFlags |= EvalFlags.ForwardRefs; - } - let sawClosedOrExtraItems = false; - - node.d.arguments.forEach((arg) => { - // Ignore unpacked arguments. - if (arg.d.argCategory === ArgCategory.UnpackedDictionary) { - // Evaluate the expression's type so symbols are marked accessed - // and errors are reported. - getTypeOfExpression(arg.d.valueExpr); - return; - } - - if (!arg.d.name) { - let argType: Type; - - if (arg.d.argCategory === ArgCategory.UnpackedList) { - getTypeOfExpression(arg.d.valueExpr); - argType = UnknownType.create(); - } else { - argType = getTypeOfExpression(arg.d.valueExpr, exprFlags).type; - - if ( - isTypeVar(argType) && - argType.props?.specialForm && - TypeBase.isInstance(argType.props.specialForm) - ) { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.baseClassInvalid(), arg); - argType = UnknownType.create(); - } - - argType = makeTopLevelTypeVarsConcrete(argType); - } - - // In some stub files, classes are conditionally defined (e.g. based - // on platform type). We'll assume that the conditional logic is correct - // and strip off the "unbound" union. - if (isUnion(argType)) { - argType = removeUnbound(argType); - } - - // Any is allowed as a base class. Remove its "special form" flag to avoid - // false positive errors. - if (isAny(argType) && argType.props?.specialForm) { - argType = AnyType.create(); - } - - argType = stripTypeFormRecursive(argType); - - if (!isAnyOrUnknown(argType) && !isUnbound(argType)) { - // If the specified base class is type(T), use the metaclass - // of T if it's known. - if ( - isClass(argType) && - TypeBase.getInstantiableDepth(argType) > 0 && - argType.shared.effectiveMetaclass && - isClass(argType.shared.effectiveMetaclass) - ) { - argType = argType.shared.effectiveMetaclass; - } - - if (isMetaclassInstance(argType)) { - assert(isClassInstance(argType)); - argType = - argType.priv.typeArgs && argType.priv.typeArgs.length > 0 - ? argType.priv.typeArgs[0] - : UnknownType.create(); - } else if (!isInstantiableClass(argType)) { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.baseClassInvalid(), arg); - argType = UnknownType.create(); - } else { - if ( - ClassType.isPartiallyEvaluated(argType) || - argType.shared.mro.some((t) => isClass(t) && ClassType.isPartiallyEvaluated(t)) - ) { - // If the base class is partially evaluated, install a callback - // so we can fix up this class (e.g. compute the MRO) when the - // dependent class is completed. - registerDeferredClassCompletion(node, argType); - } - - if (ClassType.isBuiltIn(argType, 'Protocol')) { - if ( - !fileInfo.isStubFile && - !ClassType.isTypingExtensionClass(argType) && - PythonVersion.isLessThan( - fileInfo.executionEnvironment.pythonVersion, - pythonVersion3_7 - ) - ) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.protocolIllegal(), - arg.d.valueExpr - ); - } - classType.shared.flags |= ClassTypeFlags.ProtocolClass; - } - - if (ClassType.isBuiltIn(argType, 'property')) { - classType.shared.flags |= ClassTypeFlags.PropertyClass; - } - - // If the class directly derives from NamedTuple (in Python 3.6 or - // newer), it's considered a (read-only) dataclass. - if ( - PythonVersion.isGreaterOrEqualTo( - fileInfo.executionEnvironment.pythonVersion, - pythonVersion3_6 - ) - ) { - if (ClassType.isBuiltIn(argType, 'NamedTuple')) { - isNamedTupleSubclass = true; - } - } - - // If the class directly derives from TypedDict or from a class that is - // a TypedDict, it is considered a TypedDict. - if (ClassType.isBuiltIn(argType, 'TypedDict') || ClassType.isTypedDictClass(argType)) { - classType.shared.flags |= ClassTypeFlags.TypedDictClass; - - // Propagate the "effectively closed" flag from base classes. - if (ClassType.isTypedDictEffectivelyClosed(argType)) { - classType.shared.flags |= ClassTypeFlags.TypedDictEffectivelyClosed; - } - } - - // Validate that the class isn't deriving from itself, creating a - // circular dependency. - if (derivesFromClassRecursive(argType, classType, /* ignoreUnknown */ true)) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.baseClassCircular(), - arg - ); - argType = UnknownType.create(); - } - - // If the class is attempting to derive from a TypeAliasType, - // generate an error. - if ( - argType.props?.specialForm && - ClassType.isBuiltIn(argType.props.specialForm, 'TypeAliasType') - ) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeAliasTypeBaseClass(), - arg - ); - argType = UnknownType.create(); - } - } - } - - if (isUnknown(argType)) { - addDiagnostic(DiagnosticRule.reportUntypedBaseClass, LocMessage.baseClassUnknown(), arg); - } - - // Check for a duplicate class. - if ( - classType.shared.baseClasses.some((prevBaseClass) => { - return ( - isInstantiableClass(prevBaseClass) && - isInstantiableClass(argType) && - ClassType.isSameGenericClass(argType, prevBaseClass) - ); - }) - ) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.duplicateBaseClass(), - arg.d.name || arg - ); - } - - classType.shared.baseClasses.push(argType); - if (isInstantiableClass(argType)) { - if (ClassType.isEnumClass(argType)) { - classType.shared.flags |= ClassTypeFlags.EnumClass; - } - - // Viper 扩展:CEnum 子类也标记为 EnumClass。 - // Viper 的 CEnum 不是 Python 的 enum.Enum,但语义上等价: - // 枚举成员访问(如 DRESULT.RES_PARERR)应返回 DRESULT 类型, - // 而不是成员的声明类型。这样可使枚举成员赋值给返回类型为 - // DRESULT 的变量/函数时不报类型错误。 - // 注意:不能用 ClassType.isBuiltIn 判断,因为 t.CEnum 来自 - // vipershed-fallback,没有 BuiltIn 标志。 - if ( - IsViperDialect(fileInfo.executionEnvironment.dialect) && - isCEnumType(argType) - ) { - classType.shared.flags |= ClassTypeFlags.EnumClass; - } - - // Determine if the class is abstract. Protocol classes support abstract methods - // because they are constructed by the _ProtocolMeta metaclass, which derives - // from ABCMeta. - if (ClassType.supportsAbstractMethods(argType) || ClassType.isProtocolClass(argType)) { - classType.shared.flags |= ClassTypeFlags.SupportsAbstractMethods; - } - - if (ClassType.isPropertyClass(argType)) { - classType.shared.flags |= ClassTypeFlags.PropertyClass; - } - - if (ClassType.isFinal(argType)) { - const className = printObjectTypeForClass(argType); - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.baseClassFinal().format({ type: className }), - arg.d.valueExpr - ); - } - } - - addTypeVarsToListIfUnique(typeParams, getTypeVarArgsRecursive(argType)); - if (isInstantiableClass(argType)) { - if (ClassType.isBuiltIn(argType, 'Generic')) { - // 'Generic' is implicitly added if type parameter syntax is used. - if (node.d.typeParams) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.genericBaseClassNotAllowed(), - arg.d.valueExpr - ); - } else { - if (!genericTypeParams) { - if (protocolTypeParams) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.duplicateGenericAndProtocolBase(), - arg.d.valueExpr - ); - } - genericTypeParams = buildTypeParamsFromTypeArgs(argType); - } - } - } else if ( - ClassType.isBuiltIn(argType, 'Protocol') && - argType.priv.typeArgs && - argType.priv.typeArgs.length > 0 - ) { - if (!protocolTypeParams) { - if (genericTypeParams) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.duplicateGenericAndProtocolBase(), - arg.d.valueExpr - ); - } - protocolTypeParams = buildTypeParamsFromTypeArgs(argType); - - if (node.d.typeParams && protocolTypeParams.length > 0) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.protocolBaseClassWithTypeArgs(), - arg.d.valueExpr - ); - protocolTypeParams = []; - } - } - } - } - } else if (ClassType.isTypedDictClass(classType)) { - if (arg.d.name.d.value === 'total' || arg.d.name.d.value === 'closed') { - // The "total" and "readonly" parameters apply only for TypedDict classes. - // PEP 589 specifies that the parameter must be either True or False. - const constArgValue = evaluateStaticBoolExpression( - arg.d.valueExpr, - fileInfo.executionEnvironment, - fileInfo.definedConstants - ); - - if (constArgValue === undefined) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typedDictBoolParam().format({ name: arg.d.name.d.value }), - arg.d.valueExpr - ); - } else if (arg.d.name.d.value === 'total' && !constArgValue) { - classType.shared.flags |= ClassTypeFlags.CanOmitDictValues; - } else if (arg.d.name.d.value === 'closed') { - if (constArgValue) { - classType.shared.flags |= - ClassTypeFlags.TypedDictMarkedClosed | ClassTypeFlags.TypedDictEffectivelyClosed; - - if (classType.shared.typedDictExtraItemsExpr) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typedDictExtraItemsClosed(), - classType.shared.typedDictExtraItemsExpr - ); - } - } else { - // PEP 728: A class that subclasses from a non-open TypedDict - // cannot specify closed=False. - const nonOpenBase = classType.shared.baseClasses.find( - (base) => - isInstantiableClass(base) && - ClassType.isTypedDictClass(base) && - ClassType.isTypedDictEffectivelyClosed(base) - ); - if (nonOpenBase) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typedDictClosedFalseNonOpenBase().format({ - name: (nonOpenBase as ClassType).shared.name, - }), - arg.d.valueExpr - ); - } - } - - if (sawClosedOrExtraItems) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typedDictExtraItemsClosed(), - arg.d.valueExpr - ); - } - - sawClosedOrExtraItems = true; - } - } else if (arg.d.name.d.value === 'extra_items') { - // Record a reference to the expression but don't evaluate it yet. - // It may refer to the class itself. - classType.shared.typedDictExtraItemsExpr = arg.d.valueExpr; - classType.shared.flags |= ClassTypeFlags.TypedDictEffectivelyClosed; - - if (ClassType.isTypedDictMarkedClosed(classType)) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typedDictExtraItemsClosed(), - classType.shared.typedDictExtraItemsExpr - ); - } - - if (sawClosedOrExtraItems) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typedDictExtraItemsClosed(), - arg.d.valueExpr - ); - } - - sawClosedOrExtraItems = true; - } else { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typedDictInitsubclassParameter().format({ name: arg.d.name.d.value }), - arg - ); - } - } else if (arg.d.name.d.value === 'metaclass') { - if (metaclassNode) { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.metaclassDuplicate(), arg); - } else { - metaclassNode = arg.d.valueExpr; - } - } else { - // Collect arguments that will be passed to the `__init_subclass__` - // method described in PEP 487. - initSubclassArgs.push({ - argCategory: ArgCategory.Simple, - node: arg, - name: arg.d.name, - valueExpression: arg.d.valueExpr, - }); - } - }); - - // Check for NamedTuple multiple inheritance. - if (classType.shared.baseClasses.length > 1) { - let derivesFromNamedTuple = false; - let foundIllegalBaseClass = false; - - classType.shared.baseClasses.forEach((baseClass) => { - if (isInstantiableClass(baseClass)) { - if (ClassType.isBuiltIn(baseClass, 'NamedTuple')) { - derivesFromNamedTuple = true; - } else if (!ClassType.isBuiltIn(baseClass, 'Generic')) { - foundIllegalBaseClass = true; - } - } - }); - - if (derivesFromNamedTuple && foundIllegalBaseClass) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.namedTupleMultipleInheritance(), - node.d.name - ); - } - } - - // Make sure we don't have 'object' derive from itself. Infinite - // recursion will result. - if ( - !ClassType.isBuiltIn(classType, 'object') && - classType.shared.baseClasses.filter((baseClass) => isClass(baseClass)).length === 0 - ) { - // If there are no other (known) base classes, the class implicitly derives from object. - classType.shared.baseClasses.push(getBuiltInType(node, 'object')); - } - - // If genericTypeParams or protocolTypeParams are provided, - // make sure that typeParams is a proper subset. - genericTypeParams = genericTypeParams ?? protocolTypeParams; - if (genericTypeParams && !node.d.typeParams) { - verifyGenericTypeParams(node.d.name, typeParams, genericTypeParams); - } - classType.shared.typeParams = genericTypeParams ?? typeParams; - - // Determine if one or more type parameters is autovariance. - if ( - classType.shared.typeParams.some( - (param) => - param.shared.declaredVariance === Variance.Auto && param.priv.computedVariance === undefined - ) - ) { - classType.shared.requiresVarianceInference = true; - } - - // Make sure there's at most one TypeVarTuple. - const variadics = typeParams.filter((param) => isTypeVarTuple(param)); - if (variadics.length > 1) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.variadicTypeParamTooManyClass().format({ - names: variadics.map((v) => `"${v.shared.name}"`).join(', '), - }), - node.d.name, - TextRange.combine(node.d.arguments) || node.d.name - ); - } else if (variadics.length > 0) { - // Make sure a TypeVar with a default doesn't come after a TypeVarTuple. - const firstVariadicIndex = typeParams.findIndex((param) => isTypeVarTuple(param)); - const typeVarWithDefaultIndex = typeParams.findIndex( - (param, index) => - index > firstVariadicIndex && !isParamSpec(param) && param.shared.isDefaultExplicit - ); - - if (typeVarWithDefaultIndex >= 0) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarWithDefaultFollowsVariadic().format({ - typeVarName: typeParams[typeVarWithDefaultIndex].shared.name, - variadicName: typeParams[firstVariadicIndex].shared.name, - }), - node.d.typeParams ? node.d.typeParams.d.params[typeVarWithDefaultIndex].d.name : node.d.name - ); - } - } - - // Validate the default types for all type parameters. - classType.shared.typeParams.forEach((typeParam, index) => { - let bestErrorNode: ExpressionNode = node.d.name; - if (node.d.typeParams && index < node.d.typeParams.d.params.length) { - const typeParamNode = node.d.typeParams.d.params[index]; - bestErrorNode = typeParamNode.d.defaultExpr ?? typeParamNode.d.name; - } - validateTypeParamDefault( - bestErrorNode, - typeParam, - classType.shared.typeParams.slice(0, index), - classType.shared.typeVarScopeId! - ); - }); - - if (!computeMroLinearization(classType)) { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.methodOrdering(), node.d.name); - } - - // The scope for this class becomes the "fields" for the corresponding type. - const innerScope = ScopeUtils.getScopeForNode(node.d.suite); - classType.shared.fields = innerScope?.symbolTable - ? new Map(innerScope.symbolTable) - : new Map(); - - // Determine whether the class should inherit __hash__. If a class defines - // __eq__ but doesn't define __hash__ then __hash__ is set to None. - if (classType.shared.fields.has('__eq__') && !classType.shared.fields.has('__hash__')) { - classType.shared.fields.set( - '__hash__', - Symbol.createWithType( - SymbolFlags.ClassMember | - SymbolFlags.ClassVar | - SymbolFlags.IgnoredForProtocolMatch | - SymbolFlags.IgnoredForOverrideChecks, - getNoneType() - ) - ); - } - - // Determine whether the class's instance variables are constrained - // to those defined by __slots__. We need to do this prior to dataclass - // processing because dataclasses can implicitly add to the slots - // list. - const slotsNames = innerScope?.getSlotsNames(); - if (slotsNames) { - classType.shared.localSlotsNames = slotsNames; - } - - // Determine if the class should be a "pseudo-generic" class, characterized - // by having an __init__ method with parameters that lack type annotations. - // For such classes, we'll treat them as generic, with the type arguments provided - // by the callers of the constructor. - if (!fileInfo.isStubFile && classType.shared.typeParams.length === 0) { - const initMethod = classType.shared.fields.get('__init__'); - if (initMethod) { - const initDecls = initMethod.getTypedDeclarations(); - if (initDecls.length === 1 && initDecls[0].type === DeclarationType.Function) { - const initDeclNode = initDecls[0].node; - const initParams = initDeclNode.d.params; - - if ( - initParams.length > 1 && - !initParams.some( - (param, index) => !!ParseTreeUtils.getTypeAnnotationForParam(initDeclNode, index) - ) - ) { - const genericParams = initParams.filter( - (param, index) => - index > 0 && - param.d.name && - param.d.category === ParamCategory.Simple && - !param.d.defaultValue - ); - - if (genericParams.length > 0) { - classType.shared.flags |= ClassTypeFlags.PseudoGenericClass; - - // Create a type parameter for each simple, named parameter - // in the __init__ method. - classType.shared.typeParams = genericParams.map((param) => { - const typeVar = TypeVarType.createInstance( - getPseudoGenericTypeVarName(param.d.name!.d.value) - ); - typeVar.shared.isSynthesized = true; - typeVar.priv.scopeId = ParseTreeUtils.getScopeIdForNode(initDeclNode); - typeVar.shared.boundType = UnknownType.create(); - return TypeVarType.cloneForScopeId( - typeVar, - ParseTreeUtils.getScopeIdForNode(node), - node.d.name.d.value, - TypeVarScopeType.Class - ); - }); - } - } - } - } - } - - // Determine if the class has a custom __class_getitem__ method. This applies - // only to classes that have no type parameters, since those with type parameters - // are assumed to follow normal subscripting semantics for generic classes. - if (classType.shared.typeParams.length === 0 && !ClassType.isBuiltIn(classType, 'type')) { - if ( - classType.shared.baseClasses.some( - (baseClass) => isInstantiableClass(baseClass) && ClassType.hasCustomClassGetItem(baseClass) - ) || - classType.shared.fields.has('__class_getitem__') - ) { - classType.shared.flags |= ClassTypeFlags.HasCustomClassGetItem; - } - } - - // Determine the effective metaclass. - if (metaclassNode) { - let metaclassType = getTypeOfExpression(metaclassNode, exprFlags).type; - if (isInstantiableClass(metaclassType) || isUnknown(metaclassType)) { - if (requiresSpecialization(metaclassType, { ignorePseudoGeneric: true })) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.metaclassIsGeneric(), - metaclassNode - ); - } - - // If the specified metaclass is type(T), use the metaclass - // of T if it's known. - if ( - TypeBase.getInstantiableDepth(metaclassType) > 0 && - isClass(metaclassType) && - metaclassType.shared.effectiveMetaclass && - isClass(metaclassType.shared.effectiveMetaclass) - ) { - metaclassType = metaclassType.shared.effectiveMetaclass; - } - - classType.shared.declaredMetaclass = metaclassType; - if (isInstantiableClass(metaclassType)) { - if (isEnumMetaclass(metaclassType)) { - classType.shared.flags |= ClassTypeFlags.EnumClass; - } - - if (derivesFromStdlibClass(metaclassType, 'ABCMeta')) { - classType.shared.flags |= ClassTypeFlags.SupportsAbstractMethods; - } - } - } - } - - const effectiveMetaclass = computeEffectiveMetaclass(classType, node.d.name); - - // Clear the "partially constructed" flag. - classType.shared.flags &= ~ClassTypeFlags.PartiallyEvaluated; - - // Now determine the decorated type of the class. - let decoratedType: Type = classType; - let foundUnknown = false; - - for (let i = node.d.decorators.length - 1; i >= 0; i--) { - const decorator = node.d.decorators[i]; - - const newDecoratedType = useSignatureTracker(node.parent ?? node, () => - applyClassDecorator(evaluatorInterface, decoratedType, classType, decorator) - ); - const unknownOrAny = containsAnyOrUnknown(newDecoratedType, /* recurse */ false); - - if (unknownOrAny && isUnknown(unknownOrAny)) { - // Report this error only on the first unknown type. - if (!foundUnknown) { - addDiagnostic( - DiagnosticRule.reportUntypedClassDecorator, - LocMessage.classDecoratorTypeUnknown(), - node.d.decorators[i].d.expr - ); - - foundUnknown = true; - } - } else { - // Apply the decorator only if the type is known. - decoratedType = newDecoratedType; - } - } - - // Determine whether this class derives from (or has a metaclass) that imbues - // it with dataclass-like behaviors. If so, we'll apply those here. - let dataClassBehaviors: DataClassBehaviors | undefined; - if (isInstantiableClass(effectiveMetaclass) && effectiveMetaclass.shared.classDataClassTransform) { - dataClassBehaviors = effectiveMetaclass.shared.classDataClassTransform; - } else { - const baseClassDataTransform = classType.shared.mro.find((mroClass) => { - return ( - isClass(mroClass) && - mroClass.shared.classDataClassTransform !== undefined && - !ClassType.isSameGenericClass(mroClass, classType) - ); - }); - - if (baseClassDataTransform) { - dataClassBehaviors = (baseClassDataTransform as ClassType).shared.classDataClassTransform; - } - } - - if (dataClassBehaviors) { - applyDataClassClassBehaviorOverrides( - evaluatorInterface, - node.d.name, - classType, - initSubclassArgs, - dataClassBehaviors - ); - } - - // Run any deferred class completions that depend on this class. - runDeferredClassCompletions(classType); - - // If there are any outstanding deferred class completions registered that - // were not removed by the call to runDeferredClassCompletions, assume that - // the current class may depend on them and register for deferred completion. - registerDeferredClassCompletion(node, /* dependsUpon */ undefined); - - // Synthesize TypedDict methods. - if (ClassType.isTypedDictClass(classType)) { - // TypedDict classes must derive only from other TypedDict classes. - let foundInvalidBaseClass = false; - const diag = new DiagnosticAddendum(); - - classType.shared.baseClasses.forEach((baseClass) => { - if ( - isClass(baseClass) && - !ClassType.isTypedDictClass(baseClass) && - !ClassType.isBuiltIn(baseClass, ['_TypedDict', 'TypedDictFallback', 'Generic']) - ) { - foundInvalidBaseClass = true; - diag.addMessage(LocAddendum.typedDictBaseClass().format({ type: baseClass.shared.name })); - } - }); - - if (foundInvalidBaseClass) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typedDictBaseClass() + diag.getString(), - node.d.name - ); - } - - synthesizeTypedDictClassMethods(evaluatorInterface, node, classType); - } - - // Synthesize dataclass methods. - if (ClassType.isDataClass(classType) || isNamedTupleSubclass) { - const skipSynthesizedInit = ClassType.isDataClassSkipGenerateInit(classType); - let hasExistingInitMethod = skipSynthesizedInit; - - // See if there's already a non-synthesized __init__ method. - // We shouldn't override it. - if (!skipSynthesizedInit) { - const initSymbol = classType.shared.fields.get('__init__'); - if (initSymbol && initSymbol.isClassMember()) { - hasExistingInitMethod = true; - } - } - - let skipSynthesizeHash = false; - const hashSymbol = classType.shared.fields.get('__hash__'); - - // If there is a hash symbol defined in the class (i.e. one that we didn't - // synthesize above), then we shouldn't synthesize a new one for the dataclass. - if (hashSymbol && hashSymbol.isClassMember() && !hashSymbol.getSynthesizedType()) { - skipSynthesizeHash = true; - } - - const synthesizeMethods = () => - synthesizeDataClassMethods( - evaluatorInterface, - node, - classType, - isNamedTupleSubclass, - skipSynthesizedInit, - hasExistingInitMethod, - skipSynthesizeHash - ); - - // If this is a NamedTuple subclass, immediately synthesize dataclass methods - // because we also need to update the MRO classes in this case. For regular - // dataclasses, we'll defer the method synthesis to avoid circular dependencies. - if (isNamedTupleSubclass) { - synthesizeMethods(); - } else { - classType.shared.synthesizeMethodsDeferred = () => { - delete classType.shared.synthesizeMethodsDeferred; - synthesizeMethods(); - }; - } - } - - // Build a complete list of all slots names defined by the class hierarchy. - // This needs to be done after dataclass processing. - classType.shared.calculateInheritedSlotsNamesDeferred = () => { - delete classType.shared.calculateInheritedSlotsNamesDeferred; - - if (classType.shared.localSlotsNames) { - let isLimitedToSlots = true; - const extendedSlotsNames = Array.from(classType.shared.localSlotsNames); - - classType.shared.baseClasses.forEach((baseClass) => { - if (isInstantiableClass(baseClass)) { - if ( - !ClassType.isBuiltIn(baseClass, 'object') && - !ClassType.isBuiltIn(baseClass, 'type') && - !ClassType.isBuiltIn(baseClass, 'Generic') - ) { - const inheritedSlotsNames = ClassType.getInheritedSlotsNames(baseClass); - if (inheritedSlotsNames) { - appendArray(extendedSlotsNames, inheritedSlotsNames); - } else { - isLimitedToSlots = false; - } - } - } else { - isLimitedToSlots = false; - } - }); - - if (isLimitedToSlots) { - classType.shared.inheritedSlotsNamesCached = extendedSlotsNames; - } - } - }; - - // If Any is defined using a class statement, treat it as a special form. - if (node.d.name.d.value === 'Any' && fileInfo.isTypingStubFile) { - decoratedType = AnyType.createSpecialForm(); - } - - // Update the undecorated class type. - writeTypeCache(node.d.name, { type: classType }, EvalFlags.None); - - // Update the decorated class type. - writeTypeCache(node, { type: decoratedType }, EvalFlags.None); - - return { classType, decoratedType }; - }); - } - - function buildTypeParamsFromTypeArgs(classType: ClassType): TypeVarType[] { - const typeParams: TypeVarType[] = []; - const typeArgs = classType.priv.typeArgs ?? []; - - typeArgs.forEach((typeArg, index) => { - if (isTypeVar(typeArg)) { - typeParams.push(typeArg); - return; - } - - // Synthesize a dummy type parameter. - const typeVar = TypeVarType.createInstance(`__P${index}`); - typeVar.shared.isSynthesized = true; - typeParams.push(typeVar); - }); - - return typeParams; - } - - // Determines whether the type parameters has a default that refers to another - // type parameter. If so, validates that it is in the list of "live" type - // parameters and updates the scope of the type parameter referred to in the - // default type expression. - function validateTypeParamDefault( - errorNode: ExpressionNode, - typeParam: TypeVarType, - otherLiveTypeParams: TypeVarType[], - scopeId: TypeVarScopeId - ) { - if (!typeParam.shared.isDefaultExplicit && !typeParam.shared.isSynthesized && !TypeVarType.isSelf(typeParam)) { - const typeVarWithDefault = otherLiveTypeParams.find( - (param) => param.shared.isDefaultExplicit && param.priv.scopeId === scopeId - ); - - if (typeVarWithDefault) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarWithoutDefault().format({ - name: typeParam.shared.name, - other: typeVarWithDefault.shared.name, - }), - errorNode - ); - } - return; - } - - const invalidTypeVars = new Set(); - validateTypeVarDefault(typeParam, otherLiveTypeParams, invalidTypeVars); - - // If we found one or more unapplied type variable, report an error. - if (invalidTypeVars.size > 0) { - const diag = new DiagnosticAddendum(); - invalidTypeVars.forEach((name) => { - diag.addMessage(LocAddendum.typeVarDefaultOutOfScope().format({ name })); - }); - - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarDefaultInvalidTypeVar().format({ - name: typeParam.shared.name, - }) + diag.getString(), - errorNode - ); - } - } - - function inferVarianceForClass(classType: ClassType): void { - if (!classType.shared.requiresVarianceInference) { - return; - } - - // Presumptively mark the variance inference as complete. This - // prevents potential recursion. - classType.shared.requiresVarianceInference = false; - - // Presumptively mark the computed variance to "unknown". We'll - // replace this below once the variance has been inferred. - classType.shared.typeParams.forEach((param) => { - if (param.shared.declaredVariance === Variance.Auto) { - param.priv.computedVariance = Variance.Unknown; - } - }); - - const dummyTypeObject = ClassType.createInstantiable( - '__varianceDummy', - '', - '', - Uri.empty(), - 0, - 0, - undefined, - undefined - ); - - classType.shared.typeParams.forEach((param, paramIndex) => { - // Skip TypeVarTuples and ParamSpecs. - if (isTypeVarTuple(param) || isParamSpec(param)) { - return; - } - - // Skip type variables without auto-variance. - if (param.shared.declaredVariance !== Variance.Auto) { - return; - } - - // Replace all type arguments with a dummy type except for the - // TypeVar of interest, which is replaced with an object instance. - const srcTypeArgs = classType.shared.typeParams.map((p, i) => { - if (isTypeVarTuple(p)) { - return p; - } - return i === paramIndex ? getObjectType() : dummyTypeObject; - }); - - // Replace all type arguments with a dummy type except for the - // TypeVar of interest, which is replaced with itself. - const destTypeArgs = classType.shared.typeParams.map((p, i) => { - return i === paramIndex || isTypeVarTuple(p) ? p : dummyTypeObject; - }); - - const srcType = ClassType.specialize(classType, srcTypeArgs); - const destType = ClassType.specialize(classType, destTypeArgs); - - const isDestSubtypeOfSrc = assignClassToSelf( - srcType, - destType, - Variance.Covariant, - /* ignoreBaseClassVariance */ false - ); - - let inferredVariance: Variance; - if (isDestSubtypeOfSrc) { - inferredVariance = Variance.Covariant; - } else { - const isSrcSubtypeOfDest = assignClassToSelf( - destType, - srcType, - Variance.Contravariant, - /* ignoreBaseClassVariance */ false - ); - if (isSrcSubtypeOfDest) { - inferredVariance = Variance.Contravariant; - } else { - inferredVariance = Variance.Invariant; - } - } - - // We assume here that we don't need to clone the type var object - // because it was already cloned when it was associated with this - // class scope. - classType.shared.typeParams[paramIndex].priv.computedVariance = inferredVariance; - }); - } - - function evaluateTypeParamList(node: TypeParameterListNode): TypeVarType[] { - const paramTypes: TypeVarType[] = []; - const typeParamScope = AnalyzerNodeInfo.getScope(node); - - node.d.params.forEach((param) => { - const paramSymbol = typeParamScope?.symbolTable.get(param.d.name.d.value); - if (!paramSymbol) { - // This can happen if the code is unreachable. - return; - } - - const typeOfParam = getDeclaredTypeOfSymbol(paramSymbol, param.d.name)?.type; - if (!typeOfParam || !isTypeVar(typeOfParam)) { - return; - } - - writeTypeCache(param.d.name, { type: typeOfParam }, EvalFlags.None); - paramTypes.push(typeOfParam); - }); - - return paramTypes; - } - - function computeEffectiveMetaclass(classType: ClassType, errorNode: ParseNode) { - let effectiveMetaclass = classType.shared.declaredMetaclass; - let reportedMetaclassConflict = false; - - if (!effectiveMetaclass || isInstantiableClass(effectiveMetaclass)) { - for (const baseClass of classType.shared.baseClasses) { - if (isInstantiableClass(baseClass)) { - const baseClassMeta = baseClass.shared.effectiveMetaclass ?? prefetched?.typeClass; - if (baseClassMeta && isInstantiableClass(baseClassMeta)) { - // Make sure there is no metaclass conflict. - if (!effectiveMetaclass) { - effectiveMetaclass = baseClassMeta; - } else if ( - derivesFromClassRecursive(baseClassMeta, effectiveMetaclass, /* ignoreUnknown */ false) - ) { - effectiveMetaclass = baseClassMeta; - } else if ( - !derivesFromClassRecursive(effectiveMetaclass, baseClassMeta, /* ignoreUnknown */ false) - ) { - if (!reportedMetaclassConflict) { - const diag = new DiagnosticAddendum(); - - diag.addMessage( - LocAddendum.metaclassConflict().format({ - metaclass1: printType(convertToInstance(effectiveMetaclass)), - metaclass2: printType(convertToInstance(baseClassMeta)), - }) - ); - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.metaclassConflict() + diag.getString(), - errorNode - ); - - // Don't report more than once. - reportedMetaclassConflict = true; - } - } - } else { - effectiveMetaclass = baseClassMeta ? UnknownType.create() : undefined; - break; - } - } else { - // If one of the base classes is unknown, then the effective - // metaclass is also unknowable. - effectiveMetaclass = UnknownType.create(); - break; - } - } - } - - // If we haven't found an effective metaclass, assume "type", which - // is the metaclass for "object". - if (!effectiveMetaclass) { - const typeMetaclass = getBuiltInType(errorNode, 'type'); - effectiveMetaclass = - typeMetaclass && isInstantiableClass(typeMetaclass) ? typeMetaclass : UnknownType.create(); - } - - classType.shared.effectiveMetaclass = effectiveMetaclass; - - return effectiveMetaclass; - } - - // Verifies that the type variables provided outside of "Generic" - // or "Protocol" are also provided within the "Generic". For example: - // class Foo(Mapping[K, V], Generic[V]) - // is illegal because K is not included in Generic. - function verifyGenericTypeParams( - errorNode: ExpressionNode, - typeVars: TypeVarType[], - genericTypeVars: TypeVarType[] - ) { - const missingFromGeneric = typeVars.filter((typeVar) => { - return !genericTypeVars.some((genericTypeVar) => genericTypeVar.shared.name === typeVar.shared.name); - }); - - if (missingFromGeneric.length > 0) { - const diag = new DiagnosticAddendum(); - diag.addMessage( - LocAddendum.typeVarsMissing().format({ - names: missingFromGeneric.map((typeVar) => `"${typeVar.shared.name}"`).join(', '), - }) - ); - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarsNotInGenericOrProtocol() + diag.getString(), - errorNode - ); - } - } - - // Records the fact that the specified class requires "deferred completion" because - // one of its base classes has not yet been fully evaluated. If the caller passes - // undefined for "dependsUpon", then the class is added to all outstanding deferred - // completions. - function registerDeferredClassCompletion(classToComplete: ClassNode, dependsUpon: ClassType | undefined) { - if (dependsUpon) { - // See if there is an existing entry for this dependency. - const entry = deferredClassCompletions.find((e) => - ClassType.isSameGenericClass(e.dependsUpon, dependsUpon) - ); - if (entry) { - entry.classesToComplete.push(classToComplete); - } else { - deferredClassCompletions.push({ dependsUpon, classesToComplete: [classToComplete] }); - } - } else { - deferredClassCompletions.forEach((e) => { - e.classesToComplete.push(classToComplete); - }); - } - } - - // Runs any registered "deferred class completions" that depend on the specified - // class type. This allows us to complete any work that requires dependent classes - // to be completed. - function runDeferredClassCompletions(type: ClassType) { - deferredClassCompletions.forEach((e) => { - if (ClassType.isSameGenericClass(e.dependsUpon, type)) { - e.classesToComplete.forEach((classNode) => { - const classType = readTypeCache(classNode.d.name, EvalFlags.None); - if (classType) { - completeClassTypeDeferred(classType as ClassType, classNode.d.name); - } - }); - } - }); - - // Remove any completions that depend on this type. - deferredClassCompletions = deferredClassCompletions.filter( - (e) => !ClassType.isSameGenericClass(e.dependsUpon, type) - ); - } - - // Recomputes the MRO and effective metaclass for the class after dependent - // classes have been fully constructed. - function completeClassTypeDeferred(type: ClassType, errorNode: ParseNode) { - // Recompute the MRO linearization. - if (!computeMroLinearization(type)) { - addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.methodOrdering(), errorNode); - } - - // Recompute the effective metaclass. - computeEffectiveMetaclass(type, errorNode); - } - - function validateInitSubclassArgs(node: ClassNode, classType: ClassType) { - // Collect arguments that will be passed to the `__init_subclass__` - // method described in PEP 487 and validate it. - const argList: Arg[] = []; - - node.d.arguments.forEach((arg) => { - if (arg.d.name && arg.d.name.d.value !== 'metaclass') { - argList.push({ - argCategory: ArgCategory.Simple, - node: arg, - name: arg.d.name, - valueExpression: arg.d.valueExpr, - }); - } - }); - - let newMethodMember: ClassMember | undefined; - - // See if the class has a metaclass that overrides `__new__`. If so, we - // will validate the signature of the `__new__` method. - if (classType.shared.effectiveMetaclass && isClass(classType.shared.effectiveMetaclass)) { - // If the metaclass is 'type' or 'ABCMeta', we'll assume it will call through to - // __init_subclass__, so we'll skip the `__new__` method check. We need to exclude - // TypedDict classes here because _TypedDict uses ABCMeta as its metaclass, but its - // typeshed definition doesn't override __init_subclass__. - const metaclassCallsInitSubclass = - ClassType.isBuiltIn(classType.shared.effectiveMetaclass, ['ABCMeta', 'type']) && - !ClassType.isTypedDictClass(classType); - - if (!metaclassCallsInitSubclass) { - // See if the metaclass has a `__new__` method that accepts keyword parameters. - newMethodMember = lookUpClassMember( - classType.shared.effectiveMetaclass, - '__new__', - MemberAccessFlags.SkipTypeBaseClass - ); - } - } - - if (newMethodMember) { - const newMethodType = getTypeOfMember(newMethodMember); - if (isFunction(newMethodType)) { - const paramListDetails = getParamListDetails(newMethodType); - - if (paramListDetails.firstKeywordOnlyIndex !== undefined) { - // Build a map of the keyword-only parameters. - const paramMap = new Map(); - for (let i = paramListDetails.firstKeywordOnlyIndex; i < paramListDetails.params.length; i++) { - const paramInfo = paramListDetails.params[i]; - if ( - paramInfo.param.category === ParamCategory.Simple && - paramInfo.param.name && - paramInfo.kind !== ParamKind.Positional - ) { - paramMap.set(paramInfo.param.name, i); - } - } - - argList.forEach((arg) => { - if (arg.argCategory === ArgCategory.Simple && arg.name) { - const paramIndex = paramMap.get(arg.name.d.value) ?? paramListDetails.kwargsIndex; - - if (paramIndex !== undefined) { - const paramInfo = paramListDetails.params[paramIndex]; - const argParam: ValidateArgTypeParams = { - paramCategory: paramInfo.param.category, - paramType: paramInfo.type, - requiresTypeVarMatching: false, - argument: arg, - errorNode: arg.valueExpression ?? node.d.name, - }; - - validateArgType( - argParam, - new ConstraintTracker(), - { type: newMethodType }, - { skipUnknownArgCheck: true } - ); - paramMap.delete(arg.name.d.value); - } else { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.paramNameMissing().format({ name: arg.name.d.value }), - arg.name ?? node.d.name - ); - } - } - }); - - // See if we have any remaining unmatched parameters without - // default values. - const unassignedParams: string[] = []; - paramMap.forEach((index, paramName) => { - const paramInfo = paramListDetails.params[index]; - if (!paramInfo.defaultType) { - unassignedParams.push(paramName); - } - }); - - if (unassignedParams.length > 0) { - const missingParamNames = unassignedParams.map((p) => `"${p}"`).join(', '); - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - unassignedParams.length === 1 - ? LocMessage.argMissingForParam().format({ name: missingParamNames }) - : LocMessage.argMissingForParams().format({ names: missingParamNames }), - node.d.name - ); - } - } - } - } else { - // If there was no custom metaclass __new__ method, see if there is an __init_subclass__ - // method present somewhere in the class hierarchy. - const initSubclassMethodInfo = getTypeOfBoundMember( - node.d.name, - classType, - '__init_subclass__', - /* usage */ undefined, - /* diag */ undefined, - MemberAccessFlags.SkipClassMembers | - MemberAccessFlags.SkipOriginalClass | - MemberAccessFlags.SkipAttributeAccessOverride - ); - - if (initSubclassMethodInfo) { - const initSubclassMethodType = initSubclassMethodInfo.type; - - if (initSubclassMethodType && initSubclassMethodInfo.classType) { - const callResult = validateCallArgs( - node.d.name, - argList, - { type: initSubclassMethodType }, - /* constraints */ undefined, - /* skipUnknownArgCheck */ false, - makeInferenceContext(getNoneType()) - ); - - if (callResult.argumentErrors) { - const diag = addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.initSubclassCallFailed(), - node.d.name - ); - - const initSubclassFunction = isOverloaded(initSubclassMethodType) - ? OverloadedType.getOverloads(initSubclassMethodType)[0] - : initSubclassMethodType; - const initSubclassDecl = isFunction(initSubclassFunction) - ? initSubclassFunction.shared.declaration - : undefined; - - if (diag && initSubclassDecl) { - diag.addRelatedInfo( - LocAddendum.initSubclassLocation().format({ - name: printType(convertToInstance(initSubclassMethodInfo.classType)), - }), - initSubclassDecl.uri, - initSubclassDecl.range - ); - } - } - } - } - } - - // Evaluate all of the expressions so they are checked and marked referenced. - argList.forEach((arg) => { - if (arg.valueExpression) { - getTypeOfExpression(arg.valueExpression); - } - }); - } - - function getTypeOfFunction(node: FunctionNode): FunctionTypeResult | undefined { - initializePrefetchedTypes(node); - - // Is this predecorated function type cached? - let functionType = readTypeCache(node.d.name, EvalFlags.None); - - if (functionType) { - if (!isFunction(functionType)) { - // This can happen in certain rare circumstances where the - // function declaration falls within an unreachable code block. - return undefined; - } - - if (FunctionType.isPartiallyEvaluated(functionType)) { - return { functionType, decoratedType: functionType }; - } - } else { - functionType = getTypeOfFunctionPredecorated(node); - } - - // Is the decorated function type cached? - let decoratedType = readTypeCache(node, EvalFlags.None); - if (decoratedType) { - return { functionType, decoratedType }; - } - - // Populate the cache with a temporary value to handle recursion. - writeTypeCache(node, { type: functionType }, /* flags */ undefined); - - // If it's an async function, wrap the return type in an Awaitable or Generator. - // Set the "partially evaluated" flag around this logic to detect recursion. - functionType.shared.flags |= FunctionTypeFlags.PartiallyEvaluated; - const preDecoratedType = node.d.isAsync ? createAsyncFunction(node, functionType) : functionType; - - // Apply all of the decorators in reverse order. - decoratedType = preDecoratedType; - let foundUnknown = false; - for (let i = node.d.decorators.length - 1; i >= 0; i--) { - const decorator = node.d.decorators[i]; - - const newDecoratedType = useSignatureTracker(node.parent ?? node, () => { - assert(decoratedType !== undefined); - return applyFunctionDecorator(evaluatorInterface, decoratedType, functionType, decorator, node); - }); - - const unknownOrAny = containsAnyOrUnknown(newDecoratedType, /* recurse */ false); - - if (unknownOrAny && isUnknown(unknownOrAny)) { - // Report this error only on the first unknown type. - if (!foundUnknown) { - addDiagnostic( - DiagnosticRule.reportUntypedFunctionDecorator, - LocMessage.functionDecoratorTypeUnknown(), - node.d.decorators[i].d.expr - ); - - foundUnknown = true; - } - } else { - // Apply the decorator only if the type is known. - decoratedType = newDecoratedType; - } - } - - // See if there are any overloads provided by previous function declarations. - if (isFunction(decoratedType)) { - decoratedType.shared.deprecatedMessage = functionType.shared.deprecatedMessage; - - if (FunctionType.isOverloaded(decoratedType)) { - // Mark all the parameters as accessed. - node.d.params.forEach((param) => { - markParamAccessed(param); - }); - } - } - - decoratedType = addOverloadsToFunctionType(evaluatorInterface, node, decoratedType); - - writeTypeCache(node, { type: decoratedType }, EvalFlags.None); - - // Now that the decorator has been applied, we can clear the - // "partially evaluated" flag. - functionType.shared.flags &= ~FunctionTypeFlags.PartiallyEvaluated; - - return { functionType, decoratedType }; - } - - // Evaluates the type of a "def" statement without applying an async - // modifier or any decorators. - function getTypeOfFunctionPredecorated(node: FunctionNode): FunctionType { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - - // Is this type already cached? - const cachedFunctionType = readTypeCache(node.d.name, EvalFlags.None); - - if (cachedFunctionType && isFunction(cachedFunctionType)) { - return cachedFunctionType; - } - - let functionDecl: FunctionDeclaration | undefined; - const decl = AnalyzerNodeInfo.getDeclaration(node); - if (decl) { - functionDecl = decl as FunctionDeclaration; - } - - // There was no cached type, so create a new one. - // Retrieve the containing class node if the function is a method. - const containingClassNode = ParseTreeUtils.getEnclosingClass(node, /* stopAtFunction */ true); - let containingClassType: ClassType | undefined; - if (containingClassNode) { - containingClassType = getTypeOfClass(containingClassNode)?.classType; - } - - const functionInfo = getFunctionInfoFromDecorators(evaluatorInterface, node, !!containingClassNode); - let functionFlags = functionInfo.flags; - if (functionDecl?.isGenerator) { - functionFlags |= FunctionTypeFlags.Generator; - } - - if (fileInfo.isStubFile) { - functionFlags |= FunctionTypeFlags.StubDefinition; - } else if (fileInfo.isInPyTypedPackage) { - functionFlags |= FunctionTypeFlags.PyTypedDefinition; - } - - if (node.d.isAsync) { - functionFlags |= FunctionTypeFlags.Async; - } - - const functionType = FunctionType.createInstance( - node.d.name.d.value, - getFunctionFullName(node, fileInfo.moduleName, node.d.name.d.value), - fileInfo.moduleName, - functionFlags | FunctionTypeFlags.PartiallyEvaluated, - ParseTreeUtils.getDocString(node.d.suite.d.statements) - ); - - functionType.shared.typeVarScopeId = ParseTreeUtils.getScopeIdForNode(node); - functionType.shared.deprecatedMessage = functionInfo.deprecationMessage; - functionType.shared.methodClass = containingClassType; - - if (node.d.name.d.value === '__init__' || node.d.name.d.value === '__new__') { - if (containingClassNode) { - functionType.priv.constructorTypeVarScopeId = ParseTreeUtils.getScopeIdForNode(containingClassNode); - } - } - - if (fileInfo.isBuiltInStubFile || fileInfo.isTypingStubFile || fileInfo.isTypingExtensionsStubFile) { - // Mark the function as a built-in stdlib function. - functionType.shared.flags |= FunctionTypeFlags.BuiltIn; - } - - functionType.shared.declaration = functionDecl; - - // Allow recursion by caching and registering the partially-constructed function type. - const scope = ScopeUtils.getScopeForNode(node); - const functionSymbol = scope?.lookUpSymbolRecursive(node.d.name.d.value); - if (functionDecl && functionSymbol) { - setSymbolResolutionPartialType(functionSymbol.symbol, functionDecl, functionType); - } - - return invalidateTypeCacheIfCanceled(() => { - writeTypeCache(node.d.name, { type: functionType }, /* flags */ undefined); - - // Is this an "__init__" method within a pseudo-generic class? If so, - // we'll add generic types to the constructor's parameters. - const addGenericParamTypes = - containingClassType && - ClassType.isPseudoGenericClass(containingClassType) && - node.d.name.d.value === '__init__'; - - const paramTypes: Type[] = []; - - // Determine if the first parameter should be skipped for comment-based - // function annotations. - let firstCommentAnnotationIndex = 0; - if (containingClassType && (functionType.shared.flags & FunctionTypeFlags.StaticMethod) === 0) { - firstCommentAnnotationIndex = 1; - } - - // If there is a function annotation comment, validate that it has the correct - // number of parameter annotations. - if (node.d.funcAnnotationComment && !node.d.funcAnnotationComment.d.isEllipsis) { - const expected = node.d.params.length - firstCommentAnnotationIndex; - const received = node.d.funcAnnotationComment.d.paramAnnotations.length; - - // For methods with "self" or "cls" parameters, the annotation list - // can either include or exclude the annotation for the first parameter. - if (firstCommentAnnotationIndex > 0 && received === node.d.params.length) { - firstCommentAnnotationIndex = 0; - } else if (received !== expected) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.annotatedParamCountMismatch().format({ - expected, - received, - }), - node.d.funcAnnotationComment - ); - } - } - - // If this function uses PEP 695 syntax for type parameters, - // accumulate the list of type parameters upfront. - const typeParamsSeen: TypeVarType[] = []; - if (node.d.typeParams) { - functionType.shared.typeParams = evaluateTypeParamList(node.d.typeParams).map((typeParam) => - convertToInstance(typeParam) - ); - } else { - functionType.shared.typeParams = typeParamsSeen; - } - - let paramsArePositionOnly = true; - const isFirstParamClsOrSelf = - containingClassType && - (FunctionType.isClassMethod(functionType) || - FunctionType.isInstanceMethod(functionType) || - FunctionType.isConstructorMethod(functionType)); - const firstNonClsSelfParamIndex = isFirstParamClsOrSelf ? 1 : 0; - - node.d.params.forEach((param, index) => { - let paramType: Type | undefined; - let annotatedType: Type | undefined; - let paramTypeNode: ExpressionNode | undefined; - - if (param.d.name) { - if (index === 0 && isFirstParamClsOrSelf) { - // Mark "self/cls" as accessed. - markParamAccessed(param); - } else if (FunctionType.isAbstractMethod(functionType)) { - // Mark all parameters in abstract methods as accessed. - markParamAccessed(param); - } else if (containingClassType && ClassType.isProtocolClass(containingClassType)) { - // Mark all parameters in protocol methods as accessed. - markParamAccessed(param); - } - } - - if (param.d.annotation) { - paramTypeNode = param.d.annotation; - } else if (param.d.annotationComment) { - paramTypeNode = param.d.annotationComment; - } else if (node.d.funcAnnotationComment && !node.d.funcAnnotationComment.d.isEllipsis) { - const adjustedIndex = index - firstCommentAnnotationIndex; - if (adjustedIndex >= 0 && adjustedIndex < node.d.funcAnnotationComment.d.paramAnnotations.length) { - paramTypeNode = node.d.funcAnnotationComment.d.paramAnnotations[adjustedIndex]; - } - } - - if (paramTypeNode) { - if ((functionInfo.flags & FunctionTypeFlags.NoTypeCheck) !== 0) { - annotatedType = UnknownType.create(); - } else { - annotatedType = getTypeOfParamAnnotation(paramTypeNode, param.d.category); - } - - if (annotatedType) { - addTypeVarsToListIfUnique( - typeParamsSeen, - getTypeVarArgsRecursive(annotatedType), - functionType.shared.typeVarScopeId - ); - } - - if (isTypeVarTuple(annotatedType) && !annotatedType.priv.isUnpacked) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.unpackedTypeVarTupleExpected().format({ - name1: annotatedType.shared.name, - name2: annotatedType.shared.name, - }), - paramTypeNode - ); - annotatedType = UnknownType.create(); - } - } - - if (!annotatedType && addGenericParamTypes) { - if ( - index > 0 && - param.d.category === ParamCategory.Simple && - param.d.name && - !param.d.defaultValue - ) { - const typeParamName = getPseudoGenericTypeVarName(param.d.name.d.value); - annotatedType = containingClassType!.shared.typeParams.find( - (param) => param.shared.name === typeParamName - ); - } - } - - if (annotatedType) { - const adjustedAnnotatedType = adjustParamAnnotatedType(param, annotatedType); - if (adjustedAnnotatedType !== annotatedType) { - annotatedType = adjustedAnnotatedType; - } - } - - let defaultValueType: Type | undefined; - if (param.d.defaultValue) { - // If this is a stub file, a protocol, an overload, or a class - // whose body is a placeholder implementation, treat a "...", as - // an "Any" value. - let treatEllipsisAsAny = fileInfo.isStubFile || ParseTreeUtils.isSuiteEmpty(node.d.suite); - if (containingClassType && ClassType.isProtocolClass(containingClassType)) { - treatEllipsisAsAny = true; - } - if (FunctionType.isOverloaded(functionType) || FunctionType.isAbstractMethod(functionType)) { - treatEllipsisAsAny = true; - } - - defaultValueType = getTypeOfExpression( - param.d.defaultValue, - treatEllipsisAsAny ? EvalFlags.ConvertEllipsisToAny : EvalFlags.None, - makeInferenceContext(annotatedType) - ).type; - } - - if (annotatedType) { - // If there was both a type annotation and a default value, verify - // that the default value matches the annotation. - if (param.d.defaultValue && defaultValueType) { - const diagAddendum = new DiagnosticAddendum(); - - if (!assignType(annotatedType, defaultValueType, diagAddendum)) { - addDiagnostic( - DiagnosticRule.reportArgumentType, - LocMessage.paramAssignmentMismatch().format({ - sourceType: printType(defaultValueType), - paramType: printType(annotatedType), - }) + diagAddendum.getString(), - param.d.defaultValue - ); - } - } - - paramType = annotatedType; - } - - // Determine whether we need to insert an implied position-only parameter. - // This is needed when a function's parameters are named using the old-style - // way of specifying position-only parameters. - if (index >= firstNonClsSelfParamIndex) { - let isImplicitPositionOnlyParam = false; - - if (param.d.category === ParamCategory.Simple && param.d.name) { - if ( - isPrivateName(param.d.name.d.value) && - !node.d.params.some((p) => p.d.category === ParamCategory.Simple && !p.d.name) - ) { - isImplicitPositionOnlyParam = true; - - // If the parameter name indicates an implicit position-only parameter - // but we have already seen non-position-only parameters, report an error. - if ( - !paramsArePositionOnly && - functionType.shared.parameters.every((p) => p.category === ParamCategory.Simple) - ) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.positionOnlyAfterNon(), - param.d.name - ); - } - } - } else { - paramsArePositionOnly = false; - } - - if ( - paramsArePositionOnly && - !isImplicitPositionOnlyParam && - functionType.shared.parameters.length > firstNonClsSelfParamIndex - ) { - FunctionType.addPositionOnlyParamSeparator(functionType); - } - - if (!isImplicitPositionOnlyParam) { - paramsArePositionOnly = false; - } - } - - // If there was no annotation for the parameter, infer its type if possible. - let isTypeInferred = false; - if (!paramTypeNode) { - isTypeInferred = true; - const inferredType = inferParamType(node, functionType.shared.flags, index, containingClassType); - if (inferredType) { - paramType = inferredType; - } - } - - paramType = paramType ?? UnknownType.create(); - - const functionParam = FunctionParam.create( - param.d.category, - paramType, - (isTypeInferred ? FunctionParamFlags.TypeInferred : FunctionParamFlags.None) | - (paramTypeNode ? FunctionParamFlags.TypeDeclared : FunctionParamFlags.None), - param.d.name ? param.d.name.d.value : undefined, - defaultValueType, - param.d.defaultValue - ); - - FunctionType.addParam(functionType, functionParam); - - if (FunctionParam.isTypeDeclared(functionParam)) { - addTypeVarsToListIfUnique( - typeParamsSeen, - getTypeVarArgsRecursive(paramType), - functionType.shared.typeVarScopeId - ); - } - - if (param.d.name) { - const variadicParamType = transformVariadicParamType(node, param.d.category, paramType); - paramTypes.push(variadicParamType); - } else { - paramTypes.push(paramType); - } - }); - - if (paramsArePositionOnly && functionType.shared.parameters.length > firstNonClsSelfParamIndex) { - FunctionType.addPositionOnlyParamSeparator(functionType); - } - - // Update the types for the nodes associated with the parameters. - const scopeIds = ParseTreeUtils.getTypeVarScopesForNode(node); - paramTypes.forEach((paramType, index) => { - const paramNameNode = node.d.params[index].d.name; - if (paramNameNode) { - if (isUnknown(paramType)) { - functionType.shared.flags |= FunctionTypeFlags.UnannotatedParams; - } - - paramType = makeTypeVarsBound(paramType, scopeIds); - - writeTypeCache(paramNameNode, { type: paramType }, EvalFlags.None); - } - }); - - // If the function ends in P.args and P.kwargs parameters, make it exempt from - // args/kwargs compatibility checks. This is important for protocol comparisons. - if (paramTypes.length >= 2) { - const paramType1 = paramTypes[paramTypes.length - 2]; - const paramType2 = paramTypes[paramTypes.length - 1]; - if ( - isParamSpec(paramType1) && - paramType1.priv.paramSpecAccess === 'args' && - isParamSpec(paramType2) && - paramType2.priv.paramSpecAccess === 'kwargs' - ) { - functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm; - } - } - - // If the function contains an *args and a **kwargs parameter and both - // are annotated as Any or are unannotated, make it exempt from - // args/kwargs compatibility checks. - const variadicsWithAnyType = functionType.shared.parameters.filter( - (param, index) => - param.category !== ParamCategory.Simple && - param.name && - isAnyOrUnknown(FunctionType.getParamType(functionType, index)) - ); - if (variadicsWithAnyType.length >= 2) { - functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm; - } - - // If there was a defined return type, analyze that first so when we - // walk the contents of the function, return statements can be - // validated against this type. - const returnTypeAnnotationNode = - node.d.returnAnnotation ?? node.d.funcAnnotationComment?.d.returnAnnotation; - if (returnTypeAnnotationNode) { - // Temporarily set the return type to unknown in case of recursion. - functionType.shared.declaredReturnType = UnknownType.create(); - - const returnType = getTypeOfAnnotation(returnTypeAnnotationNode, { - typeVarGetsCurScope: true, - }); - functionType.shared.declaredReturnType = returnType; - } else { - // If there was no return type annotation and this is a type stub, - // we have no opportunity to infer the return type, so we'll indicate - // that it's unknown. - if (fileInfo.isStubFile) { - // Special-case the __init__ method, which is commonly left without - // an annotated return type, but we can assume it returns None. - if (node.d.name.d.value === '__init__') { - functionType.shared.declaredReturnType = getNoneType(); - } else { - functionType.shared.declaredReturnType = UnknownType.create(); - } - } else if (IsViperDialect(fileInfo.executionEnvironment.dialect)) { - // Viper 扩展:@c.CReturn(T1, T2, ...) 装饰的函数返回 tuple[T1, T2, ...]。 - // CReturn 是多返回值装饰器,参数为各返回值的类型;无注解时据此构造 tuple 返回类型, - // 使 return (a, b, c) 按位置检查每个返回值的类型。 - let cReturnTupleType: Type | undefined; - if (node.d.decorators) { - for (const decorator of node.d.decorators) { - const expr = decorator.d.expr; - if ( - expr.nodeType === ParseNodeType.Call && - expr.d.leftExpr.nodeType === ParseNodeType.MemberAccess && - expr.d.leftExpr.d.member.d.value === 'CReturn' - ) { - const tupleTypeArgs: TupleTypeArg[] = []; - for (const arg of expr.d.args) { - const argType = getTypeOfExpression(arg.d.valueExpr, EvalFlags.None).type; - const instanceType = isInstantiableClass(argType) - ? convertToInstance(argType) - : argType; - tupleTypeArgs.push({ type: instanceType, isUnbounded: false }); - } - const tupleClass = getBuiltInType(node, 'tuple'); - if (isInstantiableClass(tupleClass) && tupleTypeArgs.length > 0) { - // specializeTupleClass 返回 instantiable(type[tuple[...]]), - // 需 convertToInstance 转为实例 tuple[...] 以匹配 return (a, b, c) 的实例类型。 - cReturnTupleType = convertToInstance( - specializeTupleClass( - tupleClass, - tupleTypeArgs, - /* isUnbounded */ false - ) - ); - } - break; - } - } - } - if (cReturnTupleType) { - functionType.shared.declaredReturnType = cReturnTupleType; - } else if (node.d.name.d.value === '__new__' && functionType.shared.methodClass) { - // Viper 扩展:`__new__` 无返回注解时,默认期望返回 `T | t.CPtr`。 - // Viper 中 `__new__` 负责分配对象并返回裸指针(如 `_list.vp` 的 - // `def __new__(self, pool, elem_size=0): return pool.alloc(48)`, - // pool.alloc 返回 `t.CVoid | t.CPtr`)。 - // 返回类型设为「类实例 | 指针」的联合,使 `pool.alloc(...)` 这类 - // 裸指针返回可通过同级指针隐式转换合法化,不再报 - // "类型不可分配给返回类型 None"。 - const methodClassInstance = convertToInstance(functionType.shared.methodClass); - const cptrType = getViperCPtrType(node.d.name); - if (cptrType) { - functionType.shared.declaredReturnType = combineTypes([ - methodClassInstance, - cptrType, - ]); - } else { - functionType.shared.declaredReturnType = methodClassInstance; - } - } else { - // 无返回类型注解的函数默认返回 None(显示为 CVoid)。 - // C 语义:空返回的函数等价于 void 函数。 - functionType.shared.declaredReturnType = getNoneType(); - } - } - } - - // Accumulate any type parameters used in the return type. - if (functionType.shared.declaredReturnType && returnTypeAnnotationNode) { - addTypeVarsToListIfUnique( - typeParamsSeen, - getTypeVarArgsRecursive(functionType.shared.declaredReturnType), - functionType.shared.typeVarScopeId - ); - } - - // Validate the default types for all type parameters. - functionType.shared.typeParams.forEach((typeParam, index) => { - let bestErrorNode: ExpressionNode = node.d.name; - if (node.d.typeParams && index < node.d.typeParams.d.params.length) { - const typeParamNode = node.d.typeParams.d.params[index]; - bestErrorNode = typeParamNode.d.defaultExpr ?? typeParamNode.d.name; - } - - validateTypeParamDefault( - bestErrorNode, - typeParam, - functionType.shared.typeParams.slice(0, index), - functionType.shared.typeVarScopeId! - ); - }); - - // Clear the "partially evaluated" flag to indicate that the functionType - // is fully evaluated. - functionType.shared.flags &= ~FunctionTypeFlags.PartiallyEvaluated; - - writeTypeCache(node.d.name, { type: functionType }, EvalFlags.None); - - return functionType; - }); - } - - function markParamAccessed(param: ParameterNode) { - if (param.d.name) { - const symbolWithScope = lookUpSymbolRecursive( - param.d.name, - param.d.name.d.value, - /* honorCodeFlow */ false - ); - if (symbolWithScope) { - setSymbolAccessed(AnalyzerNodeInfo.getFileInfo(param), symbolWithScope.symbol, param.d.name); - } - } - } - - function adjustParamAnnotatedType(param: ParameterNode, type: Type): Type { - // PEP 484 indicates that if a parameter has a default value of 'None' - // the type checker should assume that the type is optional (i.e. a union - // of the specified type and 'None'). Skip this step if the type is already - // optional to avoid losing alias names when combining the types. - if ( - param.d.defaultValue?.nodeType === ParseNodeType.Constant && - param.d.defaultValue.d.constType === KeywordType.None && - !isOptionalType(type) && - !AnalyzerNodeInfo.getFileInfo(param).diagnosticRuleSet.strictParameterNoneValue - ) { - return combineTypes([type, getNoneType()]); - } - - return type; - } - - // Attempts to infer an unannotated parameter type from available context. - function inferParamType( - functionNode: FunctionNode, - functionFlags: FunctionTypeFlags, - paramIndex: number, - containingClassType: ClassType | undefined - ) { - // Is the function a method within a class? If so, see if a base class - // defines the same method and provides annotations. - if (containingClassType) { - if (paramIndex === 0) { - if ((functionFlags & FunctionTypeFlags.StaticMethod) === 0) { - const hasClsParam = - (functionFlags & (FunctionTypeFlags.ClassMethod | FunctionTypeFlags.ConstructorMethod)) !== 0; - return synthesizeTypeVarForSelfCls(containingClassType, hasClsParam); - } - } - - const methodName = functionNode.d.name.d.value; - - const baseClassMemberInfo = lookUpClassMember( - containingClassType, - methodName, - MemberAccessFlags.SkipOriginalClass - ); - - if (baseClassMemberInfo) { - const memberDecls = baseClassMemberInfo.symbol.getDeclarations(); - if (memberDecls.length === 1 && memberDecls[0].type === DeclarationType.Function) { - const baseClassMethodNode = memberDecls[0].node; - - // Does the signature match exactly with the exception of annotations? - if ( - baseClassMethodNode.d.params.length === functionNode.d.params.length && - baseClassMethodNode.d.params.every((param, index) => { - const overrideParam = functionNode.d.params[index]; - return ( - overrideParam.d.name?.d.value === param.d.name?.d.value && - overrideParam.d.category === param.d.category - ); - }) - ) { - const baseClassParam = baseClassMethodNode.d.params[paramIndex]; - const baseClassParamAnnotation = - baseClassParam.d.annotation ?? baseClassParam.d.annotationComment; - if (baseClassParamAnnotation) { - let inferredParamType = getTypeOfParamAnnotation( - baseClassParamAnnotation, - functionNode.d.params[paramIndex].d.category - ); - - // If the parameter type is generic, specialize it in the context - // of the child class. - if (requiresSpecialization(inferredParamType) && isClass(baseClassMemberInfo.classType)) { - const scopeIds: TypeVarScopeId[] = getTypeVarScopeIds(baseClassMemberInfo.classType); - const solution = buildSolutionFromSpecializedClass(baseClassMemberInfo.classType); - - scopeIds.push(ParseTreeUtils.getScopeIdForNode(baseClassMethodNode)); - - // Replace any unsolved TypeVars with Unknown (including all function-scoped TypeVars). - inferredParamType = applySolvedTypeVars(inferredParamType, solution, { - replaceUnsolved: { - scopeIds, - tupleClassType: getTupleClassType(), - }, - }); - } - - const fileInfo = AnalyzerNodeInfo.getFileInfo(functionNode); - if (fileInfo.isInPyTypedPackage && !fileInfo.isStubFile) { - inferredParamType = TypeBase.cloneForAmbiguousType(inferredParamType); - } - - return inferredParamType; - } - } - } - } - } - - // If the parameter has a default argument value, we may be able to infer its - // type from this information. - const paramValueExpr = functionNode.d.params[paramIndex].d.defaultValue; - if (paramValueExpr) { - return inferParamTypeFromDefaultValue(paramValueExpr); - } - - return undefined; - } - - function inferParamTypeFromDefaultValue(paramValueExpr: ExpressionNode) { - const defaultValueType = getTypeOfExpression(paramValueExpr, EvalFlags.ConvertEllipsisToAny).type; - - let inferredParamType: Type | undefined; - - // Is the default value a "None", a sentinel, or an instance of some private - // class (one whose name starts with an underscore)? If so, we will assume - // that the value is a singleton sentinel. The actual supported type is - // going to be a union of this type and Unknown. - if ( - isNoneInstance(defaultValueType) || - isSentinelLiteral(defaultValueType) || - (isClassInstance(defaultValueType) && isPrivateOrProtectedName(defaultValueType.shared.name)) - ) { - inferredParamType = combineTypes([defaultValueType, UnknownType.create()]); - } else { - let skipInference = false; - - if (isFunctionOrOverloaded(defaultValueType)) { - // Do not infer parameter types that use a lambda or another function as a - // default value. We're likely to generate false positives in this case. - // It's not clear whether parameters should be positional-only or not. - skipInference = true; - } else if ( - isClassInstance(defaultValueType) && - ClassType.isBuiltIn(defaultValueType, ['tuple', 'list', 'set', 'dict']) - ) { - // Do not infer certain types like tuple because it's likely to be - // more restrictive (narrower) than intended. - skipInference = true; - } - - if (!skipInference) { - inferredParamType = convertSpecialFormToRuntimeValue( - defaultValueType, - EvalFlags.None, - /* convertModule */ true - ); - inferredParamType = stripTypeForm(inferredParamType); - inferredParamType = stripLiteralValue(inferredParamType); - } - } - - if (inferredParamType) { - const fileInfo = AnalyzerNodeInfo.getFileInfo(paramValueExpr); - if (fileInfo.isInPyTypedPackage && !fileInfo.isStubFile) { - inferredParamType = TypeBase.cloneForAmbiguousType(inferredParamType); - } - } - - return inferredParamType; - } - - // Transforms the parameter type based on its category. If it's a simple parameter, - // no transform is applied. If it's a var-arg or keyword-arg parameter, the type - // is wrapped in a List or Dict. - function transformVariadicParamType(node: ParseNode, paramCategory: ParamCategory, type: Type): Type { - switch (paramCategory) { - case ParamCategory.Simple: { - return type; - } - - case ParamCategory.ArgsList: { - if (isParamSpec(type) && type.priv.paramSpecAccess) { - return type; - } - - if (isUnpackedClass(type)) { - return ClassType.cloneForPacked(type); - } - - return makeTupleObject(evaluatorInterface, [{ type, isUnbounded: !isTypeVarTuple(type) }]); - } - - case ParamCategory.KwargsDict: { - // Leave a ParamSpec alone. - if (isParamSpec(type) && type.priv.paramSpecAccess) { - return type; - } - - // Is this an unpacked TypedDict? If so, return its packed version. - if (isClassInstance(type) && ClassType.isTypedDictClass(type) && type.priv.isUnpacked) { - return ClassType.cloneForPacked(type); - } - - // Wrap the type in a dict with str keys. - const dictType = getBuiltInType(node, 'dict'); - const strType = getBuiltInObject(node, 'str'); - - if (isInstantiableClass(dictType) && isClassInstance(strType)) { - return ClassType.cloneAsInstance(ClassType.specialize(dictType, [strType, type])); - } - - return UnknownType.create(); - } - } - } - - function createAsyncFunction(node: FunctionNode, functionType: FunctionType): FunctionType { - assert(FunctionType.isAsync(functionType)); - - // Clone the original function and replace its return type with an - // Awaitable[]. Mark the new function as no longer async. - const awaitableFunctionType = FunctionType.cloneWithNewFlags( - functionType, - functionType.shared.flags & ~(FunctionTypeFlags.Async | FunctionTypeFlags.PartiallyEvaluated) - ); - - if (functionType.shared.declaredReturnType) { - awaitableFunctionType.shared.declaredReturnType = createAwaitableReturnType( - node, - functionType.shared.declaredReturnType, - FunctionType.isGenerator(functionType) - ); - } else { - awaitableFunctionType.shared.inferredReturnType = { - type: createAwaitableReturnType( - node, - getInferredReturnType(functionType), - FunctionType.isGenerator(functionType) - ), - }; - } - - return awaitableFunctionType; - } - - function createAwaitableReturnType( - node: ParseNode, - returnType: Type, - isGenerator: boolean, - useCoroutine = true - ): Type { - let awaitableReturnType: Type | undefined; - - if (isClassInstance(returnType)) { - if (ClassType.isBuiltIn(returnType)) { - if (returnType.shared.name === 'Generator') { - // If the return type is a Generator, change it to an AsyncGenerator. - const asyncGeneratorType = getTypingType(node, 'AsyncGenerator'); - if (asyncGeneratorType && isInstantiableClass(asyncGeneratorType)) { - const typeArgs: Type[] = []; - const generatorTypeArgs = returnType.priv.typeArgs; - if (generatorTypeArgs && generatorTypeArgs.length > 0) { - typeArgs.push(generatorTypeArgs[0]); - } - if (generatorTypeArgs && generatorTypeArgs.length > 1) { - typeArgs.push(generatorTypeArgs[1]); - } - awaitableReturnType = ClassType.cloneAsInstance( - ClassType.specialize(asyncGeneratorType, typeArgs) - ); - } - } else if (['AsyncIterator', 'AsyncIterable'].some((name) => name === returnType.shared.name)) { - // If it's already an AsyncIterator or AsyncIterable, leave it as is. - awaitableReturnType = returnType; - } else if (returnType.shared.name === 'AsyncGenerator') { - // If it's already an AsyncGenerator and the function is a generator, - // leave it as is. - if (isGenerator) { - awaitableReturnType = returnType; - } - } - } - } - - if (!awaitableReturnType || !isGenerator) { - // Wrap in either an Awaitable or a CoroutineType, which is a subclass of Awaitable. - const awaitableType = useCoroutine ? getTypesType(node, 'CoroutineType') : getTypingType(node, 'Awaitable'); - if (awaitableType && isInstantiableClass(awaitableType)) { - awaitableReturnType = ClassType.cloneAsInstance( - ClassType.specialize( - awaitableType, - useCoroutine ? [AnyType.create(), AnyType.create(), returnType] : [returnType] - ) - ); - } else { - awaitableReturnType = UnknownType.create(); - } - } - - return awaitableReturnType; - } - - function inferFunctionReturnType( - node: FunctionNode, - isAbstract: boolean, - callerNode: ExpressionNode | undefined - ): TypeResult | undefined { - const returnAnnotation = node.d.returnAnnotation || node.d.funcAnnotationComment?.d.returnAnnotation; - - // This shouldn't be called if there is a declared return type, but it - // can happen if there are unexpected cycles between decorators and - // classes that they decorate. We'll just return an undefined type - // in this case. - if (returnAnnotation) { - return undefined; - } - - // Is this type already cached? - let inferredReturnType = readTypeCache(node.d.suite, EvalFlags.None); - let isIncomplete = false; - - if (inferredReturnType) { - return { type: inferredReturnType, isIncomplete }; - } - - const recursionEntry = functionRecursionMap.get(node.id) ?? []; - - if (functionRecursionMap.size >= maxInferFunctionReturnRecursionCount) { - inferredReturnType = UnknownType.create(); - isIncomplete = true; - } else if (recursionEntry.some((entry) => entry.callerNode === callerNode)) { - inferredReturnType = UnknownType.create(); - isIncomplete = true; - } else { - recursionEntry.push({ callerNode }); - functionRecursionMap.set(node.id, recursionEntry); - - try { - let functionDecl: FunctionDeclaration | undefined; - const decl = AnalyzerNodeInfo.getDeclaration(node); - if (decl) { - functionDecl = decl as FunctionDeclaration; - } - - const functionNeverReturns = !isAfterNodeReachable(node); - const implicitlyReturnsNone = isAfterNodeReachable(node.d.suite); - - // Infer the return type based on all of the return statements in the function's body. - if (AnalyzerNodeInfo.getFileInfo(node).isStubFile) { - // If a return type annotation is missing in a stub file, assume - // it's an "unknown" type. In normal source files, we can infer the - // type from the implementation. - inferredReturnType = UnknownType.create(); - } else { - if (functionNeverReturns) { - // If the function always raises and never returns, assume a "NoReturn" type. - // Skip this for abstract methods which often are implemented with "raise - // NotImplementedError()". - if (isAbstract || methodAlwaysRaisesNotImplemented(functionDecl)) { - inferredReturnType = UnknownType.create(); - } else { - inferredReturnType = NeverType.createNoReturn(); - } - } else { - const inferredReturnTypes: Type[] = []; - if (functionDecl?.returnStatements) { - functionDecl.returnStatements.forEach((returnNode) => { - if (isNodeReachable(returnNode)) { - if (returnNode.d.expr) { - const returnTypeResult = getTypeOfExpression(returnNode.d.expr); - if (returnTypeResult.isIncomplete) { - isIncomplete = true; - } - - let returnType = returnTypeResult.type; - - // If the type is a special form, use the special form instead. - if (returnType.props?.specialForm) { - returnType = returnType.props.specialForm; - } - - // If the return type includes an instance of a class with isEmptyContainer - // set, clear that because we don't want this flag to "leak" into the - // inferred return type. - returnType = mapSubtypes(returnType, (subtype) => { - if (isClassInstance(subtype) && subtype.priv.isEmptyContainer) { - return ClassType.specialize( - subtype, - subtype.priv.typeArgs, - !!subtype.priv.isTypeArgExplicit, - subtype.priv.includeSubclasses, - subtype.priv.tupleTypeArgs, - /* isEmptyContainer */ false - ); - } - return subtype; - }); - - // Do not retain TypeForm types in inferred return types. - returnType = stripTypeForm(returnType); - - // If we're returning a function value (or overload), force - // lazy return-type inference before caching this function's - // inferred return. Without this, a partially evaluated - // function can leak a temporary Any return type (for example, - // immediately after an edit), and that Any then gets cached - // as though it were final for the enclosing function. - inferReturnTypeIfNecessary(returnType); - - inferredReturnTypes.push(returnType); - } else { - inferredReturnTypes.push(getNoneType()); - } - } - }); - } - - if (!functionNeverReturns && implicitlyReturnsNone) { - inferredReturnTypes.push(getNoneType()); - } - - inferredReturnType = combineTypes(inferredReturnTypes); - - // Remove any unbound values since those would generate an exception - // before being returned. - inferredReturnType = removeUnbound(inferredReturnType); - } - - // Is it a generator? - if (functionDecl?.isGenerator) { - const inferredYieldTypes: Type[] = []; - let useAwaitableGenerator = false; - let isYieldResultUsed = false; - - if (functionDecl.yieldStatements) { - functionDecl.yieldStatements.forEach((yieldNode) => { - if (isNodeReachable(yieldNode)) { - if (yieldNode.nodeType === ParseNodeType.YieldFrom) { - isYieldResultUsed = true; - const iteratorTypeResult = getTypeOfExpression(yieldNode.d.expr); - if ( - isClassInstance(iteratorTypeResult.type) && - ClassType.isBuiltIn(iteratorTypeResult.type, ['Coroutine', 'CoroutineType']) - ) { - const yieldType = - iteratorTypeResult.type.priv.typeArgs && - iteratorTypeResult.type.priv.typeArgs.length > 0 - ? iteratorTypeResult.type.priv.typeArgs[0] - : UnknownType.create(); - - // Handle old-style (pre-await) Coroutines. - inferredYieldTypes.push(yieldType); - useAwaitableGenerator = true; - } else { - const yieldType = getTypeOfIterator( - iteratorTypeResult, - /* isAsync */ false, - yieldNode - )?.type; - - inferredYieldTypes.push(yieldType ?? UnknownType.create()); - } - } else { - // If the yield expression is not by itself in a statement list, - // assume that its result is consumed. - if (yieldNode?.parent?.nodeType !== ParseNodeType.StatementList) { - isYieldResultUsed = true; - } - - if (yieldNode.d.expr) { - const yieldType = getTypeOfExpression(yieldNode.d.expr).type; - inferredYieldTypes.push(yieldType ?? UnknownType.create()); - } else { - inferredYieldTypes.push(getNoneType()); - } - } - } - }); - } - - const inferredYieldType = combineTypes(inferredYieldTypes); - - // Inferred yield types need to be wrapped in a Generator or - // AwaitableGenerator to produce the final result. - const generatorType = useAwaitableGenerator - ? getTypeCheckerInternalsType(node, 'AwaitableGenerator') ?? - getTypingType(node, 'AwaitableGenerator') - : getTypingType(node, 'Generator'); - - if (generatorType && isInstantiableClass(generatorType)) { - const typeArgs: Type[] = []; - - // The "send type" for the generator (the second type argument) is - // not generally inferrable, but we can assume that it's Any - // if the function never uses the value and Unknown if it does. - // This eliminates any "partially unknown" errors in strict mode - // in the common case. - const sendType = isYieldResultUsed ? UnknownType.create() : AnyType.create(); - - typeArgs.push(inferredYieldType, sendType, inferredReturnType); - - if (useAwaitableGenerator) { - typeArgs.push(AnyType.create()); - } - - inferredReturnType = ClassType.cloneAsInstance( - ClassType.specialize(generatorType, typeArgs) - ); - } else { - inferredReturnType = UnknownType.create(); - } - } - } - - writeTypeCache(node.d.suite, { type: inferredReturnType, isIncomplete }, EvalFlags.None); - } catch (err) { - // Attempt to handle a stack overflow without crashing. In rare - // cases, we can get very deep stacks when inferring return types - // within untyped code. - if ((err as any)?.message === 'Maximum call stack size exceeded') { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - console.error( - `Overflowed stack when inferring return type for function: ${ - node.d.name.d.value - } in file ${fileInfo.fileUri.toUserVisibleString()}` - ); - return; - } - throw err; - } finally { - recursionEntry.pop(); - if (recursionEntry.length === 0) { - functionRecursionMap.delete(node.id); - } - } - } - - return inferredReturnType ? { type: inferredReturnType, isIncomplete } : undefined; - } - - // Determines whether the method consists only of a "raise" statement - // and the exception type raised is a NotImplementedError or a subclass - // thereof. This is commonly used for abstract methods. - function methodAlwaysRaisesNotImplemented(functionDecl?: FunctionDeclaration): boolean { - if ( - !functionDecl || - !functionDecl.isMethod || - functionDecl.returnStatements || - functionDecl.yieldStatements || - !functionDecl.raiseStatements - ) { - return false; - } - - const statements = functionDecl.node.d.suite.d.statements; - if (statements.some((statement) => statement.nodeType !== ParseNodeType.StatementList)) { - return false; - } - - for (const raiseStatement of functionDecl.raiseStatements) { - if (!raiseStatement.d.expr || raiseStatement.d.fromExpr) { - return false; - } - const raiseType = getTypeOfExpression(raiseStatement.d.expr).type; - const classType = isInstantiableClass(raiseType) - ? raiseType - : isClassInstance(raiseType) - ? raiseType - : undefined; - if (!classType || !derivesFromStdlibClass(classType, 'NotImplementedError')) { - return false; - } - } - - return true; - } - - function evaluateTypesForForStatement(node: ForNode): void { - if (isTypeCached(node)) { - return; - } - - const iteratorTypeResult = getTypeOfExpression(node.d.iterableExpr); - const iteratedType = - getTypeOfIterator(iteratorTypeResult, !!node.d.isAsync, node.d.iterableExpr)?.type ?? UnknownType.create(); - - assignTypeToExpression( - node.d.targetExpr, - { type: iteratedType, isIncomplete: iteratorTypeResult.isIncomplete }, - node.d.targetExpr - ); - - writeTypeCache(node, { type: iteratedType, isIncomplete: !!iteratorTypeResult.isIncomplete }, EvalFlags.None); - } - - function evaluateTypesForExceptStatement(node: ExceptNode): void { - // This should be called only if the except node has a target exception. - assert(node.d.typeExpr !== undefined); - - if (isTypeCached(node)) { - return; - } - - const exceptionTypeResult = getTypeOfExpression(node.d.typeExpr!); - const exceptionTypes = exceptionTypeResult.type; - let includesBaseException = false; - - function getExceptionType(exceptionType: Type, errorNode: ExpressionNode) { - exceptionType = makeTopLevelTypeVarsConcrete(exceptionType); - - if (isAnyOrUnknown(exceptionType)) { - return exceptionType; - } - - if (isInstantiableClass(exceptionType)) { - if (ClassType.isBuiltIn(exceptionType, 'BaseException')) { - includesBaseException = true; - } - return ClassType.cloneAsInstance(exceptionType); - } - - if (isClassInstance(exceptionType)) { - const iterableType = - getTypeOfIterator( - { type: exceptionType, isIncomplete: exceptionTypeResult.isIncomplete }, - /* isAsync */ false, - errorNode, - /* emitNotIterableError */ false - )?.type ?? UnknownType.create(); - - return mapSubtypes(iterableType, (subtype) => { - if (isAnyOrUnknown(subtype)) { - return subtype; - } - - return UnknownType.create(); - }); - } - - return UnknownType.create(); - } - - let targetType = mapSubtypes(exceptionTypes, (subType) => { - // If more than one type was specified for the exception, we'll receive - // a specialized tuple object here. - const tupleType = getSpecializedTupleType(subType); - if (tupleType && tupleType.priv.tupleTypeArgs) { - const entryTypes = tupleType.priv.tupleTypeArgs.map((t) => { - return getExceptionType(t.type, node.d.typeExpr!); - }); - return combineTypes(entryTypes); - } - - return getExceptionType(subType, node.d.typeExpr!); - }); - - // If this is an except group, wrap the exception type in an ExceptionGroup - // or BaseExceptionGroup depending on whether the target exception is - // a BaseException. - if (node.d.isExceptGroup) { - targetType = getBuiltInObject(node, includesBaseException ? 'BaseExceptionGroup' : 'ExceptionGroup', [ - targetType, - ]); - } - - if (node.d.name) { - assignTypeToExpression(node.d.name, { type: targetType }, node.d.name); - } - - writeTypeCache(node, { type: targetType }, EvalFlags.None); - } - - function evaluateTypesForWithStatement(node: WithItemNode): void { - if (isTypeCached(node)) { - return; - } - - const exprTypeResult = getTypeOfExpression(node.d.expr); - let isIncomplete = exprTypeResult.isIncomplete; - let exprType = exprTypeResult.type; - const isAsync = node.parent && node.parent.nodeType === ParseNodeType.With && !!node.parent.d.isAsync; - - if (isOptionalType(exprType)) { - addDiagnostic( - DiagnosticRule.reportOptionalContextManager, - isAsync ? LocMessage.noneNotUsableWithAsync() : LocMessage.noneNotUsableWith(), - node.d.expr - ); - exprType = removeNoneFromUnion(exprType); - } - - // Verify that the target has an __enter__ or __aenter__ method defined. - const enterMethodName = isAsync ? '__aenter__' : '__enter__'; - const scopedType = mapSubtypes(exprType, (subtype) => { - subtype = makeTopLevelTypeVarsConcrete(subtype); - - if (isAnyOrUnknown(subtype)) { - return subtype; - } - - const enterDiag = new DiagnosticAddendum(); - - if (isClass(subtype)) { - const enterTypeResult = getTypeOfMagicMethodCall( - subtype, - enterMethodName, - [], - node.d.expr, - /* inferenceContext */ undefined, - enterDiag.createAddendum() - ); - - if (enterTypeResult) { - if (isAsync) { - if (enterTypeResult.isIncomplete) { - isIncomplete = true; - } - - const asyncResult = getTypeOfAwaitable({ type: enterTypeResult.type }, node.d.expr); - if (asyncResult.isIncomplete) { - isIncomplete = true; - } - - return asyncResult.type; - } - return enterTypeResult.type; - } - - if (!isAsync) { - if ( - getTypeOfMagicMethodCall( - subtype, - '__aenter__', - [], - node.d.expr, - /* inferenceContext */ undefined - )?.type - ) { - enterDiag.addMessage(LocAddendum.asyncHelp()); - } - } - } - - const message = isAsync ? LocMessage.typeNotUsableWithAsync() : LocMessage.typeNotUsableWith(); - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - message.format({ type: printType(subtype), method: enterMethodName }) + enterDiag.getString(), - node.d.expr - ); - return UnknownType.create(); - }); - - // Verify that the target has an __exit__ or __aexit__ method defined. - const exitMethodName = isAsync ? '__aexit__' : '__exit__'; - const exitDiag = new DiagnosticAddendum(); - - doForEachSubtype(exprType, (subtype) => { - subtype = makeTopLevelTypeVarsConcrete(subtype); - - if (isAnyOrUnknown(subtype)) { - return; - } - - if (isClass(subtype)) { - const anyArg: TypeResult = { type: AnyType.create() }; - const exitTypeResult = getTypeOfMagicMethodCall( - subtype, - exitMethodName, - [anyArg, anyArg, anyArg], - node.d.expr, - /* inferenceContext */ undefined, - exitDiag - ); - - if (exitTypeResult) { - if (exitTypeResult.isIncomplete) { - isIncomplete = true; - } - - if (isAsync) { - const asyncResult = getTypeOfAwaitable({ type: exitTypeResult.type }, node.d.expr); - if (asyncResult.isIncomplete) { - isIncomplete = true; - } - - return asyncResult.type; - } - - return exitTypeResult.type; - } - } - - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeNotUsableWith().format({ type: printType(subtype), method: exitMethodName }) + - exitDiag.getString(), - node.d.expr - ); - return UnknownType.create(); - }); - - if (node.d.target) { - assignTypeToExpression(node.d.target, { type: scopedType, isIncomplete }, node.d.target); - } - - writeTypeCache(node, { type: scopedType, isIncomplete }, EvalFlags.None); - } - - function evaluateTypesForImportAs(node: ImportAsNode): void { - if (isTypeCached(node)) { - return; - } - - let symbolNameNode: NameNode; - if (node.d.alias) { - // The symbol name is defined by the alias. - symbolNameNode = node.d.alias; - } else { - // There was no alias, so we need to use the first element of - // the name parts as the symbol. - symbolNameNode = node.d.module.d.nameParts[0]; - } - - if (!symbolNameNode) { - // This can happen in certain cases where there are parse errors. - return; - } - - // Look up the symbol to find the alias declaration. - let symbolType = getAliasedSymbolTypeForName(node, symbolNameNode.d.value) ?? UnknownType.create(); - - // Viper 扩展:模块未解析时,在符号名处标黄警告"名称找不到"。 - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - if (isUnknown(symbolType) && IsViperDialect(fileInfo.executionEnvironment.dialect)) { - const importInfo = AnalyzerNodeInfo.getImportInfo(node.d.module); - if (importInfo && !importInfo.isImportFound) { - fileInfo.diagnosticSink.addDiagnosticWithTextRange( - 'warning', - LocMessage.viperUnresolvedImportName().format({ - name: symbolNameNode.d.value, - }), - symbolNameNode - ); - } - } - - // Is there a cached module type associated with this node? If so, use - // it instead of the type we just created. - const cachedModuleType = readTypeCache(node, EvalFlags.None) as ModuleType; - if (cachedModuleType && isModule(cachedModuleType) && symbolType) { - if (isTypeSame(symbolType, cachedModuleType)) { - symbolType = cachedModuleType; - } - } - - assignTypeToNameNode(symbolNameNode, { type: symbolType }, /* ignoreEmptyContainers */ false); - - writeTypeCache(node, { type: symbolType }, EvalFlags.None); - } - - function evaluateTypesForImportFromAs(node: ImportFromAsNode): void { - if (isTypeCached(node)) { - return; - } - - const aliasNode = node.d.alias || node.d.name; - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - - // If this is a redundant form of an import, assume it is an intentional - // export and mark the symbol as accessed. - if (node.d.alias?.d.value === node.d.name.d.value) { - const symbolInScope = lookUpSymbolRecursive(node, node.d.name.d.value, /* honorCodeFlow */ true); - if (symbolInScope) { - setSymbolAccessed(fileInfo, symbolInScope.symbol, node); - } - } - - // If this is an import into a class scope, mark the symbol as accessed. - const classNode = ParseTreeUtils.getEnclosingClass(node, /* stopAtFunction */ true); - if (classNode) { - const symbolInScope = lookUpSymbolRecursive(node, aliasNode.d.value, /* honorCodeFlow */ true); - if (symbolInScope) { - setSymbolAccessed(fileInfo, symbolInScope.symbol, node); - } - } - - let symbolType = getAliasedSymbolTypeForName(node, aliasNode.d.value); - if (!symbolType) { - const parentNode = node.parent as ImportFromNode; - assert(parentNode && parentNode.nodeType === ParseNodeType.ImportFrom); - assert(!parentNode.d.isWildcardImport); - - const importInfo = AnalyzerNodeInfo.getImportInfo(parentNode.d.module); - if (importInfo && importInfo.isImportFound && !importInfo.isNativeLib) { - const resolvedPath = importInfo.resolvedUris[importInfo.resolvedUris.length - 1]; - - const importLookupInfo = importLookup(resolvedPath); - let reportError = false; - - // If we were able to resolve the import, report the error as - // an unresolved symbol. - if (importLookupInfo) { - reportError = true; - - // Viper 扩展:模块级 __getattr__(PEP 562)语义不存在,跳过动态符号解析。 - } else if (resolvedPath.isEmpty()) { - // This corresponds to the "from . import a" form. - reportError = true; - } - - if (reportError) { - addDiagnostic( - DiagnosticRule.reportAttributeAccessIssue, - LocMessage.importSymbolUnknown().format({ name: node.d.name.d.value }), - node.d.name - ); - } - } - - if (!symbolType) { - symbolType = UnknownType.create(); - } - } - - assignTypeToNameNode(aliasNode, { type: symbolType }, /* ignoreEmptyContainers */ false); - - // Viper: Explicitly mark the imported symbol as accessed. With .vp/.vpi - // resolution, the symbol id registered at import time may differ from - // the id resolved during name usage tracking, which causes the - // setSymbolAccessed call inside getTypeOfName to miss the checker's - // accessedSymbolSet lookup and produce false "is not accessed" diagnostics. - // The redundant-alias and class-scope cases above already mark the - // symbol; this covers the remaining general import case. - const symbolInScope = lookUpSymbolRecursive(node, aliasNode.d.value, /* honorCodeFlow */ true); - if (symbolInScope) { - setSymbolAccessed(fileInfo, symbolInScope.symbol, node); - } - - writeTypeCache(node, { type: symbolType }, EvalFlags.None); - } - - function evaluateTypesForMatchStatement(node: MatchNode): void { - if (isTypeCached(node)) { - return; - } - - const subjectTypeResult = getTypeOfExpression(node.d.expr); - let subjectType = subjectTypeResult.type; - - // Apply negative narrowing for each of the cases that doesn't have a guard statement. - for (const caseStatement of node.d.cases) { - if (!caseStatement.d.guardExpr) { - subjectType = narrowTypeBasedOnPattern( - evaluatorInterface, - subjectType, - caseStatement.d.pattern, - /* isPositiveTest */ false - ); - } - } - - writeTypeCache(node, { type: subjectType, isIncomplete: !!subjectTypeResult.isIncomplete }, EvalFlags.None); - } - - function evaluateTypesForCaseStatement(node: CaseNode): void { - if (isTypeCached(node)) { - return; - } - - if (!node.parent || node.parent.nodeType !== ParseNodeType.Match) { - fail('Expected parent of case statement to be match statement'); - return; - } - - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - const subjectTypeResult = getTypeOfExpression(node.parent.d.expr); - let subjectType = subjectTypeResult.type; - - // Apply negative narrowing for each of the cases prior to the current one - // except for those that have a guard expression. - for (const caseStatement of node.parent.d.cases) { - if (caseStatement === node) { - if (fileInfo.diagnosticRuleSet.reportUnnecessaryComparison !== 'none') { - if (!subjectTypeResult.isIncomplete) { - checkForUnusedPattern(evaluatorInterface, node.d.pattern, subjectType); - } - } - break; - } - - if (!caseStatement.d.guardExpr) { - subjectType = narrowTypeBasedOnPattern( - evaluatorInterface, - subjectType, - caseStatement.d.pattern, - /* isPositiveTest */ false - ); - } - } - - const narrowedSubjectType = assignTypeToPatternTargets( - evaluatorInterface, - subjectType, - !!subjectTypeResult.isIncomplete, - node.d.pattern - ); - - writeTypeCache( - node, - { type: narrowedSubjectType, isIncomplete: !!subjectTypeResult.isIncomplete }, - EvalFlags.None - ); - } - - function evaluateTypesForImportFrom(node: ImportFromNode): void { - if (isTypeCached(node)) { - return; - } - - if (node.d.isWildcardImport) { - // Write back a dummy type so we don't evaluate this node again. - writeTypeCache(node, { type: AnyType.create() }, EvalFlags.None); - - const flowNode = AnalyzerNodeInfo.getFlowNode(node); - if (flowNode && (flowNode.flags & FlowFlags.WildcardImport) !== 0) { - const wildcardFlowNode = flowNode as FlowWildcardImport; - wildcardFlowNode.names.forEach((name) => { - const importedSymbolType = getAliasedSymbolTypeForName(node, name); - - if (!importedSymbolType) { - return; - } - - const symbolWithScope = lookUpSymbolRecursive(node, name, /* honorCodeFlow */ false); - if (!symbolWithScope) { - return; - } - - const declaredType = getDeclaredTypeOfSymbol(symbolWithScope.symbol)?.type; - if (!declaredType) { - return; - } - - const diagAddendum = new DiagnosticAddendum(); - - if (!assignType(declaredType, importedSymbolType, diagAddendum)) { - addDiagnostic( - DiagnosticRule.reportAssignmentType, - LocMessage.typeAssignmentMismatchWildcard().format({ - ...printSrcDestTypes(importedSymbolType, declaredType), - name, - }) + diagAddendum.getString(), - node, - node.d.wildcardToken ?? node - ); - } - }); - } - } else { - // Use the first element of the name parts as the symbol. - const symbolNameNode = node.d.module.d.nameParts[0]; - - // Look up the symbol to find the alias declaration. - let symbolType = getAliasedSymbolTypeForName(node, symbolNameNode.d.value); - if (!symbolType) { - return; - } - - // Is there a cached module type associated with this node? If so, use - // it instead of the type we just created. - const cachedModuleType = readTypeCache(node, EvalFlags.None) as ModuleType; - if (cachedModuleType && isModule(cachedModuleType) && symbolType) { - if (isTypeSame(symbolType, cachedModuleType)) { - symbolType = cachedModuleType; - } - } - - assignTypeToNameNode(symbolNameNode, { type: symbolType }, /* ignoreEmptyContainers */ false); - - writeTypeCache(node, { type: symbolType }, EvalFlags.None); - } - } - - function evaluateTypesForTypeAnnotationNode(node: TypeAnnotationNode) { - // If this node is part of an assignment statement, use specialized - // logic that performs bidirectional inference and assignment - // type narrowing. - if (node.parent?.nodeType === ParseNodeType.Assignment) { - evaluateTypesForAssignmentStatement(node.parent); - } else { - const annotationType = getTypeOfAnnotation(node.d.annotation, { - varTypeAnnotation: true, - allowFinal: isFinalAllowedForAssignmentTarget(node.d.valueExpr), - allowClassVar: isClassVarAllowedForAssignmentTarget(node.d.valueExpr), - }); - - writeTypeCache(node.d.valueExpr, { type: annotationType }, EvalFlags.None); - } - } - - function getAliasedSymbolTypeForName( - node: ImportAsNode | ImportFromAsNode | ImportFromNode, - name: string - ): Type | undefined { - const symbolWithScope = lookUpSymbolRecursive(node, name, /* honorCodeFlow */ true); - if (!symbolWithScope) { - return undefined; - } - - // Normally there will be at most one decl associated with the import node, but - // there can be multiple in the case of the "from .X import X" statement. In such - // case, we want to choose the last declaration. - const filteredDecls = symbolWithScope.symbol - .getDeclarations() - .filter( - (decl) => ParseTreeUtils.isNodeContainedWithin(node, decl.node) && decl.type === DeclarationType.Alias - ); - let aliasDecl = filteredDecls.length > 0 ? filteredDecls[filteredDecls.length - 1] : undefined; - - // If we didn't find an exact match, look for any alias associated with - // this symbol. In cases where we have multiple ImportAs nodes that share - // the same first-part name (e.g. "import asyncio" and "import asyncio.tasks"), - // we may not find the declaration associated with this node. - if (!aliasDecl) { - aliasDecl = symbolWithScope.symbol.getDeclarations().find((decl) => decl.type === DeclarationType.Alias); - } - - if (!aliasDecl) { - return undefined; - } - - assert(aliasDecl.type === DeclarationType.Alias); - - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - - // Try to resolve the alias while honoring external visibility. - const resolvedAliasInfo = resolveAliasDeclarationWithInfo(aliasDecl, /* resolveLocalNames */ true, { - allowExternallyHiddenAccess: fileInfo.isStubFile, - }); - - if (!resolvedAliasInfo) { - return undefined; - } - - if (!resolvedAliasInfo.declaration) { - return evaluatorOptions.evaluateUnknownImportsAsAny ? AnyType.create() : UnknownType.create(); - } - - if (node.nodeType === ParseNodeType.ImportFromAs) { - if (resolvedAliasInfo.isPrivate) { - addDiagnostic( - DiagnosticRule.reportPrivateUsage, - LocMessage.privateUsedOutsideOfModule().format({ - name: node.d.name.d.value, - }), - node.d.name - ); - } - - if (resolvedAliasInfo.privatePyTypedImporter) { - const diag = new DiagnosticAddendum(); - if (resolvedAliasInfo.privatePyTypedImported) { - diag.addMessage( - LocAddendum.privateImportFromPyTypedSource().format({ - module: resolvedAliasInfo.privatePyTypedImported, - }) - ); - } - addDiagnostic( - DiagnosticRule.reportPrivateImportUsage, - LocMessage.privateImportFromPyTypedModule().format({ - name: node.d.name.d.value, - module: resolvedAliasInfo.privatePyTypedImporter, - }) + diag.getString(), - node.d.name - ); - } - } - - return getInferredTypeOfDeclaration(symbolWithScope.symbol, aliasDecl); - } - - // In some cases, an expression must be evaluated in the context of another - // expression or statement that contains it. This contextual evaluation - // allows for bidirectional type evaluation. - function evaluateTypesForExpressionInContext(node: ExpressionNode): void { - // Check for a couple of special cases where the node is a NameNode but - // is technically not part of an expression. We'll handle these here so - // callers don't need to include special-case logic. - if (node.nodeType === ParseNodeType.Name && node.parent) { - if (node.parent.nodeType === ParseNodeType.Function && node.parent.d.name === node) { - getTypeOfFunction(node.parent); - return; - } - - if (node.parent.nodeType === ParseNodeType.Class && node.parent.d.name === node) { - getTypeOfClass(node.parent); - return; - } - - if (node.parent.nodeType === ParseNodeType.ImportFromAs) { - evaluateTypesForImportFromAs(node.parent); - return; - } - - if (node.parent.nodeType === ParseNodeType.ImportAs) { - evaluateTypesForImportAs(node.parent); - return; - } - - if (node.parent.nodeType === ParseNodeType.TypeAlias && node.parent.d.name === node) { - getTypeOfTypeAlias(node.parent); - return; - } - - if (node.parent.nodeType === ParseNodeType.Global || node.parent.nodeType === ParseNodeType.Nonlocal) { - // For global and nonlocal statements, allow forward references so - // we don't use code flow during symbol lookups. - getTypeOfExpression(node, EvalFlags.ForwardRefs); - return; - } - - if (node.parent.nodeType === ParseNodeType.ModuleName) { - // A name within a module name isn't an expression, - // so there's nothing we can evaluate here. - return; - } - } - - // If the expression is part of a type annotation, we need to evaluate - // it with special evaluation flags. - const annotationNode = ParseTreeUtils.getParentAnnotationNode(node); - if (annotationNode) { - // Annotations need to be evaluated with specialized evaluation flags. - const annotationParent = annotationNode.parent; - assert(annotationParent !== undefined); - - if (annotationParent.nodeType === ParseNodeType.Assignment) { - if (annotationNode === annotationParent.d.annotationComment) { - getTypeOfAnnotation(annotationNode, { - varTypeAnnotation: true, - allowFinal: isFinalAllowedForAssignmentTarget(annotationParent.d.leftExpr), - allowClassVar: isClassVarAllowedForAssignmentTarget(annotationParent.d.leftExpr), - }); - } else { - evaluateTypesForAssignmentStatement(annotationParent); - } - return; - } - - if (annotationParent.nodeType === ParseNodeType.TypeAnnotation) { - evaluateTypesForTypeAnnotationNode(annotationParent); - return; - } - - if ( - annotationParent.nodeType === ParseNodeType.Function && - annotationNode === annotationParent.d.returnAnnotation - ) { - getTypeOfAnnotation(annotationNode, { - typeVarGetsCurScope: true, - }); - return; - } - - getTypeOfAnnotation(annotationNode, { - varTypeAnnotation: annotationNode.parent?.nodeType === ParseNodeType.TypeAnnotation, - allowUnpackedTuple: - annotationParent.nodeType === ParseNodeType.Parameter && - annotationParent.d.category === ParamCategory.ArgsList, - allowUnpackedTypedDict: - annotationParent.nodeType === ParseNodeType.Parameter && - annotationParent.d.category === ParamCategory.KwargsDict, - }); - return; - } - - // See if the expression is part of a pattern used in a case statement. - const possibleCaseNode = ParseTreeUtils.getParentNodeOfType(node, ParseNodeType.Case); - if (possibleCaseNode) { - if (ParseTreeUtils.isNodeContainedWithin(node, possibleCaseNode.d.pattern)) { - evaluateTypesForCaseStatement(possibleCaseNode); - return; - } - } - - // Scan up the parse tree until we find a node that doesn't - // require any context to be evaluated. - let nodeToEvaluate: ExpressionNode = node; - let flags = EvalFlags.None; - - while (true) { - // If we're within an argument node in a call or index expression, skip - // all of the nodes between because the entire argument expression - // needs to be evaluated contextually. - const argumentNode = ParseTreeUtils.getParentNodeOfType(nodeToEvaluate, ParseNodeType.Argument); - if (argumentNode && argumentNode !== nodeToEvaluate) { - assert(argumentNode.parent !== undefined); - - if ( - argumentNode.parent.nodeType === ParseNodeType.Call || - argumentNode.parent.nodeType === ParseNodeType.Index - ) { - nodeToEvaluate = argumentNode.parent; - continue; - } - - if (argumentNode.parent.nodeType === ParseNodeType.Class) { - // If this is an argument node within a class declaration, - // evaluate the full class declaration node. - getTypeOfClass(argumentNode.parent); - return; - } - } - - let parent = nodeToEvaluate.parent; - if (!parent) { - break; - } - - // If this is the target of an assignment expression, evaluate the - // assignment expression node instead. - if (parent.nodeType === ParseNodeType.AssignmentExpression && nodeToEvaluate === parent.d.name) { - nodeToEvaluate = parent; - continue; - } - - // Forward-declared type annotation expressions need to be be evaluated - // in context so they have the appropriate flags set. Most of these cases - // will have been detected above when calling getParentAnnotationNode, - // but TypeAlias expressions are not handled there. - const stringEnclosure = ParseTreeUtils.getParentNodeOfType(parent, ParseNodeType.StringList); - if (stringEnclosure) { - nodeToEvaluate = stringEnclosure as StringListNode; - continue; - } - - // The left expression of a call or member access expression is not generally contextual. - if (parent.nodeType === ParseNodeType.Call || parent.nodeType === ParseNodeType.MemberAccess) { - if (nodeToEvaluate === parent.d.leftExpr) { - // Handle the special case where the LHS is a call to super(). - if ( - nodeToEvaluate.nodeType === ParseNodeType.Call && - nodeToEvaluate.d.leftExpr.nodeType === ParseNodeType.Name && - nodeToEvaluate.d.leftExpr.d.value === 'super' - ) { - nodeToEvaluate = parent; - continue; - } - - // Handle the special case where the LHS is a call to a lambda. - if (parent.nodeType === ParseNodeType.Call && nodeToEvaluate.nodeType === ParseNodeType.Lambda) { - nodeToEvaluate = parent; - continue; - } - - flags = EvalFlags.CallBaseDefaults; - break; - } - } else if (parent.nodeType === ParseNodeType.Index) { - // The base expression of an index expression is not contextual. - if (nodeToEvaluate === parent.d.leftExpr) { - flags = EvalFlags.IndexBaseDefaults; - } - } - - if (!isExpressionNode(parent)) { - // If we've hit a non-expression node, we generally want to - // stop. However, there are a few special "pass through" - // node types that we can skip over to get to a known - // expression node. - if ( - parent.nodeType === ParseNodeType.DictionaryKeyEntry || - parent.nodeType === ParseNodeType.DictionaryExpandEntry || - parent.nodeType === ParseNodeType.ComprehensionFor || - parent.nodeType === ParseNodeType.ComprehensionIf - ) { - assert(parent.parent !== undefined && isExpressionNode(parent.parent)); - parent = parent.parent; - } else if (parent.nodeType === ParseNodeType.Parameter) { - assert(parent.parent !== undefined); - - // Parameters are contextual for lambdas. - if (parent.parent.nodeType === ParseNodeType.Lambda) { - parent = parent.parent; - } else { - break; - } - } else if (parent.nodeType === ParseNodeType.TypeParameter) { - // If this is a bound or default expression in a type parameter list, - // we need to evaluate it in the context of the type parameter. - if (node === parent.d.boundExpr || node === parent.d.defaultExpr) { - getTypeOfTypeParam(parent); - return; - } - - break; - } else { - break; - } - } - - nodeToEvaluate = parent; - } - - const parent = nodeToEvaluate.parent!; - assert(parent !== undefined); - - switch (parent.nodeType) { - case ParseNodeType.Del: { - verifyDeleteExpression(nodeToEvaluate); - return; - } - - case ParseNodeType.TypeParameter: { - // If this is the name node within a type parameter list, see if it's a type alias - // definition. If so, we need to evaluate the type alias contextually. - if ( - nodeToEvaluate === parent.d.name && - parent.parent?.nodeType === ParseNodeType.TypeParameterList && - parent.parent.parent?.nodeType === ParseNodeType.TypeAlias - ) { - getTypeOfTypeAlias(parent.parent.parent); - return; - } - break; - } - - case ParseNodeType.TypeAlias: { - getTypeOfTypeAlias(parent); - return; - } - - case ParseNodeType.Decorator: { - if (parent.parent?.nodeType === ParseNodeType.Class) { - getTypeOfClass(parent.parent); - } else if (parent.parent?.nodeType === ParseNodeType.Function) { - getTypeOfFunction(parent.parent); - } - return; - } - - case ParseNodeType.Parameter: { - if (nodeToEvaluate !== parent.d.defaultValue) { - evaluateTypeOfParam(parent); - return; - } - break; - } - - case ParseNodeType.Argument: { - if (nodeToEvaluate === parent.d.name) { - // A name used to specify a named parameter in an argument isn't an - // expression, so there's nothing we can evaluate here. - return; - } - - if (parent.parent?.nodeType === ParseNodeType.Class) { - // A class argument must be evaluated in the context of the class declaration. - getTypeOfClass(parent.parent); - return; - } - break; - } - - case ParseNodeType.Return: { - // Return expressions must be evaluated in the context of the expected return type. - if (parent.d.expr) { - const enclosingFunctionNode = ParseTreeUtils.getEnclosingFunction(node); - let declaredReturnType = enclosingFunctionNode - ? getDeclaredReturnType(enclosingFunctionNode) - : undefined; - if (declaredReturnType) { - const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(node); - declaredReturnType = makeTypeVarsBound(declaredReturnType, liveScopeIds); - } - getTypeOfExpression(parent.d.expr, EvalFlags.None, makeInferenceContext(declaredReturnType)); - return; - } - break; - } - - case ParseNodeType.TypeAnnotation: { - evaluateTypesForTypeAnnotationNode(parent); - return; - } - - case ParseNodeType.Assignment: { - evaluateTypesForAssignmentStatement(parent); - return; - } - - case ParseNodeType.AugmentedAssignment: { - evaluateTypesForAugmentedAssignment(parent); - return; - } - } - - if (nodeToEvaluate.nodeType === ParseNodeType.TypeAnnotation) { - evaluateTypesForTypeAnnotationNode(nodeToEvaluate); - return; - } - - getTypeOfExpression(nodeToEvaluate, flags); - } - - function evaluateTypeOfParam(node: ParameterNode): void { - // If this parameter has no name, we have nothing to do. - if (!node.d.name) { - return; - } - - // We need to handle lambdas differently from functions because - // the former never have parameter type annotations but can - // be inferred, whereas the latter sometimes have type annotations - // but cannot be inferred. - const parent = node.parent!; - if (parent.nodeType === ParseNodeType.Lambda) { - evaluateTypesForExpressionInContext(parent); - return; - } - - assert(parent.nodeType === ParseNodeType.Function); - const functionNode = parent as FunctionNode; - - const paramIndex = functionNode.d.params.findIndex((param) => param === node); - const typeAnnotation = ParseTreeUtils.getTypeAnnotationForParam(functionNode, paramIndex); - - if (typeAnnotation) { - const param = functionNode.d.params[paramIndex]; - let annotatedType = getTypeOfParamAnnotation(typeAnnotation, functionNode.d.params[paramIndex].d.category); - - const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(param); - annotatedType = makeTypeVarsBound(annotatedType, liveTypeVarScopes); - - const adjType = transformVariadicParamType( - node, - node.d.category, - adjustParamAnnotatedType(param, annotatedType) - ); - - writeTypeCache(node.d.name, { type: adjType }, EvalFlags.None); - return; - } - - const containingClassNode = ParseTreeUtils.getEnclosingClass(functionNode, /* stopAtFunction */ true); - const classInfo = containingClassNode ? getTypeOfClass(containingClassNode) : undefined; - - if ( - classInfo && - ClassType.isPseudoGenericClass(classInfo?.classType) && - functionNode.d.name.d.value === '__init__' - ) { - const typeParamName = getPseudoGenericTypeVarName(node.d.name.d.value); - const paramType = classInfo.classType.shared.typeParams.find( - (param) => param.shared.name === typeParamName - ); - - if (paramType) { - writeTypeCache(node.d.name, { type: TypeVarType.cloneAsBound(paramType) }, EvalFlags.None); - return; - } - } - - // See if the function is a method in a child class. We may be able to - // infer the type of the parameter from a method of the same name in - // a parent class if it has an annotated type. - const functionFlags = getFunctionInfoFromDecorators( - evaluatorInterface, - functionNode, - /* isInClass */ true - ).flags; - - let inferredParamType = - inferParamType(functionNode, functionFlags, paramIndex, classInfo?.classType) ?? UnknownType.create(); - const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(node); - inferredParamType = makeTypeVarsBound(inferredParamType, liveTypeVarScopes); - - writeTypeCache( - node.d.name, - { type: transformVariadicParamType(node, node.d.category, inferredParamType) }, - EvalFlags.None - ); - } - - // Evaluates the types that are assigned within the statement that contains - // the specified parse node. In some cases, a broader statement may need to - // be evaluated to provide sufficient context for the type. Evaluated types - // are written back to the type cache for later retrieval. - function evaluateTypesForStatement(node: ParseNode): void { - initializePrefetchedTypes(node); - - let curNode: ParseNode | undefined = node; - - while (curNode) { - switch (curNode.nodeType) { - case ParseNodeType.Assignment: { - // See if the assignment is part of a chain of assignments. If so, - // evaluate the entire chain. - const isInAssignmentChain = - curNode.parent && - (curNode.parent.nodeType === ParseNodeType.Assignment || - curNode.parent.nodeType === ParseNodeType.AssignmentExpression || - curNode.parent.nodeType === ParseNodeType.AugmentedAssignment) && - curNode.parent.d.rightExpr === curNode; - if (!isInAssignmentChain) { - evaluateTypesForAssignmentStatement(curNode); - return; - } - break; - } - - case ParseNodeType.TypeAlias: { - getTypeOfTypeAlias(curNode); - return; - } - - case ParseNodeType.AssignmentExpression: { - evaluateTypesForExpressionInContext(curNode); - return; - } - - case ParseNodeType.AugmentedAssignment: { - evaluateTypesForAugmentedAssignment(curNode); - return; - } - - case ParseNodeType.Class: { - getTypeOfClass(curNode); - return; - } - - case ParseNodeType.Parameter: { - evaluateTypeOfParam(curNode); - return; - } - - case ParseNodeType.Lambda: { - evaluateTypesForExpressionInContext(curNode); - return; - } - - case ParseNodeType.Function: { - getTypeOfFunction(curNode); - return; - } - - case ParseNodeType.For: { - evaluateTypesForForStatement(curNode); - return; - } - - case ParseNodeType.Except: { - evaluateTypesForExceptStatement(curNode); - return; - } - - case ParseNodeType.WithItem: { - evaluateTypesForWithStatement(curNode); - return; - } - - case ParseNodeType.ComprehensionFor: { - const comprehension = curNode.parent as ComprehensionNode; - assert(comprehension.nodeType === ParseNodeType.Comprehension); - if (curNode === comprehension.d.expr) { - evaluateTypesForExpressionInContext(comprehension); - } else { - // Evaluate the individual iterations starting with the first - // up to the curNode. - for (const forIfNode of comprehension.d.forIfNodes) { - evaluateComprehensionForIf(forIfNode); - if (forIfNode === curNode) { - break; - } - } - } - return; - } - - case ParseNodeType.ImportAs: { - evaluateTypesForImportAs(curNode); - return; - } - - case ParseNodeType.ImportFromAs: { - evaluateTypesForImportFromAs(curNode); - return; - } - - case ParseNodeType.ImportFrom: { - evaluateTypesForImportFrom(curNode); - return; - } - - case ParseNodeType.Case: { - evaluateTypesForCaseStatement(curNode); - return; - } - } - - curNode = curNode.parent; - } - - fail('Unexpected statement'); - return undefined; - } - - // Helper function for cases where we need to evaluate the types - // for a subtree so we can determine the type of one of the subnodes - // within that tree. If the type cannot be determined (because it's part - // of a cyclical dependency), the function returns undefined. - function evaluateTypeForSubnode(subnode: ParseNode, callback: () => void): TypeResult | undefined { - // If the type cache is already populated with a complete type, - // don't bother doing additional work. - let cacheEntry = readTypeCacheEntry(subnode); - if (cacheEntry && !cacheEntry.typeResult.isIncomplete) { - const typeResult = cacheEntry.typeResult; - - // Handle the special case where a function or class is partially evaluated. - // Indicate that these are not complete types. - if (isFunction(typeResult.type) && FunctionType.isPartiallyEvaluated(typeResult.type)) { - return { ...typeResult, isIncomplete: true }; - } - - if (isClass(typeResult.type) && ClassType.isPartiallyEvaluated(typeResult.type)) { - return { ...typeResult, isIncomplete: true }; - } - - return typeResult; - } - - callback(); - cacheEntry = readTypeCacheEntry(subnode); - if (cacheEntry) { - return cacheEntry.typeResult; - } - - return undefined; - } - - function getCodeFlowAnalyzerForNode( - node: ExecutionScopeNode, - typeAtStart: TypeResult | undefined - ): CodeFlowAnalyzer { - let entries = codeFlowAnalyzerCache.get(node.id); - - if (entries) { - const cachedEntry = entries.find((entry) => { - if (!typeAtStart || !entry.typeAtStart) { - return !typeAtStart && !entry.typeAtStart; - } - - if (!typeAtStart.isIncomplete !== !entry.typeAtStart.isIncomplete) { - return false; - } - - return isTypeSame(typeAtStart.type, entry.typeAtStart.type); - }); - - if (cachedEntry) { - return cachedEntry.codeFlowAnalyzer; - } - } - - // Allocate a new code flow analyzer. - const analyzer = codeFlowEngine.createCodeFlowAnalyzer(); - if (entries) { - entries.push({ typeAtStart, codeFlowAnalyzer: analyzer }); - } else { - entries = [{ typeAtStart, codeFlowAnalyzer: analyzer }]; - codeFlowAnalyzerCache.set(node.id, entries); - } - - return analyzer; - } - - // Attempts to determine the type of the reference expression at the - // point in the code. If the code flow analysis has nothing to say - // about that expression, it returns un undefined type. Normally - // flow analysis starts from the reference node, but startNode can be - // specified to override this in a few special cases (functions and - // lambdas) to support analysis of captured variables. - function getFlowTypeOfReference( - reference: CodeFlowReferenceExpressionNode, - startNode?: ClassNode | FunctionNode | LambdaNode, - options?: FlowNodeTypeOptions - ): FlowNodeTypeResult { - // See if this execution scope requires code flow for this reference expression. - const referenceKey = createKeyForReference(reference); - const executionNode = ParseTreeUtils.getExecutionScopeNode(startNode?.parent ?? reference); - const codeFlowExpressions = AnalyzerNodeInfo.getCodeFlowExpressions(executionNode); - - if ( - !codeFlowExpressions || - (!codeFlowExpressions.has(referenceKey) && !codeFlowExpressions.has(wildcardImportReferenceKey)) - ) { - return FlowNodeTypeResult.create(/* type */ undefined, /* isIncomplete */ false); - } - - if (checkCodeFlowTooComplex(reference)) { - return FlowNodeTypeResult.create( - /* type */ options?.typeAtStart && isUnbound(options.typeAtStart.type) - ? UnknownType.create() - : undefined, - /* isIncomplete */ true - ); - } - - // Is there an code flow analyzer cached for this execution scope? - let analyzer: CodeFlowAnalyzer | undefined; - - if (isNodeInReturnTypeInferenceContext(executionNode)) { - // If we're performing the analysis within a temporary - // context of a function for purposes of inferring its - // return type for a specified set of arguments, use - // a temporary analyzer that we'll use only for this context. - analyzer = getCodeFlowAnalyzerForReturnTypeInferenceContext(); - } else { - analyzer = getCodeFlowAnalyzerForNode(executionNode, options?.typeAtStart); - } - - const flowNode = AnalyzerNodeInfo.getFlowNode(startNode ?? reference); - if (flowNode === undefined) { - return FlowNodeTypeResult.create(/* type */ undefined, /* isIncomplete */ false); - } - - return analyzer.getTypeFromCodeFlow(flowNode!, reference, options); - } - - // Specializes the specified (potentially generic) class type using - // the specified type arguments, reporting errors as appropriate. - // Returns the specialized type and a boolean indicating whether - // the type indicates a class type (true) or an object type (false). - function createSpecializedClassType( - classType: ClassType, - typeArgs: TypeResultWithNode[] | undefined, - flags: EvalFlags, - errorNode: ExpressionNode - ): TypeResult { - let isValidTypeForm = true; - - // Handle the special-case classes that are not defined - // in the type stubs. - if (ClassType.isSpecialBuiltIn(classType)) { - const aliasedName = classType.priv.aliasName || classType.shared.name; - switch (aliasedName) { - case 'Callable': { - return { type: createCallableType(classType, typeArgs, errorNode) }; - } - - case 'Never': - case 'NoReturn': { - if (typeArgs && typeArgs.length > 0) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.typeArgsExpectingNone().format({ name: aliasedName }), - typeArgs[0].node - ); - } - - let resultType = aliasedName === 'Never' ? NeverType.createNever() : NeverType.createNoReturn(); - resultType = TypeBase.cloneAsSpecialForm(resultType, classType); - resultType = TypeBase.cloneWithTypeForm(resultType, convertToInstance(resultType)); - - return { type: resultType }; - } - - case 'Optional': { - return { type: createOptionalType(classType, errorNode, typeArgs, flags) }; - } - - case 'Type': { - let typeType = createSpecialType( - classType, - typeArgs, - 1, - /* allowParamSpec */ undefined, - /* isSpecialForm */ false - ); - - if (isInstantiableClass(typeType)) { - typeType = explodeGenericClass(typeType); - } - - typeType = TypeBase.cloneWithTypeForm(typeType, convertToInstance(typeType)); - - return { type: typeType }; - } - - case 'ClassVar': { - return { type: createClassVarType(classType, errorNode, typeArgs, flags) }; - } - - case 'Protocol': { - if ((flags & (EvalFlags.NoNonTypeSpecialForms | EvalFlags.TypeExpression)) !== 0) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.protocolNotAllowed(), errorNode); - } - - typeArgs?.forEach((typeArg) => { - if (typeArg.typeList || !isTypeVar(typeArg.type)) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.protocolTypeArgMustBeTypeParam(), - typeArg.node - ); - } - }); - - return { - type: createSpecialType( - classType, - typeArgs, - /* paramLimit */ undefined, - /* allowParamSpec */ true - ), - }; - } - - case 'TypedDict': { - if ((flags & (EvalFlags.NoNonTypeSpecialForms | EvalFlags.TypeExpression)) !== 0) { - const isInlinedTypedDict = - AnalyzerNodeInfo.getFileInfo(errorNode).diagnosticRuleSet.enableExperimentalFeatures && - !!typeArgs; - - if (!isInlinedTypedDict) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.typedDictNotAllowed(), - errorNode - ); - } - } - isValidTypeForm = false; - break; - } - - case 'Literal': { - if ((flags & (EvalFlags.NoNonTypeSpecialForms | EvalFlags.TypeExpression)) !== 0) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.literalNotAllowed(), errorNode); - } - isValidTypeForm = false; - break; - } - - case 'Tuple': { - return { - type: createSpecialType( - classType, - typeArgs, - /* paramLimit */ undefined, - /* allowParamSpec */ false, - /* isSpecialForm */ false - ), - }; - } - - case 'Union': { - return { type: createUnionType(classType, errorNode, typeArgs, flags) }; - } - - case 'Generic': { - return { type: createGenericType(classType, errorNode, typeArgs, flags) }; - } - - case 'Final': { - return { type: createFinalType(classType, errorNode, typeArgs, flags) }; - } - - case 'Annotated': { - return createAnnotatedType(classType, errorNode, typeArgs, flags); - } - - case 'Concatenate': { - return { type: createConcatenateType(classType, errorNode, typeArgs, flags) }; - } - - case 'TypeGuard': - case 'TypeIs': { - return { type: createTypeGuardType(classType, errorNode, typeArgs, flags) }; - } - - case 'Unpack': { - return { type: createUnpackType(classType, errorNode, typeArgs, flags) }; - } - - case 'Required': - case 'NotRequired': { - return createRequiredOrReadOnlyType(classType, errorNode, typeArgs, flags); - } - - case 'ReadOnly': { - return createRequiredOrReadOnlyType(classType, errorNode, typeArgs, flags); - } - - case 'Self': { - return { type: createSelfType(classType, errorNode, typeArgs, flags) }; - } - - case 'LiteralString': { - return { type: createSpecialType(classType, typeArgs, 0) }; - } - - case 'TypeForm': { - return { type: createTypeFormType(classType, errorNode, typeArgs) }; - } - } - } - - const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode); - if ( - fileInfo.isStubFile || - PythonVersion.isGreaterOrEqualTo(fileInfo.executionEnvironment.pythonVersion, pythonVersion3_9) || - isAnnotationEvaluationPostponed(AnalyzerNodeInfo.getFileInfo(errorNode)) || - (flags & EvalFlags.ForwardRefs) !== 0 - ) { - // Handle "type" specially, since it needs to act like "Type" - // in Python 3.9 and newer. - if (ClassType.isBuiltIn(classType, 'type') && typeArgs) { - if (typeArgs.length >= 1) { - // Treat type[function] as illegal. - if (isFunctionOrOverloaded(typeArgs[0].type)) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.typeAnnotationWithCallable(), - typeArgs[0].node - ); - - return { type: UnknownType.create() }; - } - } - - if (prefetched?.typeClass && isInstantiableClass(prefetched.typeClass)) { - let typeType = createSpecialType( - prefetched.typeClass, - typeArgs, - 1, - /* allowParamSpec */ undefined, - /* isSpecialForm */ false - ); - - if (isInstantiableClass(typeType)) { - typeType = explodeGenericClass(typeType); - } - - typeType = TypeBase.cloneWithTypeForm(typeType, convertToInstance(typeType)); - - return { type: typeType }; - } - } - - // Handle "tuple" specially, since it needs to act like "Tuple" - // in Python 3.9 and newer. - if (isTupleClass(classType)) { - let specializedClass = createSpecialType( - classType, - typeArgs, - /* paramLimit */ undefined, - /* allowParamSpec */ undefined, - /* isSpecialForm */ false - ); - - specializedClass = TypeBase.cloneWithTypeForm(specializedClass, convertToInstance(specializedClass)); - - return { type: specializedClass }; - } - } - - let typeArgCount = typeArgs ? typeArgs.length : 0; - - // Make sure the argument list count is correct. - const typeParams = ClassType.isPseudoGenericClass(classType) ? [] : ClassType.getTypeParams(classType); - - // If there are no type parameters or args, the class is already specialized. - // No need to do any more work. - if (typeParams.length === 0 && typeArgCount === 0) { - return { type: classType }; - } - - const variadicTypeParamIndex = typeParams.findIndex((param) => isTypeVarTuple(param)); - - if (typeArgs) { - let minTypeArgCount = typeParams.length; - const firstDefaultParamIndex = typeParams.findIndex((param) => !!param.shared.isDefaultExplicit); - - if (firstDefaultParamIndex >= 0) { - minTypeArgCount = firstDefaultParamIndex; - } - - // Classes that accept inlined type dict type args allow only one. - if (typeArgs.length > 0 && typeArgs[0].inlinedTypeDict) { - if (typeArgs.length > 1) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeArguments, - LocMessage.typeArgsTooMany().format({ - name: classType.priv.aliasName || classType.shared.name, - expected: 1, - received: typeArgCount, - }), - typeArgs[1].node - ); - } - - return { type: typeArgs[0].inlinedTypeDict }; - } else if (typeArgCount > typeParams.length) { - if (!ClassType.isPartiallyEvaluated(classType) && !ClassType.isTupleClass(classType)) { - if (typeParams.length === 0) { - isValidTypeForm = false; - addDiagnostic( - DiagnosticRule.reportInvalidTypeArguments, - LocMessage.typeArgsExpectingNone().format({ - name: classType.priv.aliasName || classType.shared.name, - }), - typeArgs[typeParams.length].node - ); - } else if (typeParams.length !== 1 || !isParamSpec(typeParams[0])) { - isValidTypeForm = false; - addDiagnostic( - DiagnosticRule.reportInvalidTypeArguments, - LocMessage.typeArgsTooMany().format({ - name: classType.priv.aliasName || classType.shared.name, - expected: typeParams.length, - received: typeArgCount, - }), - typeArgs[typeParams.length].node - ); - } - - typeArgCount = typeParams.length; - } - } else if (typeArgCount < minTypeArgCount) { - isValidTypeForm = false; - addDiagnostic( - DiagnosticRule.reportInvalidTypeArguments, - LocMessage.typeArgsTooFew().format({ - name: classType.priv.aliasName || classType.shared.name, - expected: minTypeArgCount, - received: typeArgCount, - }), - typeArgs.length > 0 ? typeArgs[0].node.parent! : errorNode - ); - } - - typeArgs.forEach((typeArg, index) => { - if (!typeArg.type.props?.typeForm) { - isValidTypeForm = false; - } - - if (index === variadicTypeParamIndex) { - // The types that make up the tuple that maps to the - // TypeVarTuple have already been validated when the tuple - // object was created in adjustTypeArgsForTypeVarTuple. - if (isClassInstance(typeArg.type) && isTupleClass(typeArg.type)) { - return; - } - - if (isTypeVarTuple(typeArg.type)) { - if (!validateTypeVarTupleIsUnpacked(typeArg.type, typeArg.node)) { - isValidTypeForm = false; - } - return; - } - } - - const typeParam = index < typeParams.length ? typeParams[index] : undefined; - const isParamSpecTarget = typeParam && isParamSpec(typeParam); - - if ( - !validateTypeArg(typeArg, { - allowParamSpec: true, - allowTypeArgList: isParamSpecTarget, - }) - ) { - isValidTypeForm = false; - } - }); - } - - // Handle ParamSpec arguments and fill in any missing type arguments with Unknown. - let typeArgTypes: Type[] = []; - const fullTypeParams = ClassType.getTypeParams(classType); - - typeArgs = transformTypeArgsForParamSpec(fullTypeParams, typeArgs, errorNode); - if (!typeArgs) { - isValidTypeForm = false; - } - - const constraints = new ConstraintTracker(); - - fullTypeParams.forEach((typeParam, index) => { - if (typeArgs && index < typeArgs.length) { - if (isParamSpec(typeParam)) { - const typeArg = typeArgs[index]; - const functionType = FunctionType.createSynthesizedInstance('', FunctionTypeFlags.ParamSpecValue); - - if (isEllipsisType(typeArg.type)) { - FunctionType.addDefaultParams(functionType); - functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm; - typeArgTypes.push(functionType); - constraints.setBounds(typeParam, functionType); - return; - } - - if (typeArg.typeList) { - typeArg.typeList!.forEach((paramType, paramIndex) => { - FunctionType.addParam( - functionType, - FunctionParam.create( - ParamCategory.Simple, - convertToInstance(paramType.type), - FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared, - `__p${paramIndex}` - ) - ); - }); - - if (typeArg.typeList.length > 0) { - FunctionType.addPositionOnlyParamSeparator(functionType); - } - - typeArgTypes.push(functionType); - constraints.setBounds(typeParam, functionType); - return; - } - - if (isInstantiableClass(typeArg.type) && ClassType.isBuiltIn(typeArg.type, 'Concatenate')) { - const concatTypeArgs = typeArg.type.priv.typeArgs; - if (concatTypeArgs && concatTypeArgs.length > 0) { - concatTypeArgs.forEach((typeArg, index) => { - if (index === concatTypeArgs.length - 1) { - if (isParamSpec(typeArg)) { - FunctionType.addParamSpecVariadics(functionType, typeArg); - } else if (isEllipsisType(typeArg)) { - FunctionType.addDefaultParams(functionType); - functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm; - } - } else { - FunctionType.addParam( - functionType, - FunctionParam.create( - ParamCategory.Simple, - typeArg, - FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared, - `__p${index}` - ) - ); - } - }); - } - - typeArgTypes.push(functionType); - return; - } - } - - const typeArgType = convertToInstance(typeArgs[index].type); - typeArgTypes.push(typeArgType); - constraints.setBounds(typeParam, typeArgType); - return; - } - - const solvedDefaultType = solveAndApplyConstraints(typeParam, constraints, { - replaceUnsolved: { - scopeIds: getTypeVarScopeIds(classType), - tupleClassType: getTupleClassType(), - }, - }); - typeArgTypes.push(solvedDefaultType); - constraints.setBounds(typeParam, solvedDefaultType); - }); - - typeArgTypes = typeArgTypes.map((typeArgType, index) => { - if (index < typeArgCount) { - const diag = new DiagnosticAddendum(); - let adjustedTypeArgType = applyTypeArgToTypeVar(typeParams[index], typeArgType, diag); - - // Determine if the variance must match. - if (adjustedTypeArgType && (flags & EvalFlags.EnforceVarianceConsistency) !== 0) { - const destType = typeParams[index]; - const declaredVariance = destType.shared.declaredVariance; - - if (!isVarianceOfTypeArgCompatible(adjustedTypeArgType, declaredVariance)) { - diag.addMessage( - LocAddendum.varianceMismatchForClass().format({ - typeVarName: printType(adjustedTypeArgType), - className: classType.shared.name, - }) - ); - adjustedTypeArgType = undefined; - } - } - - if (adjustedTypeArgType) { - typeArgType = adjustedTypeArgType; - } else { - // Avoid emitting this error for a partially-constructed class. - if (!isClassInstance(typeArgType) || !ClassType.isPartiallyEvaluated(typeArgType)) { - assert(typeArgs !== undefined); - isValidTypeForm = false; - addDiagnostic( - DiagnosticRule.reportInvalidTypeArguments, - LocMessage.typeVarAssignmentMismatch().format({ - type: printType(typeArgType), - name: TypeVarType.getReadableName(typeParams[index]), - }) + diag.getString(), - typeArgs[index].node - ); - } - } - } - - return typeArgType; - }); - - // If the class is partially constructed and doesn't yet have - // type parameters, assume that the number and types of supplied type - // arguments are correct. - if (typeArgs && classType.shared.typeParams.length === 0 && ClassType.isPartiallyEvaluated(classType)) { - typeArgTypes = typeArgs.map((t) => convertToInstance(t.type)); - } - - let specializedClass = ClassType.specialize(classType, typeArgTypes, typeArgs !== undefined); - - specializedClass = TypeBase.cloneWithTypeForm( - specializedClass, - isValidTypeForm ? convertToInstance(specializedClass) : undefined - ); - - return { type: specializedClass }; - } - - // PEP 612 says that if the class has only one type parameter consisting - // of a ParamSpec, the list of arguments does not need to be enclosed in - // a list. We'll handle that case specially here. - function transformTypeArgsForParamSpec( - typeParams: TypeVarType[], - typeArgs: TypeResultWithNode[] | undefined, - errorNode: ExpressionNode - ): TypeResultWithNode[] | undefined { - if (typeParams.length !== 1 || !isParamSpec(typeParams[0]) || !typeArgs) { - return typeArgs; - } - - if (typeArgs.length > 1) { - for (const typeArg of typeArgs) { - if (isParamSpec(typeArg.type)) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.paramSpecContext(), typeArg.node); - return undefined; - } - - if (isEllipsisType(typeArg.type)) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.ellipsisContext(), typeArg.node); - return undefined; - } - - if (isInstantiableClass(typeArg.type) && ClassType.isBuiltIn(typeArg.type, 'Concatenate')) { - addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.concatenateContext(), typeArg.node); - return undefined; - } - - if (typeArg.typeList) { - addDiagnostic( - DiagnosticRule.reportInvalidTypeForm, - LocMessage.typeArgListNotAllowed(), - typeArg.node - ); - return undefined; - } - } - } - - if (typeArgs.length === 1) { - // Don't transform a type list. - if (typeArgs[0].typeList) { - return typeArgs; - } - - const typeArgType = typeArgs[0].type; - - // Don't transform a single ParamSpec or ellipsis. - if (isParamSpec(typeArgType) || isEllipsisType(typeArgType)) { - return typeArgs; - } - - // Don't transform a Concatenate. - if (isInstantiableClass(typeArgType) && ClassType.isBuiltIn(typeArgType, 'Concatenate')) { - return typeArgs; - } - } - - // Package up the type arguments into a type list. - return [ - { - type: UnknownType.create(), - node: typeArgs.length > 0 ? typeArgs[0].node : errorNode, - typeList: typeArgs, - }, - ]; - } - - function getTypeOfArg(arg: Arg, inferenceContext: InferenceContext | undefined): TypeResult { - if (arg.typeResult) { - const type = arg.typeResult.type; - return { type: type?.props?.specialForm ?? type, isIncomplete: arg.typeResult.isIncomplete }; - } - - if (!arg.valueExpression) { - // We shouldn't ever get here, but just in case. - return { type: UnknownType.create() }; - } - - // If there was no defined type provided, there should always - // be a value expression from which we can retrieve the type. - return getTypeOfExpression(arg.valueExpression, /* flags */ undefined, inferenceContext); - } - - // This function is like getTypeOfArg except that it is - // used in cases where the argument is expected to be a type - // and therefore follows the normal rules of types (e.g. they - // can be forward-declared in stubs, etc.). - function getTypeOfArgExpectingType(arg: Arg, options?: ExpectedTypeOptions): TypeResult { - if (arg.typeResult) { - return { type: arg.typeResult.type, isIncomplete: arg.typeResult.isIncomplete }; - } - - // If there was no defined type provided, there should always - // be a value expression from which we can retrieve the type. - assert(arg.valueExpression !== undefined); - return getTypeOfExpressionExpectingType(arg.valueExpression, options); - } - - function getTypeOfExpressionExpectingType(node: ExpressionNode, options?: ExpectedTypeOptions): TypeResult { - let flags = EvalFlags.InstantiableType | EvalFlags.StrLiteralAsType; - - if (options?.allowTypeVarsWithoutScopeId) { - flags |= EvalFlags.AllowTypeVarWithoutScopeId; - } - - if (options?.typeVarGetsCurScope) { - flags |= EvalFlags.TypeVarGetsCurScope; - } - - if (options?.enforceClassTypeVarScope) { - flags |= EvalFlags.EnforceClassTypeVarScope; - } - - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - if ((isAnnotationEvaluationPostponed(fileInfo) || options?.forwardRefs) && !options?.runtimeTypeExpression) { - flags |= EvalFlags.ForwardRefs; - } else if (options?.parsesStringLiteral) { - flags |= EvalFlags.ParsesStringLiteral; - } - - if (!options?.allowFinal) { - flags |= EvalFlags.NoFinal; - } - - if (options?.allowRequired) { - flags |= EvalFlags.AllowRequired | EvalFlags.TypeExpression; - } - - if (options?.allowReadOnly) { - flags |= EvalFlags.AllowReadOnly | EvalFlags.TypeExpression; - } - - if (options?.allowUnpackedTuple) { - flags |= EvalFlags.AllowUnpackedTuple; - } else { - flags |= EvalFlags.NoTypeVarTuple; - } - - if (options?.allowUnpackedTypedDict) { - flags |= EvalFlags.AllowUnpackedTypedDict; - } - - if (!options?.allowParamSpec) { - flags |= EvalFlags.NoParamSpec; - } - - if (options?.typeExpression) { - flags |= EvalFlags.TypeExpression; - } - - if (options?.convertEllipsisToAny) { - flags |= EvalFlags.ConvertEllipsisToAny; - } - - if (options?.allowEllipsis) { - flags |= EvalFlags.AllowEllipsis; - } - - if (options?.noNonTypeSpecialForms) { - flags |= EvalFlags.NoNonTypeSpecialForms; - } - - if (!options?.allowClassVar) { - flags |= EvalFlags.NoClassVar; - } - - if (options?.varTypeAnnotation) { - flags |= EvalFlags.VarTypeAnnotation; - } - - if (options?.notParsed) { - flags |= EvalFlags.NotParsed; - } - - if (options?.typeFormArg) { - flags |= EvalFlags.TypeFormArg; - } - - return getTypeOfExpression(node, flags); - } - - function getBuiltInType(node: ParseNode, name: string): Type { - const scope = ScopeUtils.getScopeForNode(node); - if (scope) { - const builtInScope = ScopeUtils.getBuiltInScope(scope); - const nameType = builtInScope.lookUpSymbol(name); - if (nameType) { - return getEffectiveTypeOfSymbol(nameType); - } - } - - return UnknownType.create(); - } - - function getBuiltInObject(node: ParseNode, name: string, typeArgs?: Type[]) { - const nameType = getBuiltInType(node, name); - if (isInstantiableClass(nameType)) { - let classType = nameType; - if (typeArgs) { - classType = ClassType.specialize(classType, typeArgs); - } - - return ClassType.cloneAsInstance(classType); - } - - return nameType; - } - - function lookUpSymbolRecursive( - node: ParseNode, - name: string, - honorCodeFlow: boolean, - preferGlobalScope = false - ): SymbolWithScope | undefined { - const scopeNodeInfo = ParseTreeUtils.getEvaluationScopeNode(node); - const scope = AnalyzerNodeInfo.getScope(scopeNodeInfo.node); - - let symbolWithScope = scope?.lookUpSymbolRecursive(name, { - useProxyScope: !!scopeNodeInfo.useProxyScope, - useChainedModuleLevelScopes: !!scopeNodeInfo.useChainedModuleLevelScopes, - }); - - const scopeType = scope?.type ?? ScopeType.Module; - - // Functions and list comprehensions don't allow access to implicitly - // aliased symbols in outer scopes if they haven't yet been assigned - // within the local scope. - let scopeTypeHonorsCodeFlow = scopeType !== ScopeType.Function && scopeType !== ScopeType.Comprehension; - - // Type parameter scopes don't honor code flow. - if (symbolWithScope?.scope.type === ScopeType.TypeParameter) { - scopeTypeHonorsCodeFlow = false; - } - - if (symbolWithScope && honorCodeFlow && scopeTypeHonorsCodeFlow) { - // Filter the declarations based on flow reachability. - const reachableDecl = symbolWithScope.symbol.getDeclarations().find((decl) => { - if (decl.type !== DeclarationType.Alias && decl.type !== DeclarationType.Intrinsic) { - // Determine if the declaration is in the same execution scope as the "usageNode" node. - let usageScopeNode = ParseTreeUtils.getExecutionScopeNode(node); - const declNode: ParseNode = - decl.type === DeclarationType.Class || - decl.type === DeclarationType.Function || - decl.type === DeclarationType.TypeAlias - ? decl.node.d.name - : decl.node; - const declScopeNode = ParseTreeUtils.getExecutionScopeNode(declNode); - - // If this is a type parameter scope, it will be a proxy for its - // containing scope, so we need to use that instead. - const usageScope = AnalyzerNodeInfo.getScope(usageScopeNode); - if (usageScope?.proxy) { - const typeParamScope = AnalyzerNodeInfo.getScope(usageScopeNode); - if (!typeParamScope?.symbolTable.has(name) && usageScopeNode.parent) { - usageScopeNode = ParseTreeUtils.getExecutionScopeNode(usageScopeNode.parent); - } - } - - if (usageScopeNode === declScopeNode) { - if (!isFlowPathBetweenNodes(declNode, node)) { - // If there was no control flow path from the usage back - // to the source, see if the usage node is reachable by - // any path. - const flowNode = AnalyzerNodeInfo.getFlowNode(node); - const isReachable = - flowNode && - codeFlowEngine.getFlowNodeReachability( - flowNode, - /* sourceFlowNode */ undefined, - /* ignoreNoReturn */ true - ) === Reachability.Reachable; - return !isReachable; - } - } - } - return true; - }); - - // If none of the declarations are reachable from the current node, - // search for the symbol in outer scopes. - if (!reachableDecl) { - if (symbolWithScope.scope.type !== ScopeType.Function) { - let nextScopeToSearch = symbolWithScope.scope.parent; - const isOutsideCallerModule = - symbolWithScope.isOutsideCallerModule || symbolWithScope.scope.type === ScopeType.Module; - let isBeyondExecutionScope = - symbolWithScope.isBeyondExecutionScope || symbolWithScope.scope.isIndependentlyExecutable(); - - if (symbolWithScope.scope.type === ScopeType.Class) { - // There is an odd documented behavior for classes in that - // symbol resolution skips to the global scope rather than - // the next scope in the chain. - const globalScopeResult = symbolWithScope.scope.getGlobalScope(); - nextScopeToSearch = globalScopeResult.scope; - if (globalScopeResult.isBeyondExecutionScope) { - isBeyondExecutionScope = true; - } - } - - if (nextScopeToSearch) { - symbolWithScope = nextScopeToSearch.lookUpSymbolRecursive(name, { - isOutsideCallerModule, - isBeyondExecutionScope, - }); - } else { - symbolWithScope = undefined; - } - } else { - symbolWithScope = undefined; - } - } - } - - // PEP 563 indicates that if a forward reference can be resolved in the module - // scope (or, by implication, in the builtins scope), it should prefer that - // resolution over local resolutions. - if (symbolWithScope && preferGlobalScope) { - let curSymbolWithScope: SymbolWithScope | undefined = symbolWithScope; - while ( - curSymbolWithScope.scope.type !== ScopeType.Module && - curSymbolWithScope.scope.type !== ScopeType.Builtin && - curSymbolWithScope.scope.type !== ScopeType.TypeParameter && - curSymbolWithScope.scope.parent - ) { - curSymbolWithScope = curSymbolWithScope.scope.parent.lookUpSymbolRecursive(name, { - isOutsideCallerModule: curSymbolWithScope.isOutsideCallerModule, - isBeyondExecutionScope: - curSymbolWithScope.isBeyondExecutionScope || - curSymbolWithScope.scope.isIndependentlyExecutable(), - }); - if (!curSymbolWithScope) { - break; - } - } - - if ( - curSymbolWithScope?.scope.type === ScopeType.Module || - curSymbolWithScope?.scope.type === ScopeType.Builtin - ) { - symbolWithScope = curSymbolWithScope; - } - } - - return symbolWithScope; - } - - // Disables recording of errors and warnings. - function suppressDiagnostics( - node: ParseNode, - callback: () => T, - diagCallback?: (suppressedDiags: string[]) => void - ) { - suppressedNodeStack.push({ node, suppressedDiags: diagCallback ? [] : undefined }); - - try { - const result = callback(); - const poppedNode = suppressedNodeStack.pop(); - if (diagCallback && poppedNode?.suppressedDiags) { - diagCallback(poppedNode.suppressedDiags); - } - return result; - } catch (e) { - // We don't use finally here because the TypeScript debugger doesn't - // handle finally well when single stepping. - suppressedNodeStack.pop(); - throw e; - } - } - - function getSignatureTrackerForNode(node: ParseNode): UniqueSignatureTracker | undefined { - for (let i = signatureTrackerStack.length - 1; i >= 0; i--) { - const rootNode = signatureTrackerStack[i].rootNode; - if (ParseTreeUtils.isNodeContainedWithin(node, rootNode)) { - return signatureTrackerStack[i].tracker; - } - } - - return undefined; - } - - function useSignatureTracker(node: ParseNode, callback: () => T): T { - const tracker = getSignatureTrackerForNode(node); - - try { - // If a signature tracker doesn't already exist, allocate one. - if (!tracker) { - signatureTrackerStack.push({ - tracker: new UniqueSignatureTracker(), - rootNode: node, - }); - } - - const result = callback(); - - if (!tracker) { - signatureTrackerStack.pop(); - } - - return result; - } catch (e) { - // We don't use finally here because the TypeScript debugger doesn't - // handle finally well when single stepping. - if (!tracker) { - signatureTrackerStack.pop(); - } - - throw e; - } - } - - function ensureSignatureIsUnique(type: T, node: ParseNode): T { - const tracker = getSignatureTrackerForNode(node); - if (!tracker) { - return type; - } - - if (isFunctionOrOverloaded(type)) { - return ensureSignaturesAreUnique(type, tracker, node.start); - } - - return type; - } - - // Disables recording of errors and warnings and disables any caching of - // types, under the assumption that we're performing speculative evaluations. - // If speculativeNode is undefined, speculative mode is not used. This is - // useful in cases where we conditionally want to use speculative mode. - function useSpeculativeMode( - speculativeNode: ParseNode | undefined, - callback: () => T, - options?: SpeculativeModeOptions - ) { - if (!speculativeNode) { - return callback(); - } - - speculativeTypeTracker.enterSpeculativeContext(speculativeNode, options); - - try { - const result = callback(); - speculativeTypeTracker.leaveSpeculativeContext(); - return result; - } catch (e) { - // We don't use finally here because the TypeScript debugger doesn't - // handle finally well when single stepping. - speculativeTypeTracker.leaveSpeculativeContext(); - throw e; - } - } - - function disableSpeculativeMode(callback: () => void) { - const stack = speculativeTypeTracker.disableSpeculativeMode(); - - try { - callback(); - speculativeTypeTracker.enableSpeculativeMode(stack); - } catch (e) { - // We don't use finally here because the TypeScript debugger doesn't - // handle finally well when single stepping. - speculativeTypeTracker.enableSpeculativeMode(stack); - throw e; - } - } - - // Indicates whether the specified node is within a context that - // is currently being evaluated speculative. If node is undefined, - // returns true if any node is being evaluated speculatively. - function isSpeculativeModeInUse(node: ParseNode | undefined) { - return speculativeTypeTracker.isSpeculative(node); - } - - function getDeclarationFromKeywordParam(type: FunctionType, paramName: string): Declaration | undefined { - if (isFunction(type)) { - if (type.shared.declaration) { - const functionDecl = type.shared.declaration; - if (functionDecl.type === DeclarationType.Function) { - const functionNode = functionDecl.node; - const functionScope = AnalyzerNodeInfo.getScope(functionNode); - if (functionScope) { - const paramSymbol = functionScope.lookUpSymbol(paramName)!; - if (paramSymbol) { - return paramSymbol.getDeclarations().find((decl) => decl.type === DeclarationType.Param); - } - - const parameterDetails = getParamListDetails(type); - if (parameterDetails.unpackedKwargsTypedDictType) { - const lookupResults = lookUpClassMember( - parameterDetails.unpackedKwargsTypedDictType, - paramName - ); - if (lookupResults) { - return lookupResults.symbol - .getDeclarations() - .find((decl) => decl.type === DeclarationType.Variable); - } - } - } - } - } - } - - return undefined; - } - - function _shouldFallBackToClassEntryForKeywordArg(baseType: ClassType, paramName: string) { - return ( - ClassType.isDataClass(baseType) || - ClassType.isTypedDictClass(baseType) || - ClassType.hasNamedTupleEntry(baseType, paramName) - ); - } - - function _addSymbolDeclInfo( - symbol: Symbol, - decls: Declaration[], - synthesizedTypes: SynthesizedTypeInfo[], - preferTypedDeclarations = false - ) { - const declCountBeforeAdd = decls.length; - const typedDecls = preferTypedDeclarations ? symbol.getTypedDeclarations() : undefined; - appendArray(decls, typedDecls?.length ? typedDecls : symbol.getDeclarations()); - - const synthTypeInfo = symbol.getSynthesizedType(); - if (synthTypeInfo) { - synthesizedTypes.push(synthTypeInfo); - - // Some module members are represented only by a synthesized module type, - // with no concrete declaration node to navigate to. Synthesize a matching - // alias declaration so definition/declaration/reference features can still - // bind to the submodule's file. - if ( - decls.length === declCountBeforeAdd && - isModule(synthTypeInfo.type) && - !synthTypeInfo.type.priv.fileUri.isEmpty() - ) { - decls.push(synthesizeAliasDeclaration(synthTypeInfo.type.priv.fileUri)); - } - } - } - - function _addClassEntryDeclsForKeywordArgIfPresent( - baseType: ClassType, - paramName: string, - decls: Declaration[], - synthesizedTypes: SynthesizedTypeInfo[] - ) { - const lookupResults = lookUpClassMember(baseType, paramName); - if (!lookupResults) { - return; - } - - _addSymbolDeclInfo(lookupResults.symbol, decls, synthesizedTypes); - } - - // In general, string nodes don't have any declarations associated with them, but - // we need to handle the special case of string literals used as keys within a - // dictionary expression where those keys are associated with a known TypedDict. - function getDeclInfoForStringNode(node: StringNode): SymbolDeclInfo | undefined { - const decls: Declaration[] = []; - const synthesizedTypes: SynthesizedTypeInfo[] = []; - const expectedType = getExpectedType(node)?.type; - - if (expectedType) { - doForEachSubtype(expectedType, (subtype) => { - // If the expected type is a TypedDict then the node is either a key expression - // or a single entry in a set. We then need to check that the value of the node - // is a valid entry in the TypedDict to avoid resolving declarations for - // synthesized symbols such as 'get'. - if (isClassInstance(subtype) && ClassType.isTypedDictClass(subtype)) { - const entry = subtype.shared.typedDictEntries?.knownItems.get(node.d.value); - if (entry) { - const symbol = lookUpObjectMember(subtype, node.d.value)?.symbol; - - if (symbol) { - _addSymbolDeclInfo(symbol, decls, synthesizedTypes); - } - } - } - }); - } - - return decls.length === 0 ? undefined : { decls, synthesizedTypes }; - } - - function getAliasFromImport(node: NameNode): NameNode | undefined { - if ( - node.parent && - node.parent.nodeType === ParseNodeType.ImportFromAs && - node.parent.d.alias && - node === node.parent.d.name - ) { - return node.parent.d.alias; - } - return undefined; - } - - function getDeclInfoForNameNode(node: NameNode, skipUnreachableCode = true): SymbolDeclInfo | undefined { - if (skipUnreachableCode && AnalyzerNodeInfo.isCodeUnreachable(node)) { - return undefined; - } - - const decls: Declaration[] = []; - const synthesizedTypes: SynthesizedTypeInfo[] = []; - - // If the node is part of a "from X import Y as Z" statement and the node - // is the "Y" (non-aliased) name, we need to look up the alias symbol - // since the non-aliased name is not in the symbol table. - const alias = getAliasFromImport(node); - if (alias) { - const scope = ScopeUtils.getScopeForNode(node); - if (scope) { - // Look up the alias symbol. - const symbolInScope = scope.lookUpSymbolRecursive(alias.d.value); - if (symbolInScope) { - // The alias could have more decls that don't refer to this import. Filter - // out the one(s) that specifically associated with this import statement. - const declsForThisImport = symbolInScope.symbol.getDeclarations().filter((decl) => { - return decl.type === DeclarationType.Alias && decl.node === node.parent; - }); - - appendArray(decls, getDeclarationsWithUsesLocalNameRemoved(declsForThisImport)); - } - } - } else if ( - node.parent && - node.parent.nodeType === ParseNodeType.MemberAccess && - node === node.parent.d.member - ) { - let baseType = getType(node.parent.d.leftExpr); - if (baseType) { - baseType = makeTopLevelTypeVarsConcrete(baseType); - const memberName = node.parent.d.member.d.value; - doForEachSubtype(baseType, (subtype) => { - let symbol: Symbol | undefined; - - subtype = makeTopLevelTypeVarsConcrete(subtype); - - if (isInstantiableClass(subtype)) { - // Try to find a member that has a declared type. If so, that - // overrides any inferred types. - let member = lookUpClassMember(subtype, memberName, MemberAccessFlags.DeclaredTypesOnly); - if (!member) { - member = lookUpClassMember(subtype, memberName); - } - - if (!member) { - const metaclass = subtype.shared.effectiveMetaclass; - if (metaclass && isInstantiableClass(metaclass)) { - member = lookUpClassMember(metaclass, memberName); - } - } - - if (member) { - symbol = member.symbol; - } - } else if (isClassInstance(subtype)) { - // Viper 扩展:CPtr[T] 指针解引用转发。 - // 当 baseType 是 `T | t.CPtr`(折叠为 CPtr[T],如 - // `args: ParsedArgs | t.CPtr`)时,成员访问 `args._set_int(...)` - // 应转发到 T 的成员(模拟 C 的 ptr->field 语义)。 - // 否则 lookUpObjectMember 会在 CPtr 上找 `_set_int`,找不到时 - // 走 CPtr.__getattr__ 返回 variable 声明,导致方法显示为变量颜色。 - let subtypeForLookup: ClassType = subtype; - if ( - isCPtrType(subtypeForLookup) && - subtypeForLookup.priv.typeArgs && - subtypeForLookup.priv.typeArgs.length > 0 - ) { - const pointeeType = subtypeForLookup.priv.typeArgs[0]; - if (isClassInstance(pointeeType)) { - subtypeForLookup = pointeeType; - } else if (isInstantiableClass(pointeeType)) { - const converted = convertToInstance(pointeeType); - if (isClassInstance(converted)) { - subtypeForLookup = converted; - } - } - } - - // Try to find a member that has a declared type. If so, that - // overrides any inferred types. - let member = lookUpObjectMember( - subtypeForLookup, - memberName, - MemberAccessFlags.DeclaredTypesOnly - ); - if (!member) { - member = lookUpObjectMember(subtypeForLookup, memberName); - } - if (member) { - symbol = member.symbol; - } - } else if (isModule(subtype)) { - symbol = ModuleType.getField(subtype, memberName); - } - - if (symbol) { - // By default, report only the declarations that have type annotations. - // If there are none, then report all of the unannotated declarations, - // which includes every assignment of that symbol. - _addSymbolDeclInfo(symbol, decls, synthesizedTypes, /* preferTypedDeclarations */ true); - } - }); - } - } else if (node.parent && node.parent.nodeType === ParseNodeType.ModuleName) { - const namePartIndex = node.parent.d.nameParts.findIndex((part) => part === node); - const importInfo = AnalyzerNodeInfo.getImportInfo(node.parent); - if ( - namePartIndex >= 0 && - importInfo && - !importInfo.isNativeLib && - namePartIndex < importInfo.resolvedUris.length - ) { - if (importInfo.resolvedUris[namePartIndex]) { - evaluateTypesForStatement(node); - - // Synthesize an alias declaration for this name part. The only - // time this case is used is for IDE services such as - // the find all references, hover provider and etc. - decls.push(synthesizeAliasDeclaration(importInfo.resolvedUris[namePartIndex])); - } - } - } else if (node.parent && node.parent.nodeType === ParseNodeType.Argument && node === node.parent.d.name) { - // The target node is the name in a keyword argument. We need to determine whether - // the corresponding keyword parameter can be determined from the context. - const argNode = node.parent; - const paramName = node.d.value; - if (argNode.parent?.nodeType === ParseNodeType.Call) { - const baseType = getType(argNode.parent.d.leftExpr); - - if (baseType) { - if (isFunction(baseType) && baseType.shared.declaration) { - const paramDecl = getDeclarationFromKeywordParam(baseType, paramName); - if (paramDecl) { - decls.push(paramDecl); - } - } else if (isOverloaded(baseType)) { - OverloadedType.getOverloads(baseType).forEach((f) => { - const paramDecl = getDeclarationFromKeywordParam(f, paramName); - if (paramDecl) { - decls.push(paramDecl); - } - }); - } else if (isInstantiableClass(baseType)) { - const initMethodType = getBoundInitMethod( - evaluatorInterface, - argNode.parent.d.leftExpr, - ClassType.cloneAsInstance(baseType) - )?.type; - - if (initMethodType && isFunction(initMethodType)) { - const paramDecl = getDeclarationFromKeywordParam(initMethodType, paramName); - if (paramDecl) { - decls.push(paramDecl); - } else if (_shouldFallBackToClassEntryForKeywordArg(baseType, paramName)) { - _addClassEntryDeclsForKeywordArgIfPresent(baseType, paramName, decls, synthesizedTypes); - } - } else if (_shouldFallBackToClassEntryForKeywordArg(baseType, paramName)) { - // Some synthesized callables (notably TypedDict "constructors") don't have a - // meaningful __init__ signature we can map keyword arguments to. In these cases, - // treat the keyword as referring to the class entry so IDE features like - // go-to-definition and rename can bind to the field declaration. - _addClassEntryDeclsForKeywordArgIfPresent(baseType, paramName, decls, synthesizedTypes); - } - } - } - } else if (argNode.parent?.nodeType === ParseNodeType.Class) { - const classTypeResult = getTypeOfClass(argNode.parent); - - // Validate the init subclass args for this class so we can properly - // evaluate its custom keyword parameters. - if (classTypeResult) { - validateInitSubclassArgs(argNode.parent, classTypeResult.classType); - } - } - } else { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - - // Determine if this node is within a quoted type annotation. - const isWithinTypeAnnotation = ParseTreeUtils.isWithinTypeAnnotation( - node, - !isAnnotationEvaluationPostponed(AnalyzerNodeInfo.getFileInfo(node)) - ); - - // Determine if this is part of a "type" statement. - const isWithinTypeAliasStatement = !!ParseTreeUtils.getParentNodeOfType(node, ParseNodeType.TypeAlias); - const allowForwardReferences = isWithinTypeAnnotation || isWithinTypeAliasStatement || fileInfo.isStubFile; - - const symbolWithScope = lookUpSymbolRecursive( - node, - node.d.value, - !allowForwardReferences, - isWithinTypeAnnotation - ); - - if (symbolWithScope) { - _addSymbolDeclInfo(symbolWithScope.symbol, decls, synthesizedTypes); - } - } - - return { decls, synthesizedTypes }; - } - - function getTypeForDeclaration(declaration: Declaration): DeclaredSymbolTypeInfo { - switch (declaration.type) { - case DeclarationType.Intrinsic: { - if (declaration.intrinsicType === 'Any') { - return { type: AnyType.create() }; - } - - if (declaration.intrinsicType === '__class__') { - const classNode = ParseTreeUtils.getEnclosingClass(declaration.node) as ClassNode; - const classTypeInfo = getTypeOfClass(classNode); - return { - type: classTypeInfo - ? specializeWithUnknownTypeArgs(classTypeInfo.classType, getTupleClassType()) - : UnknownType.create(), - }; - } - - const strType = getBuiltInObject(declaration.node, 'str'); - const intType = getBuiltInObject(declaration.node, 'int'); - if (isClassInstance(intType) && isClassInstance(strType)) { - if (declaration.intrinsicType === 'str') { - return { type: strType }; - } - - if (declaration.intrinsicType === 'str | None') { - return { type: combineTypes([strType, getNoneType()]) }; - } - - if (declaration.intrinsicType === 'int') { - return { type: intType }; - } - - if (declaration.intrinsicType === 'MutableSequence[str]') { - const sequenceType = getBuiltInType(declaration.node, 'MutableSequence'); - if (isInstantiableClass(sequenceType)) { - return { - type: ClassType.cloneAsInstance(ClassType.specialize(sequenceType, [strType])), - }; - } - } - - if (declaration.intrinsicType === 'dict[str, Any]') { - const dictType = getBuiltInType(declaration.node, 'dict'); - if (isInstantiableClass(dictType)) { - return { - type: ClassType.cloneAsInstance( - ClassType.specialize(dictType, [strType, AnyType.create()]) - ), - }; - } - } - } - - return { type: UnknownType.create() }; - } - - case DeclarationType.Class: { - const classTypeInfo = getTypeOfClass(declaration.node); - return { type: classTypeInfo?.decoratedType }; - } - - case DeclarationType.SpecialBuiltInClass: { - return { type: getTypeOfAnnotation(declaration.node.d.annotation) }; - } - - case DeclarationType.Function: { - const functionTypeInfo = getTypeOfFunction(declaration.node); - return { type: functionTypeInfo?.decoratedType }; - } - - case DeclarationType.TypeAlias: { - return { type: getTypeOfTypeAlias(declaration.node) }; - } - - case DeclarationType.Param: { - let typeAnnotationNode = declaration.node.d.annotation ?? declaration.node.d.annotationComment; - - // If there wasn't an annotation, see if the parent function - // has a function-level annotation comment that provides - // this parameter's annotation type. - if (!typeAnnotationNode) { - if (declaration.node.parent?.nodeType === ParseNodeType.Function) { - const functionNode = declaration.node.parent; - if ( - functionNode.d.funcAnnotationComment && - !functionNode.d.funcAnnotationComment.d.isEllipsis - ) { - const paramIndex = functionNode.d.params.findIndex((param) => param === declaration.node); - typeAnnotationNode = ParseTreeUtils.getTypeAnnotationForParam(functionNode, paramIndex); - } - } - } - - if (typeAnnotationNode) { - let declaredType = getTypeOfParamAnnotation(typeAnnotationNode, declaration.node.d.category); - - const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(declaration.node); - declaredType = makeTypeVarsBound(declaredType, liveTypeVarScopes); - - return { - type: transformVariadicParamType( - declaration.node, - declaration.node.d.category, - adjustParamAnnotatedType(declaration.node, declaredType) - ), - }; - } - - return { type: undefined }; - } - - case DeclarationType.TypeParam: { - return { type: getTypeOfTypeParam(declaration.node) }; - } - - case DeclarationType.Variable: { - const typeAnnotationNode = declaration.typeAnnotationNode; - - if (typeAnnotationNode) { - let declaredType: Type | undefined; - - if (declaration.isRuntimeTypeExpression) { - declaredType = convertToInstance( - getTypeOfExpressionExpectingType(typeAnnotationNode, { - allowFinal: true, - allowRequired: true, - allowReadOnly: true, - runtimeTypeExpression: true, - }).type - ); - } else { - const declNode = - declaration.isDefinedByMemberAccess && - declaration.node.parent?.nodeType === ParseNodeType.MemberAccess - ? declaration.node.parent - : declaration.node; - const allowClassVar = isClassVarAllowedForAssignmentTarget(declNode); - const allowFinal = isFinalAllowedForAssignmentTarget(declNode); - const allowRequired = - ParseTreeUtils.isRequiredAllowedForAssignmentTarget(declNode) || - !!declaration.isInInlinedTypedDict; - - declaredType = getTypeOfAnnotation(typeAnnotationNode, { - varTypeAnnotation: true, - allowClassVar, - allowFinal, - allowRequired, - allowReadOnly: allowRequired, - enforceClassTypeVarScope: declaration.isDefinedByMemberAccess, - }); - } - - if (declaredType) { - // If this is a declaration for a member variable within a method, - // we need to convert any bound TypeVars associated with the - // class to their free counterparts. - if (declaration.isDefinedByMemberAccess) { - const enclosingClass = ParseTreeUtils.getEnclosingClass(declaration.node); - - if (enclosingClass) { - declaredType = makeTypeVarsFree(declaredType, [ - ParseTreeUtils.getScopeIdForNode(enclosingClass), - ]); - } - } - - if (isClassInstance(declaredType)) { - // Viper 扩展:t.CTypedef 是 TypeAlias 的别名,但来自 - // vipershed-fallback 无 BuiltIn 标志,需额外检查类型名称。 - // 修复跨模块 from ... import * 后 typedef 显示为 int 的问题。 - const fileInfo = AnalyzerNodeInfo.getFileInfo(declaration.node); - const isViperCTypedef = - IsViperDialect(fileInfo.executionEnvironment.dialect) && - (declaredType.shared.name === 'CTypedef' || - declaredType.shared.fullName === 't.CTypedef'); - if (ClassType.isBuiltIn(declaredType, 'TypeAlias') || isViperCTypedef) { - return { type: undefined, isTypeAlias: true }; - } - } - - return { type: declaredType }; - } - } - - return { type: undefined }; - } - - case DeclarationType.Alias: { - return { type: undefined }; - } - } - } - - function getTypeOfTypeParam(node: TypeParameterNode): TypeVarType { - // Is this type already cached? - const cachedTypeVarType = readTypeCache(node.d.name, EvalFlags.None) as FunctionType; - if (cachedTypeVarType && isTypeVar(cachedTypeVarType)) { - return cachedTypeVarType; - } - - let runtimeClassName = 'TypeVar'; - let kind: TypeVarKind = TypeVarKind.TypeVar; - if (node.d.typeParamKind === TypeParamKind.TypeVarTuple) { - runtimeClassName = 'TypeVarTuple'; - kind = TypeVarKind.TypeVarTuple; - } else if (node.d.typeParamKind === TypeParamKind.ParamSpec) { - runtimeClassName = 'ParamSpec'; - kind = TypeVarKind.ParamSpec; - } - const runtimeType = getTypingType(node, runtimeClassName); - const runtimeClass = runtimeType && isInstantiableClass(runtimeType) ? runtimeType : undefined; - - let typeVar = TypeVarType.createInstantiable(node.d.name.d.value, kind); - if (runtimeClass) { - typeVar = TypeBase.cloneAsSpecialForm(typeVar, ClassType.cloneAsInstance(runtimeClass)); - } - typeVar.shared.isTypeParamSyntax = true; - - // Cache the value before we evaluate the bound or the default type in - // case it refers to itself in a circular manner. - writeTypeCache(node, { type: typeVar }, /* flags */ undefined); - writeTypeCache(node.d.name, { type: typeVar }, /* flags */ undefined); - - if (node.d.boundExpr) { - if (node.d.boundExpr.nodeType === ParseNodeType.Tuple) { - const constraints = node.d.boundExpr.d.items.map((constraint) => { - const constraintType = getTypeOfExpressionExpectingType(constraint, { - noNonTypeSpecialForms: true, - forwardRefs: true, - typeExpression: true, - }).type; - - if ( - requiresSpecialization(constraintType, { - ignorePseudoGeneric: true, - ignoreImplicitTypeArgs: true, - }) - ) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarBoundGeneric(), - constraint - ); - } - - return convertToInstance(constraintType); - }); - - if (constraints.length < 2) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarSingleConstraint(), - node.d.boundExpr - ); - } else if (node.d.typeParamKind === TypeParamKind.TypeVar) { - typeVar.shared.constraints = constraints; - } - } else { - const boundType = getTypeOfExpressionExpectingType(node.d.boundExpr, { - noNonTypeSpecialForms: true, - forwardRefs: true, - typeExpression: true, - }).type; - - if (requiresSpecialization(boundType, { ignorePseudoGeneric: true })) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.typeVarConstraintGeneric(), - node.d.boundExpr - ); - } - - if (node.d.typeParamKind === TypeParamKind.TypeVar) { - typeVar.shared.boundType = convertToInstance(boundType); - } - } - } - - if (node.d.typeParamKind === TypeParamKind.ParamSpec) { - const defaultType = node.d.defaultExpr - ? getParamSpecDefaultType(node.d.defaultExpr, /* isPep695Syntax */ true) - : undefined; - - if (defaultType) { - typeVar.shared.defaultType = defaultType; - typeVar.shared.isDefaultExplicit = true; - } else { - typeVar.shared.defaultType = ParamSpecType.getUnknown(); - } - } else if (node.d.typeParamKind === TypeParamKind.TypeVarTuple) { - const defaultType = node.d.defaultExpr - ? getTypeVarTupleDefaultType(node.d.defaultExpr, /* isPep695Syntax */ true) - : undefined; - - if (defaultType) { - typeVar.shared.defaultType = defaultType; - typeVar.shared.isDefaultExplicit = true; - } else { - typeVar.shared.defaultType = makeTupleObject(evaluatorInterface, [ - { type: UnknownType.create(), isUnbounded: true }, - ]); - } - } else { - const defaultType = node.d.defaultExpr - ? convertToInstance( - getTypeOfExpressionExpectingType(node.d.defaultExpr, { - forwardRefs: true, - typeExpression: true, - }).type - ) - : undefined; - - if (defaultType) { - typeVar.shared.defaultType = defaultType; - typeVar.shared.isDefaultExplicit = true; - } else { - typeVar.shared.defaultType = UnknownType.create(); - } - } - - // If a default is provided, make sure it is compatible with the bound - // or constraint. - if (typeVar.shared.isDefaultExplicit && node.d.defaultExpr) { - verifyTypeVarDefaultIsCompatible(typeVar, node.d.defaultExpr); - } - - // Associate the type variable with the owning scope. - const scopeNode = ParseTreeUtils.getTypeVarScopeNode(node); - if (scopeNode) { - let scopeType: TypeVarScopeType; - if (scopeNode.nodeType === ParseNodeType.Class) { - scopeType = TypeVarScopeType.Class; - - // Set the variance to "auto" for class-scoped TypeVars. - typeVar.shared.declaredVariance = - isParamSpec(typeVar) || isTypeVarTuple(typeVar) ? Variance.Invariant : Variance.Auto; - } else if (scopeNode.nodeType === ParseNodeType.Function) { - scopeType = TypeVarScopeType.Function; - } else { - assert(scopeNode.nodeType === ParseNodeType.TypeAlias); - scopeType = TypeVarScopeType.TypeAlias; - typeVar.shared.declaredVariance = - isParamSpec(typeVar) || isTypeVarTuple(typeVar) ? Variance.Invariant : Variance.Auto; - } - - typeVar = TypeVarType.cloneForScopeId( - typeVar, - ParseTreeUtils.getScopeIdForNode( - scopeNode.nodeType === ParseNodeType.TypeAlias ? scopeNode.d.name : scopeNode - ), - scopeNode.d.name.d.value, - scopeType - ); - } - - writeTypeCache(node, { type: typeVar }, /* flags */ undefined); - writeTypeCache(node.d.name, { type: typeVar }, /* flags */ undefined); - - return typeVar; - } - - function getInferredTypeOfDeclaration(symbol: Symbol, decl: Declaration): Type | undefined { - const resolvedDecl = resolveAliasDeclaration(decl, /* resolveLocalNames */ true, { - allowExternallyHiddenAccess: AnalyzerNodeInfo.getFileInfo(decl.node).isStubFile, - }); - - // We couldn't resolve the alias. Substitute an unknown - // type in this case. - if (!resolvedDecl) { - return evaluatorOptions.evaluateUnknownImportsAsAny ? AnyType.create() : UnknownType.create(); - } - - function applyLoaderActionsToModuleType( - moduleType: ModuleType, - loaderActions: ModuleLoaderActions, - importLookup: ImportLookup - ): Type { - if (!loaderActions.uri.isEmpty() && loaderActions.loadSymbolsFromPath) { - const lookupResults = importLookup(loaderActions.uri); - if (lookupResults) { - moduleType.priv.fields = lookupResults.symbolTable; - moduleType.priv.docString = lookupResults.docString; - } else { - // Note that all module attributes that are not found in the - // symbol table should be treated as Any or Unknown rather than - // as an error. - moduleType.priv.notPresentFieldType = evaluatorOptions.evaluateUnknownImportsAsAny - ? AnyType.create() - : UnknownType.create(); - } - } - - if (loaderActions.implicitImports) { - loaderActions.implicitImports.forEach((implicitImport, name) => { - const existingLoaderField = moduleType.priv.loaderFields.get(name); - - // Recursively apply loader actions. - let symbolType: Type; - - if (implicitImport.isUnresolved) { - symbolType = UnknownType.create(); - } else { - let importedModuleType: ModuleType; - - const existingType = existingLoaderField?.getSynthesizedType(); - if (existingType?.type && isModule(existingType.type)) { - importedModuleType = existingType.type; - } else { - const moduleName = moduleType.priv.moduleName - ? moduleType.priv.moduleName + '.' + name - : ''; - importedModuleType = ModuleType.create(moduleName, implicitImport.uri); - } - - symbolType = applyLoaderActionsToModuleType(importedModuleType, implicitImport, importLookup); - } - - if (!existingLoaderField) { - const importedModuleSymbol = Symbol.createWithType(SymbolFlags.None, symbolType); - moduleType.priv.loaderFields.set(name, importedModuleSymbol); - } - }); - } - - return moduleType; - } - - // If the resolved declaration is still an alias, the alias - // is pointing at a module, and we need to synthesize a - // module type. - if (resolvedDecl.type === DeclarationType.Alias) { - let moduleType: ModuleType | undefined; - - // See if this is an import that shares a ModuleType with another - // import statement. If so, used the cached type. This happens when - // multiple import statements start with the same module name, such - // as "import a.b" and "import a.c". - if (resolvedDecl.node.nodeType === ParseNodeType.ImportAs) { - const cachedType = readTypeCache(resolvedDecl.node.d.module, EvalFlags.None); - if (cachedType && isModule(cachedType)) { - moduleType = cachedType; - } - } - - if (!moduleType) { - // Build a module type that corresponds to the declaration and - // its associated loader actions. - moduleType = ModuleType.create(resolvedDecl.moduleName, resolvedDecl.uri); - - if (resolvedDecl.node.nodeType === ParseNodeType.ImportAs) { - writeTypeCache(resolvedDecl.node.d.module, { type: moduleType }, EvalFlags.None); - } - } - - return applyLoaderActionsToModuleType( - moduleType, - resolvedDecl.symbolName && resolvedDecl.submoduleFallback - ? resolvedDecl.submoduleFallback - : resolvedDecl, - importLookup - ); - } - - const declaredType = getTypeForDeclaration(resolvedDecl); - if (declaredType.type) { - return declaredType.type; - } - - // If this is part of a "py.typed" package, don't fall back on type inference - // unless it's marked Final, is a constant, or is a declared type alias. - const fileInfo = AnalyzerNodeInfo.getFileInfo(resolvedDecl.node); - let isUnambiguousType = !fileInfo.isInPyTypedPackage || fileInfo.isStubFile; - - // If this is a py.typed package, determine if this is a case where an unannotated - // variable is considered "unambiguous" because all type checkers are almost - // guaranteed to infer its type the same. - if (!isUnambiguousType) { - if (resolvedDecl.type === DeclarationType.Variable) { - // Special-case variables within an enum class. These are effectively - // constants, so we'll treat them as unambiguous. - const enclosingClass = ParseTreeUtils.getEnclosingClass(resolvedDecl.node, /* stopAtFunction */ true); - if (enclosingClass) { - const classTypeInfo = getTypeOfClass(enclosingClass); - if (classTypeInfo && ClassType.isEnumClass(classTypeInfo.classType)) { - isUnambiguousType = true; - } - } - - // Special-case constants, which are treated as unambiguous. - if (isFinalVariableDeclaration(resolvedDecl) || resolvedDecl.isConstant) { - isUnambiguousType = true; - } - - // Special-case calls to certain built-in type functions. - if (resolvedDecl.inferredTypeSource?.nodeType === ParseNodeType.Call) { - const baseTypeResult = getTypeOfExpression( - resolvedDecl.inferredTypeSource.d.leftExpr, - EvalFlags.CallBaseDefaults - ); - const callType = baseTypeResult.type; - - const exemptBuiltins = [ - 'TypeVar', - 'ParamSpec', - 'TypeVarTuple', - 'TypedDict', - 'NamedTuple', - 'NewType', - 'TypeAliasType', - ]; - - if (isInstantiableClass(callType) && ClassType.isBuiltIn(callType, exemptBuiltins)) { - isUnambiguousType = true; - } else if ( - isFunction(callType) && - exemptBuiltins.some((name) => FunctionType.isBuiltIn(callType, name)) - ) { - isUnambiguousType = true; - } - } - } - } - - // If the resolved declaration had no defined type, use the - // inferred type for this node. - if (resolvedDecl.type === DeclarationType.Param) { - assert(resolvedDecl.node.d.name !== undefined); - return evaluateTypeForSubnode(resolvedDecl.node.d.name, () => { - evaluateTypeOfParam(resolvedDecl.node); - })?.type; - } - - if (resolvedDecl.type === DeclarationType.Variable && resolvedDecl.inferredTypeSource) { - const isTypeAlias = - isExplicitTypeAliasDeclaration(resolvedDecl) || isPossibleTypeAliasOrTypedDict(resolvedDecl); - - // If this is a type alias, evaluate types for the entire assignment - // statement rather than just the RHS of the assignment. - const typeSource = - isTypeAlias && resolvedDecl.inferredTypeSource.parent - ? resolvedDecl.inferredTypeSource.parent - : resolvedDecl.inferredTypeSource; - let inferredType = evaluateTypeForSubnode(resolvedDecl.node, () => { - evaluateTypesForStatement(typeSource); - })?.type; - - if (inferredType && isTypeAlias && resolvedDecl.typeAliasName) { - // If this was a speculative type alias, it becomes a real type alias only - // in the event that its inferred type is instantiable or explicitly Any - // (but not an ellipsis). - if (isLegalImplicitTypeAliasType(inferredType)) { - const typeAliasTypeVar = synthesizeTypeAliasPlaceholder(resolvedDecl.typeAliasName); - - inferredType = transformTypeForTypeAlias( - inferredType, - resolvedDecl.node, - typeAliasTypeVar, - /* isPep695TypeVarType */ false - ); - - isUnambiguousType = true; - } - } - - // Determine whether we need to mark the annotation as ambiguous. - if (inferredType && fileInfo.isInPyTypedPackage && !fileInfo.isStubFile) { - if (!isUnambiguousType) { - // See if this particular inference can be considered "unambiguous". - // Any symbol that is assigned more than once is considered ambiguous. - if (isUnambiguousInference(symbol, decl, inferredType)) { - isUnambiguousType = true; - } - } - - if (!isUnambiguousType) { - inferredType = TypeBase.cloneForAmbiguousType(inferredType); - } - } - - return inferredType; - } - - return undefined; - } - - // Applies some heuristics to determine whether it's likely that all Python - // type checkers will infer the same type. - function isUnambiguousInference(symbol: Symbol, decl: Declaration, inferredType: Type): boolean { - const nonSlotsDecls = symbol.getDeclarations().filter((decl) => { - return decl.type !== DeclarationType.Variable || !decl.isInferenceAllowedInPyTyped; - }); - - // Any symbol with more than one assignment is considered ambiguous. - if (nonSlotsDecls.length > 1) { - return false; - } - - if (decl.type !== DeclarationType.Variable) { - return false; - } - - // If there are no non-slots declarations, don't mark the inferred type as ambiguous. - if (nonSlotsDecls.length === 0) { - return true; - } - - // TypeVar definitions don't require a declaration. - if (isTypeVar(inferredType)) { - return true; - } - - let assignmentNode: AssignmentNode | undefined; - - const parentNode = decl.node.parent; - if (parentNode) { - // Is this a simple assignment (x = y) or an assignment of an instance variable (self.x = y)? - if (parentNode.nodeType === ParseNodeType.Assignment) { - assignmentNode = parentNode; - } else if ( - parentNode.nodeType === ParseNodeType.MemberAccess && - parentNode.parent?.nodeType === ParseNodeType.Assignment - ) { - assignmentNode = parentNode.parent; - } - } - - if (!assignmentNode) { - return false; - } - - const assignedType = getTypeOfExpression(assignmentNode.d.rightExpr).type; - - // Assume that literal values will always result in the same inferred type. - if (isClassInstance(assignedType) && isLiteralType(assignedType)) { - return true; - } - - // If the assignment is a simple name corresponding to an unambiguous - // type, we'll assume the resulting variable will receive the same - // unambiguous type. - if (assignmentNode.d.rightExpr.nodeType === ParseNodeType.Name && !TypeBase.isAmbiguous(assignedType)) { - return true; - } - - return false; - } - - // If the specified declaration is an alias declaration that points to a symbol, - // it resolves the alias and looks up the symbol, then returns the first declaration - // associated with that symbol. It does this recursively if necessary. If a symbol - // lookup fails, undefined is returned. If resolveLocalNames is true, the method - // resolves aliases through local renames ("as" clauses found in import statements). - function resolveAliasDeclaration( - declaration: Declaration, - resolveLocalNames: boolean, - options?: ResolveAliasOptions - ): Declaration | undefined { - return resolveAliasDeclarationUtil(importLookup, declaration, { - resolveLocalNames, - allowExternallyHiddenAccess: options?.allowExternallyHiddenAccess ?? false, - skipFileNeededCheck: options?.skipFileNeededCheck ?? false, - })?.declaration; - } - - function resolveAliasDeclarationWithInfo( - declaration: Declaration, - resolveLocalNames: boolean, - options?: ResolveAliasOptions - ): ResolvedAliasInfo | undefined { - return resolveAliasDeclarationUtil(importLookup, declaration, { - resolveLocalNames, - allowExternallyHiddenAccess: options?.allowExternallyHiddenAccess ?? false, - skipFileNeededCheck: options?.skipFileNeededCheck ?? false, - }); - } - - // Returns the type of the symbol. If the type is explicitly declared, that type - // is returned. If not, the type is inferred from assignments to the symbol. All - // assigned types are evaluated and combined into a union. - function getEffectiveTypeOfSymbol(symbol: Symbol): Type { - return getEffectiveTypeOfSymbolForUsage(symbol).type; - } - - // If a "usageNode" node is specified, only declarations that are outside - // of the current execution scope or that are reachable (as determined by - // code flow analysis) are considered. This helps in cases where there - // are cyclical dependencies between symbols. - function getEffectiveTypeOfSymbolForUsage( - symbol: Symbol, - usageNode?: NameNode, - useLastDecl = false - ): EffectiveTypeResult { - let declaredTypeInfo: DeclaredSymbolTypeInfo | undefined; - - // If there's a declared type, it takes precedence over inferred types. - if (symbol.hasTypedDeclarations()) { - declaredTypeInfo = getDeclaredTypeOfSymbol(symbol, usageNode); - const declaredType = declaredTypeInfo?.type; - - let isIncomplete = false; - if (declaredType) { - if (isFunction(declaredType) && FunctionType.isPartiallyEvaluated(declaredType)) { - isIncomplete = true; - } else if (isClass(declaredType) && ClassType.isPartiallyEvaluated(declaredType)) { - isIncomplete = true; - } - } - - // If the "declared" type uses a "TypeAlias" type annotation, then - // we need to use the inferred type path to evaluate its type. - if (declaredType || !declaredTypeInfo.isTypeAlias) { - const typedDecls = symbol.getTypedDeclarations(); - - // If we received an undefined declared type, this can be caused by - // exceeding the max number of type declarations, speculative - // evaluation, or a recursive definition. - const isRecursiveDefinition = - !declaredType && - !declaredTypeInfo.exceedsMaxDecls && - !speculativeTypeTracker.isSpeculative(/* node */ undefined); - - const result: EffectiveTypeResult = { - type: declaredType ?? UnknownType.create(), - isIncomplete, - includesVariableDecl: includesVariableTypeDecl(typedDecls), - includesIllegalTypeAliasDecl: !typedDecls.every((decl) => isPossibleTypeAliasDeclaration(decl)), - includesSpeculativeResult: false, - isRecursiveDefinition, - }; - - return result; - } - } - - return inferTypeOfSymbolForUsage(symbol, usageNode, useLastDecl); - } - - // Determines whether the set of declarations includes a variable declaration - // that is not part of a typing.pyi or typingExtensions.pyi file. - function includesVariableTypeDecl(decls: Declaration[]): boolean { - return decls.some((decl) => { - if (decl.type === DeclarationType.Variable) { - // Exempt typing.pyi and typingExtensions.pyi, which use variables to - // define some special forms. - const fileInfo = AnalyzerNodeInfo.getFileInfo(decl.node); - - if (!fileInfo.isTypingStubFile && !fileInfo.isTypingExtensionsStubFile) { - return true; - } - } - - if (decl.type === DeclarationType.Param) { - return true; - } - - return false; - }); - } - - function inferTypeOfSymbolForUsage(symbol: Symbol, usageNode?: NameNode, useLastDecl = false): EffectiveTypeResult { - // Look in the inferred type cache to see if we've computed this already. - let cacheEntries = effectiveTypeCache.get(symbol.id); - const usageNodeId = usageNode ? usageNode.id : undefined; - const effectiveTypeCacheKey = `${usageNodeId === undefined ? '.' : usageNodeId.toString()}${ - useLastDecl ? '*' : '' - }`; - const cacheEntry = cacheEntries?.get(effectiveTypeCacheKey); - - if (cacheEntry && !cacheEntry.isIncomplete) { - return cacheEntry; - } - - // Infer the type. - const decls = symbol.getDeclarations(); - - let declIndexToConsider: number | undefined; - - // Limit the number of declarations to explore. - if (decls.length > maxDeclarationsToUseForInference) { - const result: EffectiveTypeResult = { - type: UnknownType.create(), - isIncomplete: false, - includesVariableDecl: false, - includesIllegalTypeAliasDecl: !decls.every((decl) => isPossibleTypeAliasDeclaration(decl)), - includesSpeculativeResult: false, - isRecursiveDefinition: false, - }; - - addToEffectiveTypeCache(result); - return result; - } - - // If the caller has requested that we use only the last decl, we - // will use only the last one, but we'll ignore decls that are in - // except clauses. - if (useLastDecl) { - decls.forEach((decl, index) => { - if (!decl.isInExceptSuite) { - declIndexToConsider = index; - } - }); - } else { - // Handle the case where there are multiple imports — one of them in - // a try block and one or more in except blocks. In this case, we'll - // use the one in the try block rather than the excepts. - if (decls.length > 1 && decls.every((decl) => decl.type === DeclarationType.Alias)) { - const nonExceptDecls = decls.filter( - (decl) => decl.type === DeclarationType.Alias && !decl.isInExceptSuite - ); - if (nonExceptDecls.length === 1) { - declIndexToConsider = decls.findIndex((decl) => decl === nonExceptDecls[0]); - } - } - } - - // Determine which declarations to use for inference. - const declsToConsider: Declaration[] = []; - let includesVariableDecl = false; - let includesIllegalTypeAliasDecl = false; - - let sawExplicitTypeAlias = false; - decls.forEach((decl, index) => { - const resolvedDecl = - resolveAliasDeclaration(decl, /* resolveLocalNames */ true, { - allowExternallyHiddenAccess: AnalyzerNodeInfo.getFileInfo(decl.node).isStubFile, - }) ?? decl; - - if (!isPossibleTypeAliasDeclaration(resolvedDecl) && !isExplicitTypeAliasDeclaration(resolvedDecl)) { - includesIllegalTypeAliasDecl = true; - } - - if (includesVariableTypeDecl([resolvedDecl])) { - includesVariableDecl = true; - } - - if (declIndexToConsider !== undefined && declIndexToConsider !== index) { - return; - } - - // If we have already seen an explicit type alias, do not consider - // additional decls. This can happen if multiple TypeAlias declarations - // are provided -- normally an error, but it can happen in stdlib stubs - // if the user sets the pythonPlatform to "All". - if (sawExplicitTypeAlias) { - return; - } - - // If the symbol is explicitly marked as a ClassVar, consider only the - // declarations that assign to it from within the class body, not through - // a member access expression. - if ( - isEffectivelyClassVar(symbol, /* isDataclass */ false) && - decl.type === DeclarationType.Variable && - decl.isDefinedByMemberAccess - ) { - return; - } - - if (usageNode !== undefined) { - if (decl.type !== DeclarationType.Alias) { - // Is the declaration in the same execution scope as the "usageNode" node? - // If so, we can skip it because code flow analysis will allow us - // to determine the type in this context. - const usageScope = ParseTreeUtils.getExecutionScopeNode(usageNode); - const declScope = ParseTreeUtils.getExecutionScopeNode(decl.node); - if (usageScope === declScope) { - // Skip declarations that appear after the usage in the source. - // Such declarations are typically only reached via loop back-edges, - // and including them causes circular evaluation (producing Unknown). - // Declarations that textually precede the usage are retained so - // that order-independent type aliases resolve correctly; for - // branching constructs (if/else, try/except), a retained - // declaration may be flow-unreachable at the usage site, but the - // code-flow engine overrides the effective type for local - // variables, so the narrowed type at the usage site is correct. - // Note: for the cases filtered out here (decl after usage in same - // scope), the code-flow engine handles loop-carried narrowing when - // it evaluates the type at the actual usage site. - if (decl.node.start >= usageNode.start) { - return; - } - } - } - } - - const isExplicitTypeAlias = isExplicitTypeAliasDeclaration(resolvedDecl); - const isTypeAlias = isExplicitTypeAlias || isPossibleTypeAliasOrTypedDict(resolvedDecl); - - if (isExplicitTypeAlias) { - sawExplicitTypeAlias = true; - } - - // If this is a type alias, evaluate it outside of the recursive symbol - // resolution check so we can evaluate the full assignment statement. - if ( - isTypeAlias && - resolvedDecl.type === DeclarationType.Variable && - resolvedDecl.inferredTypeSource?.parent?.nodeType === ParseNodeType.Assignment - ) { - evaluateTypesForAssignmentStatement(resolvedDecl.inferredTypeSource.parent); - } - - // Viper 扩展:显式类型别名声明(如 `UINT32PTR: t.CTypedef = ...`) - // 产生两个声明:类型注解声明(有 typeAnnotationNode,无 inferredTypeSource) - // 和赋值声明(有 inferredTypeSource,无 typeAnnotationNode)。 - // 类型注解声明被识别为显式类型别名后,赋值声明会被 sawExplicitTypeAlias 跳过。 - // 此处补充:当显式类型别名声明没有 inferredTypeSource 时, - // 查找同符号的其他声明中是否有 inferredTypeSource,触发赋值语句求值。 - if ( - isExplicitTypeAlias && - resolvedDecl.type === DeclarationType.Variable && - !resolvedDecl.inferredTypeSource - ) { - const otherDecl = decls.find( - (d) => - d !== decl && - d.type === DeclarationType.Variable && - (d as VariableDeclaration).inferredTypeSource?.parent?.nodeType === ParseNodeType.Assignment - ); - if (otherDecl) { - const otherVarDecl = otherDecl as VariableDeclaration; - evaluateTypesForAssignmentStatement(otherVarDecl.inferredTypeSource!.parent as AssignmentNode); - } - } - - declsToConsider.push(resolvedDecl); - }); - - // If all of the decls come from augmented assignments, we won't be able to - // determine its type. At least one declaration must be a simple assignment. - if ( - declsToConsider.every( - (decl) => - isVariableDeclaration(decl) && - ParseTreeUtils.isNodeContainedWithinNodeType(decl.node, ParseNodeType.AugmentedAssignment) - ) - ) { - declsToConsider.splice(0); - } - - const result = getTypeOfSymbolForDecls(symbol, declsToConsider, effectiveTypeCacheKey); - result.includesVariableDecl = includesVariableDecl; - result.includesIllegalTypeAliasDecl = includesIllegalTypeAliasDecl; - - // Add the result to the effective type cache if it doesn't include speculative results. - if (!result.includesSpeculativeResult) { - addToEffectiveTypeCache(result); - } - - return result; - - function addToEffectiveTypeCache(result: EffectiveTypeResult) { - // Add the entry to the cache so we don't need to compute it next time. - if (!cacheEntries) { - cacheEntries = new Map(); - effectiveTypeCache.set(symbol.id, cacheEntries); - } - - cacheEntries.set(effectiveTypeCacheKey, result); - } - } - - // Returns the type of a symbol based on a subset of its declarations. - function getTypeOfSymbolForDecls(symbol: Symbol, decls: Declaration[], typeCacheKey: string): EffectiveTypeResult { - const typesToCombine: Type[] = []; - let isIncomplete = false; - let sawPendingEvaluation = false; - let includesSpeculativeResult = false; - - decls.forEach((decl) => { - if (pushSymbolResolution(symbol, decl)) { - let symbolPopped = false; - try { - let type = getInferredTypeOfDeclaration(symbol, decl); - - if (!popSymbolResolution(symbol)) { - isIncomplete = true; - } - symbolPopped = true; - - if (type) { - if (decl.type === DeclarationType.Variable) { - let isConstant = false; - if (decl.type === DeclarationType.Variable) { - if (decl.isConstant || isFinalVariableDeclaration(decl)) { - isConstant = true; - } - } - - // Treat enum values declared within an enum class as though they are const even - // though they may not be named as such. - if ( - isClassInstance(type) && - ClassType.isEnumClass(type) && - isDeclInEnumClass(evaluatorInterface, decl) - ) { - isConstant = true; - } - - // If the symbol is constant, we can retain the literal - // value and TypeForm types. Otherwise, strip literal values - // and TypeForm types to widen. - if (TypeBase.isInstance(type) && !isConstant && !isExplicitTypeAliasDeclaration(decl)) { - type = stripTypeForm(stripLiteralValue(type)); - } - } - - typesToCombine.push(type); - - if (isSpeculativeModeInUse(decl.node)) { - includesSpeculativeResult = true; - } - } else { - isIncomplete = true; - } - } catch (e: any) { - // Clean up the stack before rethrowing, but only if we haven't already popped. - if (!symbolPopped) { - popSymbolResolution(symbol); - } - throw e; - } - } else { - if (decl.type === DeclarationType.Class) { - const classTypeInfo = getTypeOfClass(decl.node); - if (classTypeInfo?.decoratedType) { - typesToCombine.push(classTypeInfo.decoratedType); - } - } - - isIncomplete = true; - - // Note that at least one decl could not be evaluated because - // it was already in the process of being evaluated. - sawPendingEvaluation = true; - } - }); - - // How many times have we already attempted to evaluate this declaration already? - const cacheEntries = effectiveTypeCache.get(symbol.id); - const evaluationAttempts = (cacheEntries?.get(typeCacheKey)?.evaluationAttempts ?? 0) + 1; - - let type: Type; - - if (typesToCombine.length > 0) { - // Ignore the pending evaluation flag if we've already attempted the - // type evaluation many times because this probably means there's a - // cyclical dependency that cannot be broken. - isIncomplete = sawPendingEvaluation && evaluationAttempts < maxEffectiveTypeEvaluationAttempts; - - type = combineTypes(typesToCombine); - } else { - // We can encounter this situation in the case of a bare ClassVar annotation. - if (symbol.isClassVar()) { - type = UnknownType.create(); - isIncomplete = false; - } else { - type = UnboundType.create(); - } - } - - return { type, isIncomplete, includesSpeculativeResult, evaluationAttempts }; - } - - // If a declaration has an explicit type (e.g. a variable with an annotation), - // this function evaluates the type and returns it. If the symbol has no - // explicit declared type, its type will need to be inferred instead. In some - // cases, non-type information (such as Final or ClassVar attributes) may be - // provided, but type inference is still required. In such cases, the attributes - // are returned as flags. - function getDeclaredTypeOfSymbol(symbol: Symbol, usageNode?: NameNode): DeclaredSymbolTypeInfo { - const synthesizedType = symbol.getSynthesizedType()?.type; - if (synthesizedType) { - return { type: synthesizedType }; - } - - let typedDecls = symbol.getTypedDeclarations(); - - if (typedDecls.length === 0) { - // If the symbol has no type declaration but is assigned many times, - // treat it as though it has an explicit type annotation of "Unknown". - // This will avoid a pathological performance condition for unannotated - // code that reassigns the same variable hundreds of times. If the symbol - // effectively has an "Any" annotation, it won't be narrowed. - if (symbol.getDeclarations().length > maxDeclarationsToUseForInference) { - return { type: UnknownType.create() }; - } - - // There was no declaration with a defined type. - return { type: undefined }; - } - - // If there is more than one typed decl, filter out any that are not - // reachable from the usage node (if specified). This can happen in - // cases where a property symbol is redefined to add a setter, deleter, - // etc. - let exceedsMaxDecls = false; - // Viper 扩展:跳过 flow 可达性过滤(AOT 静态类型语义)。 - // Viper 是 AOT 静态语言,变量类型在声明时确定(如 `arg: Argument | t.CPtr`), - // 不应因 flow 路径未建立而过滤 typed declarations。否则函数内多赋值变量 - // 的第一个使用点可能拿到 Unbound(如 argparse.vp 的 `arg.action` 报 - // "Cannot access member on unresolved type"),而后续使用点才正常。 - // 命中场景:parse_args 内 `arg: Argument | t.CPtr` 被循环内多次赋值, - // 第一个 `arg.action` 访问时 flow 图尚未覆盖声明节点。 - let isViperTypedDecl = false; - if (usageNode) { - const usageFileInfo = AnalyzerNodeInfo.getFileInfo(usageNode); - isViperTypedDecl = - IsViperDialect(usageFileInfo.executionEnvironment.dialect) && typedDecls.length > 0; - } - if (usageNode && typedDecls.length > 1 && !isViperTypedDecl) { - if (typedDecls.length > maxTypedDeclsPerSymbol) { - // If there are too many typed decls, don't bother filtering them - // because this can be very expensive. Simply use the last one - // in this case. - typedDecls = [typedDecls[typedDecls.length - 1]]; - exceedsMaxDecls = true; - } else { - const filteredTypedDecls = typedDecls.filter((decl) => { - if (decl.type !== DeclarationType.Alias) { - // Is the declaration in the same execution scope as the "usageNode" node? - const usageScope = ParseTreeUtils.getExecutionScopeNode(usageNode); - const declScope = ParseTreeUtils.getExecutionScopeNode(decl.node); - - if (usageScope === declScope) { - // For typed declarations we use the precise flow-graph reachability - // check rather than a simple position comparison, because typed decls - // (e.g. explicit annotations) can legitimately appear after the usage - // in the source text (e.g. a class attribute annotated below a method - // that references it) and must not be excluded by position alone. - if (!isFlowPathBetweenNodes(decl.node, usageNode, /* allowSelf */ false)) { - return false; - } - } - } - return true; - }); - - if (filteredTypedDecls.length === 0) { - return { type: UnboundType.create() }; - } - - typedDecls = filteredTypedDecls; - } - } - - // Start with the last decl. If that's already being resolved, - // use the next-to-last decl, etc. This can happen when resolving - // property methods. Often the setter method is defined in reference to - // the initial property, which defines the getter method with the same - // symbol name. - let declIndex = typedDecls.length - 1; - while (declIndex >= 0) { - const decl = typedDecls[declIndex]; - - // If there's a partially-constructed type that is allowed - // for recursive symbol resolution, return it as the resolved type. - const partialType = getSymbolResolutionPartialType(symbol, decl); - if (partialType) { - return { type: partialType }; - } - - if (getIndexOfSymbolResolution(symbol, decl) < 0) { - if (pushSymbolResolution(symbol, decl)) { - try { - const declaredTypeInfo = getTypeForDeclaration(decl); - - // If there was recursion detected, don't use this declaration. - // The exception is it's a class declaration because getTypeOfClass - // handles recursion by populating a partially-created class type - // in the type cache. This exception is required to handle the - // circular dependency between the "type" and "object" classes in - // builtins.pyi (since "object" is a "type" and "type" is an "object"). - if (popSymbolResolution(symbol) || decl.type === DeclarationType.Class) { - return declaredTypeInfo; - } - } catch (e: any) { - // Clean up the stack before rethrowing. - popSymbolResolution(symbol); - throw e; - } - } - } - - declIndex--; - } - - return { type: undefined, exceedsMaxDecls }; - } - - function inferReturnTypeIfNecessary(type: Type) { - if (isFunction(type)) { - getEffectiveReturnType(type); - } else if (isOverloaded(type)) { - OverloadedType.getOverloads(type).forEach((overload) => { - getEffectiveReturnType(overload); - }); - - const impl = OverloadedType.getImplementation(type); - if (impl && isFunction(impl)) { - getEffectiveReturnType(impl); - } - } - } - - function getEffectiveReturnType(type: FunctionType): Type { - return getEffectiveReturnTypeResult(type).type; - } - - function getInferredReturnType(type: FunctionType): Type { - return getInferredReturnTypeResult(type).type; - } - - // Returns the return type of the function. If the type is explicitly provided in - // a type annotation, that type is returned. If not, an attempt is made to infer - // the return type. If a list of args is provided, the inference logic may take - // into account argument types to infer the return type. - function getEffectiveReturnTypeResult(type: FunctionType, options?: EffectiveReturnTypeOptions): TypeResult { - const specializedReturnType = FunctionType.getEffectiveReturnType(type, /* includeInferred */ false); - if (specializedReturnType && !isUnknown(specializedReturnType)) { - return { type: specializedReturnType }; - } - - return getInferredReturnTypeResult(type, options?.callSiteInfo); - } - - function _getInferredReturnTypeResult(type: FunctionType, callSiteInfo?: CallSiteEvaluationInfo): TypeResult { - let returnType: Type | undefined; - let isIncomplete = false; - const analyzeUnannotatedFunctions = true; - - // Don't attempt to infer the return type for a stub file. - if (FunctionType.isStubDefinition(type)) { - return { type: UnknownType.create() }; - } - - // Don't infer the return type for a ParamSpec value. - if (FunctionType.isParamSpecValue(type)) { - return { type: UnknownType.create() }; - } - - // Don't infer the return type for an overloaded function (unless it's synthesized, - // which is needed for proper operation of the __get__ method in properties). - if (FunctionType.isOverloaded(type) && !FunctionType.isSynthesizedMethod(type)) { - return { type: UnknownType.create() }; - } - - const evalCount = type.shared.inferredReturnType?.evaluationCount ?? 0; - - // If the return type has already been lazily evaluated, - // don't bother computing it again. - if (type.shared.inferredReturnType && !type.shared.inferredReturnType.isIncomplete) { - returnType = type.shared.inferredReturnType.type; - } else if (evalCount > maxReturnTypeInferenceAttempts) { - // Detect a case where a return type won't converge because of recursion. - returnType = UnknownType.create(); - } else { - // Don't bother inferring the return type of __init__ because it's - // always None. - if (FunctionType.isInstanceMethod(type) && type.shared.name === '__init__') { - returnType = getNoneType(); - } else if (type.shared.declaration) { - const functionNode = type.shared.declaration.node; - const skipUnannotatedFunction = - !AnalyzerNodeInfo.getFileInfo(functionNode).diagnosticRuleSet.analyzeUnannotatedFunctions && - ParseTreeUtils.isUnannotatedFunction(functionNode); - - // Skip return type inference if we are in "skip unannotated function" mode. - if (!skipUnannotatedFunction && !checkCodeFlowTooComplex(functionNode.d.suite)) { - const codeFlowComplexity = AnalyzerNodeInfo.getCodeFlowComplexity(functionNode); - - // For very complex functions that have no annotated parameter types, - // don't attempt to infer the return type because it can be extremely - // expensive. - const parametersAreAnnotated = - type.shared.parameters.length <= 1 || - type.shared.parameters.some((param) => FunctionParam.isTypeDeclared(param)); - - if (parametersAreAnnotated || codeFlowComplexity < maxReturnTypeInferenceCodeFlowComplexity) { - // Temporarily disable speculative mode while we - // lazily evaluate the return type. - let returnTypeResult: TypeResult | undefined; - disableSpeculativeMode(() => { - returnTypeResult = inferFunctionReturnType( - functionNode, - FunctionType.isAbstractMethod(type), - callSiteInfo?.errorNode - ); - }); - - returnType = returnTypeResult?.type; - if (returnTypeResult?.isIncomplete) { - isIncomplete = true; - } - } - } - } - - if (!returnType) { - returnType = UnknownType.create(); - } - - // Externalize any TypeVars that appear in the type. - const typeVarScopes: TypeVarScopeId[] = []; - if (type.shared.typeVarScopeId) { - typeVarScopes.push(type.shared.typeVarScopeId); - } - if (type.shared.methodClass?.shared.typeVarScopeId) { - typeVarScopes.push(type.shared.methodClass.shared.typeVarScopeId); - } - returnType = makeTypeVarsFree(returnType, typeVarScopes); - - // Cache the type for next time. - type.shared.inferredReturnType = { type: returnType, isIncomplete, evaluationCount: evalCount + 1 }; - } - - // If the type is partially unknown and the function has one or more unannotated - // params, try to analyze the function with the provided argument types and - // attempt to do a better job at inference. - if ( - !isIncomplete && - analyzeUnannotatedFunctions && - isPartlyUnknown(returnType) && - FunctionType.hasUnannotatedParams(type) && - !FunctionType.isStubDefinition(type) && - !FunctionType.isPyTypedDefinition(type) && - callSiteInfo - ) { - let hasDecorators = false; - let isAsync = false; - const declNode = type.shared.declaration?.node; - if (declNode) { - if (declNode.d.decorators.length > 0) { - hasDecorators = true; - } - if (declNode.d.isAsync) { - isAsync = true; - } - } - - // We can't use this technique if decorators or async are used because they - // would need to be applied to the inferred return type. - if (!hasDecorators && !isAsync) { - const contextualReturnType = inferReturnTypeForCallSite(type, callSiteInfo); - if (contextualReturnType) { - returnType = contextualReturnType; - - if (type.shared.declaration?.node) { - // Externalize any TypeVars that appear in the type. - const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(type.shared.declaration.node); - returnType = makeTypeVarsFree(returnType, liveScopeIds); - } - } - } - } - - return { type: returnType, isIncomplete }; - } - - function inferReturnTypeForCallSite(type: FunctionType, callSiteInfo: CallSiteEvaluationInfo): Type | undefined { - const args = callSiteInfo.args; - let contextualReturnType: Type | undefined; - - if (!type.shared.declaration) { - return undefined; - } - const functionNode = type.shared.declaration.node; - const codeFlowComplexity = AnalyzerNodeInfo.getCodeFlowComplexity(functionNode); - - if (codeFlowComplexity >= maxReturnCallSiteTypeInferenceCodeFlowComplexity) { - return undefined; - } - - // If an arg hasn't been matched to a specific named parameter, - // it's an unpacked value that corresponds to multiple parameters. - // That's an edge case that we don't handle here. - if (args.some((arg) => !arg.paramName)) { - return undefined; - } - - // Detect recurrence. If a function invokes itself either directly - // or indirectly, we won't attempt to infer contextual return - // types any further. - if (returnTypeInferenceContextStack.some((context) => context.functionNode === functionNode)) { - return undefined; - } - - const functionTypeResult = getTypeOfFunction(functionNode); - if (!functionTypeResult) { - return undefined; - } - - // Very complex functions with many arguments can take a long time to analyze, - // so we'll use a heuristic and avoiding this inference technique for any - // call site that involves too many arguments. - if (args.length > maxReturnTypeInferenceArgCount) { - return undefined; - } - - // Don't explore arbitrarily deep in the call graph. - if (returnTypeInferenceContextStack.length >= maxReturnTypeInferenceStackSize) { - return undefined; - } - - const paramTypes: Type[] = []; - let isResultFromCache = false; - - // If the call is located in a loop, don't use literal argument types - // for the same reason we don't do literal math in loops. - const stripLiteralArgTypes = ParseTreeUtils.isWithinLoop(callSiteInfo.errorNode); - - // Suppress diagnostics because we don't want to generate errors. - suppressDiagnostics(functionNode, () => { - // Allocate a new temporary type cache for the context of just - // this function so we can analyze it separately without polluting - // the main type cache. - const prevTypeCache = returnTypeInferenceTypeCache; - returnTypeInferenceContextStack.push({ - functionNode, - codeFlowAnalyzer: codeFlowEngine.createCodeFlowAnalyzer(), - }); - - try { - returnTypeInferenceTypeCache = new Map(); - - let allArgTypesAreUnknown = true; - functionNode.d.params.forEach((param, index) => { - if (param.d.name) { - let paramType: Type | undefined; - const arg = args.find((arg) => param.d.name!.d.value === arg.paramName); - - if (arg && arg.argument.valueExpression) { - paramType = getTypeOfExpression(arg.argument.valueExpression).type; - if (!isUnknown(paramType)) { - allArgTypesAreUnknown = false; - } - } else if (param.d.defaultValue) { - paramType = getTypeOfExpression(param.d.defaultValue).type; - if (!isUnknown(paramType)) { - allArgTypesAreUnknown = false; - } - } else if (index === 0) { - // If this is an instance or class method, use the implied - // parameter type for the "self" or "cls" parameter. - if ( - FunctionType.isInstanceMethod(functionTypeResult.functionType) || - FunctionType.isClassMethod(functionTypeResult.functionType) - ) { - if (functionTypeResult.functionType.shared.parameters.length > 0) { - if (functionNode.d.params[0].d.name) { - paramType = FunctionType.getParamType(functionTypeResult.functionType, 0); - } - } - } - } - - if (!paramType) { - paramType = UnknownType.create(); - } - - if (stripLiteralArgTypes) { - paramType = stripTypeForm( - convertSpecialFormToRuntimeValue( - stripLiteralValue(paramType), - EvalFlags.None, - /* convertModule */ true - ) - ); - } - - paramTypes.push(paramType); - writeTypeCache(param.d.name, { type: paramType }, EvalFlags.None); - } - }); - - // Don't bother trying to determine the contextual return - // type if none of the argument types are known. - if (!allArgTypesAreUnknown) { - // See if the return type is already cached. If so, skip the - // inference step, which is potentially very expensive. - const cacheEntry = functionTypeResult.functionType.priv.callSiteReturnTypeCache?.find((entry) => { - return ( - entry.paramTypes.length === paramTypes.length && - entry.paramTypes.every((t, i) => isTypeSame(t, paramTypes[i])) - ); - }); - - if (cacheEntry) { - contextualReturnType = cacheEntry.returnType; - isResultFromCache = true; - } else { - contextualReturnType = inferFunctionReturnType( - functionNode, - FunctionType.isAbstractMethod(type), - callSiteInfo?.errorNode - )?.type; - } - } - } finally { - returnTypeInferenceContextStack.pop(); - returnTypeInferenceTypeCache = prevTypeCache; - } - }); - - if (contextualReturnType) { - contextualReturnType = removeUnbound(contextualReturnType); - - if (!isResultFromCache) { - // Cache the resulting type. - if (!functionTypeResult.functionType.priv.callSiteReturnTypeCache) { - functionTypeResult.functionType.priv.callSiteReturnTypeCache = []; - } - if ( - functionTypeResult.functionType.priv.callSiteReturnTypeCache.length >= - maxCallSiteReturnTypeCacheSize - ) { - functionTypeResult.functionType.priv.callSiteReturnTypeCache = - functionTypeResult.functionType.priv.callSiteReturnTypeCache.slice(1); - } - functionTypeResult.functionType.priv.callSiteReturnTypeCache.push({ - paramTypes, - returnType: contextualReturnType, - }); - } - - return contextualReturnType; - } - - return undefined; - } - - // If the function has an explicitly-declared return type, it is returned - // unaltered unless the function is a generator, in which case it is - // modified to return only the return type for the generator. - function getDeclaredReturnType(node: FunctionNode): Type | undefined { - const functionTypeInfo = getTypeOfFunction(node); - const returnType = functionTypeInfo?.functionType.shared.declaredReturnType; - - if (!returnType) { - return undefined; - } - - if (FunctionType.isGenerator(functionTypeInfo.functionType)) { - return getDeclaredGeneratorReturnType(functionTypeInfo.functionType); - } - - return returnType; - } - - function getTypeOfMember(member: ClassMember): Type { - if (isInstantiableClass(member.classType)) { - return partiallySpecializeType( - getEffectiveTypeOfSymbol(member.symbol), - member.classType, - getTypeClassType(), - /* selfClass */ undefined - ); - } - return UnknownType.create(); - } - - function getTypeOfMemberInternal( - errorNode: ExpressionNode | undefined, - member: ClassMember, - selfClass: ClassType | TypeVarType | undefined, - flags: MemberAccessFlags - ): TypeResult | undefined { - if (isAnyOrUnknown(member.classType)) { - return { - type: member.classType, - isIncomplete: false, - }; - } - - if (!isInstantiableClass(member.classType)) { - return undefined; - } - - const typeResult = getEffectiveTypeOfSymbolForUsage(member.symbol); - - if (!typeResult) { - return undefined; - } - - // Report inappropriate use of variables in type expressions. - if ((flags & MemberAccessFlags.TypeExpression) !== 0 && errorNode) { - typeResult.type = validateSymbolIsTypeExpression( - errorNode, - typeResult.type, - !!typeResult.includesVariableDecl - ); - } - - // If the type is a function or overloaded function, infer - // and cache the return type if necessary. This needs to be done - // prior to specializing. - inferReturnTypeIfNecessary(typeResult.type); - - // Check for ambiguous accesses to attributes with generic types? - if ( - errorNode && - selfClass && - isClass(selfClass) && - member.isInstanceMember && - isClass(member.unspecializedClassType) && - (flags & MemberAccessFlags.DisallowGenericInstanceVariableAccess) !== 0 && - requiresSpecialization(typeResult.type, { ignoreSelf: true, ignoreImplicitTypeArgs: true }) - ) { - const specializedType = partiallySpecializeType( - typeResult.type, - member.unspecializedClassType, - getTypeClassType(), - selfSpecializeClass(selfClass, { overrideTypeArgs: true }) - ); - - if ( - findSubtype( - specializedType, - (subtype) => - !isFunctionOrOverloaded(subtype) && - requiresSpecialization(subtype, { ignoreSelf: true, ignoreImplicitTypeArgs: true }) - ) - ) { - addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.genericInstanceVariableAccess(), - errorNode - ); - } - } - - return { - type: partiallySpecializeType(typeResult.type, member.classType, getTypeClassType(), selfClass), - isIncomplete: !!typeResult.isIncomplete, - }; - } - - function assignClass( - destType: ClassType, - srcType: ClassType, - diag: DiagnosticAddendum | undefined, - constraints: ConstraintTracker | undefined, - flags: AssignTypeFlags, - recursionCount: number, - reportErrorsUsingObjType: boolean - ): boolean { - // If the source or dest types are partially evaluated (i.e. they are in the - // process of being constructed), assume they are assignable rather than risk - // emitting false positives. - if (ClassType.isHierarchyPartiallyEvaluated(destType) || ClassType.isHierarchyPartiallyEvaluated(srcType)) { - return true; - } - - // Handle typed dicts. They also use a form of structural typing for type - // checking, as defined in PEP 589. - if (ClassType.isTypedDictClass(srcType)) { - if (ClassType.isTypedDictClass(destType) && !ClassType.isSameGenericClass(destType, srcType)) { - if ( - !assignTypedDictToTypedDict( - evaluatorInterface, - destType, - srcType, - diag, - constraints, - flags, - recursionCount - ) - ) { - return false; - } - - // If invariance is being enforced, the two TypedDicts must be assignable to each other. - if ((flags & AssignTypeFlags.Invariant) !== 0) { - return assignTypedDictToTypedDict( - evaluatorInterface, - srcType, - destType, - /* diag */ undefined, - /* constraints */ undefined, - flags, - recursionCount - ); - } - - return true; - } - - // Handle some special cases where a TypedDict can act like - // a Mapping[str, T] or a dict[str, T]. - if (ClassType.isBuiltIn(destType, 'Mapping')) { - const mappingValueType = getTypedDictMappingEquivalent(evaluatorInterface, srcType); - - if ( - mappingValueType && - prefetched?.mappingClass && - isInstantiableClass(prefetched.mappingClass) && - prefetched?.strClass && - isInstantiableClass(prefetched.strClass) - ) { - srcType = ClassType.specialize(prefetched.mappingClass, [ - ClassType.cloneAsInstance(prefetched.strClass), - mappingValueType, - ]); - } - } else if (ClassType.isBuiltIn(destType, ['dict', 'MutableMapping'])) { - const dictValueType = getTypedDictDictEquivalent(evaluatorInterface, srcType, recursionCount); - - if ( - dictValueType && - prefetched?.dictClass && - isInstantiableClass(prefetched.dictClass) && - prefetched.strClass && - isInstantiableClass(prefetched.strClass) - ) { - srcType = ClassType.specialize(prefetched.dictClass, [ - ClassType.cloneAsInstance(prefetched.strClass), - dictValueType, - ]); - } - } - } - - // Handle special-case type promotions. - if (destType.priv.includePromotions) { - const promotionList = typePromotions.get(destType.shared.fullName); - if ( - promotionList && - promotionList.some((srcName) => - srcType.shared.mro.some((mroClass) => isClass(mroClass) && srcName === mroClass.shared.fullName) - ) - ) { - if ((flags & AssignTypeFlags.Invariant) === 0) { - return true; - } - } - } - - // Is it a structural type (i.e. a protocol)? If so, we need to - // perform a member-by-member check. - const inheritanceChain: InheritanceChain = []; - const isDerivedFrom = ClassType.isDerivedFrom(srcType, destType, inheritanceChain); - - // Viper 扩展:CType 整数/浮点子类 -> 协议类(如 SupportsIndex/SupportsFloat)隐式转换。 - // Viper 中 CInt/CUInt32T 等 CType 整数子类语义上等价于 Python int, - // CFloat/CDouble 等 CType 浮点子类语义上等价于 Python float。 - // int 满足 SupportsIndex(有 __index__)等数值协议, - // float 满足 SupportsFloat(有 __float__)等数值协议。 - // 因此 CType 整数/浮点子类也应满足对应协议,避免误报。 - // 覆盖场景: - // - int.__new__(cls, ry: CInt) 中 ry 作为 SupportsIndex 参数传入 - // - float.__new__(cls, val: CFloat) 中 val 作为 SupportsFloat 参数传入 - if ( - ClassType.isProtocolClass(destType) && !isDerivedFrom && - isCTypeSubclassType(srcType) && ( - isViperCIntegerTypeName(srcType.shared.name) || - isViperCFloatTypeName(srcType.shared.name) - ) - ) { - return true; - } - - // Use the slow path for protocols if the dest doesn't explicitly - // derive from the source. We also need to use this path if we're - // testing to see if the metaclass matches the protocol. - if (ClassType.isProtocolClass(destType) && !isDerivedFrom) { - if ( - !assignClassToProtocol( - evaluatorInterface, - destType, - ClassType.cloneAsInstance(srcType), - diag?.createAddendum(), - constraints, - flags, - recursionCount - ) - ) { - diag?.addMessage( - LocAddendum.protocolIncompatible().format({ - sourceType: printType(convertToInstance(srcType)), - destType: printType(convertToInstance(destType)), - }) - ); - return false; - } - - return true; - } - - if ((flags & AssignTypeFlags.Invariant) === 0 || ClassType.isSameGenericClass(srcType, destType)) { - if (isDerivedFrom) { - assert(inheritanceChain.length > 0); - - if ( - assignClassWithTypeArgs( - destType, - srcType, - inheritanceChain, - diag?.createAddendum(), - constraints, - flags, - recursionCount - ) - ) { - return true; - } - } - } - - // Everything is assignable to an object. - if (ClassType.isBuiltIn(destType, 'object')) { - if ((flags & AssignTypeFlags.Invariant) === 0) { - return true; - } - } - - if (diag) { - const destErrorType = reportErrorsUsingObjType ? ClassType.cloneAsInstance(destType) : destType; - const srcErrorType = reportErrorsUsingObjType ? ClassType.cloneAsInstance(srcType) : srcType; - - let destErrorTypeText = printType(destErrorType); - let srcErrorTypeText = printType(srcErrorType); - - // If the text is the same, use the fully-qualified name rather than the short name. - if (destErrorTypeText === srcErrorTypeText && destType.shared.fullName && srcType.shared.fullName) { - destErrorTypeText = destType.shared.fullName; - srcErrorTypeText = srcType.shared.fullName; - } - - diag?.addMessage( - LocAddendum.typeIncompatible().format({ - sourceType: srcErrorTypeText, - destType: destErrorTypeText, - }) - ); - - // Tell the user about the disableBytesTypePromotions if that is involved. - if (ClassType.isBuiltIn(destType, 'bytes')) { - const promotions = typePromotions.get(destType.shared.fullName); - if (promotions && promotions.some((name) => name === srcType.shared.fullName)) { - diag?.addMessage(LocAddendum.bytesTypePromotions()); - } - } - } - - return false; - } - - // This function is used to validate or infer the variance of type - // parameters within a class. If ignoreBaseClassVariance is set to false, - // the type parameters for the base class are honored. This is useful for - // variance inference (PEP 695). For validation of protocol variance, we - // want to ignore the variance for all base classes in the class hierarchy. - function assignClassToSelf( - destType: ClassType, - srcType: ClassType, - assumedVariance: Variance, - ignoreBaseClassVariance = true, - recursionCount = 0 - ): boolean { - assert(ClassType.isSameGenericClass(destType, srcType)); - assert(destType.shared.typeParams.length > 0); - - srcType = makeTypeVarsBound(srcType, getTypeVarScopeIds(srcType)); - destType = makeTypeVarsBound(destType, getTypeVarScopeIds(destType)); - - let isAssignable = true; - - // Use a try/catch block here to make sure that we reset - // the assignClassToSelfClass to undefined if an exception occurs. - try { - // Stash the current class type so any references to it are treated - // as though all type parameters are invariant. - assignClassToSelfStack.push({ class: destType, assumedVariance }); - - ClassType.getSymbolTable(destType).forEach((symbol, name) => { - if (!isAssignable || symbol.isIgnoredForProtocolMatch()) { - return; - } - - // Constructor methods are exempt from variance calculations. - if (name === '__new__' || name === '__init__') { - return; - } - - const memberInfo = lookUpClassMember(srcType, name); - assert(memberInfo !== undefined); - - let destMemberType = getEffectiveTypeOfSymbol(symbol); - const srcMemberType = getTypeOfMember(memberInfo); - destMemberType = partiallySpecializeType(destMemberType, destType, getTypeClassType()); - - // Properties require special processing. - if ( - isClassInstance(destMemberType) && - ClassType.isPropertyClass(destMemberType) && - isClassInstance(srcMemberType) && - ClassType.isPropertyClass(srcMemberType) - ) { - if ( - !assignProperty( - evaluatorInterface, - ClassType.cloneAsInstantiable(destMemberType), - ClassType.cloneAsInstantiable(srcMemberType), - destType, - srcType, - /* diag */ undefined, - /* constraints */ undefined, - /* selfConstraints */ undefined, - recursionCount - ) - ) { - isAssignable = false; - } - } else { - const primaryDecl = symbol.getDeclarations()[0]; - - let flags = AssignTypeFlags.Default; - if ( - primaryDecl?.type === DeclarationType.Variable && - !isFinalVariableDeclaration(primaryDecl) && - !isMemberReadOnly(destType, name) - ) { - // Class and instance variables that are mutable need to - // enforce invariance. We will exempt variables that are - // private or protected, since these are presumably - // not modifiable outside of the class. - if (!isPrivateOrProtectedName(name)) { - flags |= AssignTypeFlags.Invariant; - } - } - - if ( - !assignType( - destMemberType, - srcMemberType, - /* diag */ undefined, - /* constraints */ undefined, - flags | AssignTypeFlags.SkipSelfClsParamCheck, - recursionCount - ) - ) { - isAssignable = false; - } - } - }); - - if (!isAssignable) { - return false; - } - - // Now handle generic base classes. - destType.shared.baseClasses.forEach((baseClass) => { - if ( - !isAssignable || - !isInstantiableClass(baseClass) || - ClassType.isBuiltIn(baseClass, ['object', 'Protocol', 'Generic']) || - baseClass.shared.typeParams.length === 0 - ) { - return; - } - - const specializedDestBaseClass = specializeForBaseClass(destType, baseClass); - const specializedSrcBaseClass = specializeForBaseClass(srcType, baseClass); - - if (!ignoreBaseClassVariance) { - specializedDestBaseClass.shared.typeParams.forEach((param, index) => { - if (isParamSpec(param) || isTypeVarTuple(param) || param.shared.isSynthesized) { - return; - } - - if ( - !specializedSrcBaseClass.priv.typeArgs || - index >= specializedSrcBaseClass.priv.typeArgs.length || - !specializedDestBaseClass.priv.typeArgs || - index >= specializedDestBaseClass.priv.typeArgs.length - ) { - return; - } - - const paramVariance = param.shared.declaredVariance; - if (isTypeVar(specializedSrcBaseClass.priv.typeArgs[index])) { - if (paramVariance === Variance.Invariant || paramVariance === Variance.Contravariant) { - isAssignable = false; - return; - } - } - - if (isTypeVar(specializedDestBaseClass.priv.typeArgs[index])) { - if (paramVariance === Variance.Invariant || paramVariance === Variance.Covariant) { - isAssignable = false; - return; - } - } - }); - } - - if (!isAssignable) { - return; - } - - // Handle tuples specially since their type arguments are variadic. - if (ClassType.isTupleClass(specializedDestBaseClass)) { - return; - } - - if ( - !assignClassToSelf( - specializedDestBaseClass, - specializedSrcBaseClass, - assumedVariance, - ignoreBaseClassVariance, - recursionCount - ) - ) { - isAssignable = false; - } - }); - - return isAssignable; - } finally { - assignClassToSelfStack.pop(); - } - } - - // Determines whether the specified type can be assigned to the - // specified inheritance chain, taking into account its type arguments. - function assignClassWithTypeArgs( - destType: ClassType, - srcType: ClassType, - inheritanceChain: InheritanceChain, - diag: DiagnosticAddendum | undefined, - constraints: ConstraintTracker | undefined, - flags: AssignTypeFlags, - recursionCount: number - ): boolean { - let curSrcType = srcType; - let prevSrcType: ClassType | undefined; - - inferVarianceForClass(destType); - - // If we're enforcing invariance, literal types must match. - if ((flags & AssignTypeFlags.Invariant) !== 0) { - const srcIsLiteral = isLiteralLikeType(srcType); - const destIsLiteral = isLiteralLikeType(destType); - - if (srcIsLiteral !== destIsLiteral) { - return false; - } - } - - for (let ancestorIndex = inheritanceChain.length - 1; ancestorIndex >= 0; ancestorIndex--) { - const ancestorType = inheritanceChain[ancestorIndex]; - - // If we've hit an "unknown", all bets are off, and we need to assume - // that the type is assignable. If the destType is marked "@final", - // we should be able to assume that it's not assignable, but we can't do - // this in the general case because it breaks assumptions with the - // NotImplemented symbol exported by typeshed's builtins.pyi. Instead, - // we'll special-case only None. - if (isUnknown(ancestorType)) { - return !isNoneTypeClass(destType); - } - - // If this isn't the first time through the loop, specialize - // for the next ancestor in the chain. - if (ancestorIndex < inheritanceChain.length - 1) { - // If the curSrcType is a NamedTuple and the ancestorType is a tuple, - // we need to handle this as a special case because the NamedTuple may - // include typeParams from its parent class. - let effectiveCurSrcType = curSrcType; - if ( - ClassType.isBuiltIn(curSrcType, 'NamedTuple') && - ClassType.isBuiltIn(ancestorType, 'tuple') && - prevSrcType - ) { - effectiveCurSrcType = prevSrcType; - } - - curSrcType = specializeForBaseClass(effectiveCurSrcType, ancestorType); - } - - // If there are no type parameters on this class, we're done. - const ancestorTypeParams = ClassType.getTypeParams(ancestorType); - if (ancestorTypeParams.length === 0) { - continue; - } - - // If the dest type isn't specialized, there are no type args to validate. - if (!ancestorType.priv.typeArgs) { - return true; - } - - prevSrcType = curSrcType; - } - - // Handle tuple, which supports a variable number of type arguments. - if (destType.priv.tupleTypeArgs && curSrcType.priv.tupleTypeArgs) { - return assignTupleTypeArgs( - evaluatorInterface, - destType, - curSrcType, - diag, - constraints, - flags, - recursionCount - ); - } - - if (destType.priv.typeArgs) { - // If the dest type is specialized, make sure the specialized source - // type arguments are assignable to the dest type arguments. - return assignTypeArgs( - destType, - curSrcType, - // Don't emit a diag addendum if we're in an invariant context. It's - // sufficient to simply indicate that the types are not the same - // in this case. Adding more information is unnecessary and confusing. - (flags & AssignTypeFlags.Invariant) === 0 ? diag : undefined, - constraints, - flags, - recursionCount - ); - } - - if (constraints && curSrcType.priv.typeArgs) { - // Populate the typeVar map with type arguments of the source. - const srcTypeArgs = curSrcType.priv.typeArgs; - for (let i = 0; i < destType.shared.typeParams.length; i++) { - let typeArgType: Type; - const typeParam = destType.shared.typeParams[i]; - const variance = TypeVarType.getVariance(typeParam); - - if (curSrcType.priv.tupleTypeArgs) { - typeArgType = convertToInstance( - makeTupleObject(evaluatorInterface, curSrcType.priv.tupleTypeArgs, /* isUnpacked */ true) - ); - } else { - typeArgType = i < srcTypeArgs.length ? srcTypeArgs[i] : UnknownType.create(); - } - - constraints.setBounds( - typeParam, - variance !== Variance.Contravariant ? typeArgType : undefined, - variance !== Variance.Covariant ? typeArgType : undefined, - /* retainLiterals */ true - ); - } - } - - return true; - } - - function getGetterTypeFromProperty(propertyClass: ClassType): Type | undefined { - if (!ClassType.isPropertyClass(propertyClass)) { - return undefined; - } - - if (propertyClass.priv.fgetInfo && isFunction(propertyClass.priv.fgetInfo.methodType)) { - return getEffectiveReturnType(propertyClass.priv.fgetInfo.methodType); - } - - return undefined; - } - - function assignTypeArgs( - destType: ClassType, - srcType: ClassType, - diag: DiagnosticAddendum | undefined, - constraints: ConstraintTracker | undefined, - flags: AssignTypeFlags, - recursionCount: number - ): boolean { - assert(ClassType.isSameGenericClass(destType, srcType)); - - inferVarianceForClass(destType); - - const destTypeParams = ClassType.getTypeParams(destType); - let destTypeArgs: Type[]; - let srcTypeArgs: Type[] | undefined; - - // Are we performing protocol variance validation for this class? If so, - // treat all of the type parameters as invariant even if they are declared - // otherwise. - const assignClassToSelfInfo = assignClassToSelfStack.find((info) => - ClassType.isSameGenericClass(info.class, destType) - ); - const assumedVariance = assignClassToSelfInfo?.assumedVariance; - - // If either source or dest type arguments are missing, they are - // treated as "Any", so they are assumed to be assignable. - if (!destType.priv.typeArgs || !srcType.priv.typeArgs) { - return true; - } - - if (ClassType.isTupleClass(destType)) { - destTypeArgs = destType.priv.tupleTypeArgs?.map((t) => t.type) ?? []; - srcTypeArgs = srcType.priv.tupleTypeArgs?.map((t) => t.type); - } else { - destTypeArgs = destType.priv.typeArgs; - srcTypeArgs = srcType.priv.typeArgs; - } - - let isCompatible = true; - - srcTypeArgs?.forEach((srcTypeArg, srcArgIndex) => { - // In most cases, the number of type args should match the number - // of type arguments, but there are a few special cases where this - // isn't true (e.g. assigning a Tuple[X, Y, Z] to a tuple[W]). - const destArgIndex = srcArgIndex >= destTypeArgs.length ? destTypeArgs.length - 1 : srcArgIndex; - const destTypeArg = destArgIndex >= 0 ? destTypeArgs[destArgIndex] : UnknownType.create(); - const destTypeParam = destArgIndex < destTypeParams.length ? destTypeParams[destArgIndex] : undefined; - const assignmentDiag = new DiagnosticAddendum(); - const variance = - assumedVariance ?? (destTypeParam ? TypeVarType.getVariance(destTypeParam) : Variance.Covariant); - let effectiveFlags: AssignTypeFlags; - let errorSource: () => ParameterizedString<{ name: string; sourceType: string; destType: string }>; - let includeDiagAddendum = true; - - if (variance === Variance.Covariant) { - effectiveFlags = flags | AssignTypeFlags.RetainLiteralsForTypeVar; - errorSource = LocAddendum.typeVarIsCovariant; - } else if (variance === Variance.Contravariant) { - effectiveFlags = flags | AssignTypeFlags.Contravariant | AssignTypeFlags.RetainLiteralsForTypeVar; - errorSource = LocAddendum.typeVarIsContravariant; - } else { - effectiveFlags = flags | AssignTypeFlags.Invariant | AssignTypeFlags.RetainLiteralsForTypeVar; - errorSource = LocAddendum.typeVarIsInvariant; - - // Omit the diagnostic addendum for the invariant case because it's obvious - // why two types are not the same. - includeDiagAddendum = false; - } - - // Special-case TypeForm to retain literals when solving TypeVars. - if (ClassType.isBuiltIn(destType, 'TypeForm')) { - effectiveFlags |= AssignTypeFlags.RetainLiteralsForTypeVar; - } - - if ( - !assignType( - variance === Variance.Contravariant ? srcTypeArg : destTypeArg, - variance === Variance.Contravariant ? destTypeArg : srcTypeArg, - assignmentDiag, - constraints, - effectiveFlags, - recursionCount - ) - ) { - // Don't report errors with type variables in "pseudo-random" - // classes since these type variables are not real. - if (!ClassType.isPseudoGenericClass(destType)) { - if (diag) { - if (destTypeParam) { - const childDiag = diag.createAddendum(); - - childDiag.addMessage( - errorSource().format({ - name: TypeVarType.getReadableName(destTypeParam), - ...printSrcDestTypes(srcTypeArg, destTypeArg), - }) - ); - - if (includeDiagAddendum) { - childDiag.addAddendum(assignmentDiag); - } - - if (isCompatible && ClassType.isSameGenericClass(destType, srcType)) { - // Add additional notes to help the user if this is a common type mismatch. - if (ClassType.isBuiltIn(destType, 'dict') && srcArgIndex === 1) { - childDiag.addMessage(LocAddendum.invariantSuggestionDict()); - } else if (ClassType.isBuiltIn(destType, 'list')) { - childDiag.addMessage(LocAddendum.invariantSuggestionList()); - } else if (ClassType.isBuiltIn(destType, 'set')) { - childDiag.addMessage(LocAddendum.invariantSuggestionSet()); - } - } - } else { - diag.addAddendum(assignmentDiag); - } - } - isCompatible = false; - } - } - }); - - return isCompatible; - } - - // Determines if the source type can be assigned to the dest type. - // If constraint are provided, type variables within the destType are - // matched against existing type variables in the map. If a type variable - // in the dest type is not in the type map already, it is assigned a type - // and added to the map. - function assignType( - destType: Type, - srcType: Type, - diag?: DiagnosticAddendum, - constraints?: ConstraintTracker, - flags = AssignTypeFlags.Default, - recursionCount = 0 - ): boolean { - // Viper 扩展:t.CDefine 任意转参转换语义。 - // CDefine 是编译时元指令,类型检查时视为"任意类型": - // - destType 是 CDefine:接受任何源类型(x: t.CDefine = 任意值) - // - srcType 是 CDefine:CDefine 值可传给任何参数(func(t.CDefine_value)) - if (isCDefineType(destType) || isCDefineType(srcType)) { - return true; - } - - // Viper 扩展:None → 指针类型隐式转换语义(NULL 指针)。 - // 在 C 语义中,NULL 宏等价于 (void*)0,用于表示空指针。 - // Viper 中 None 对应 C 的 NULL,可赋值给任何指针类型: - // - CPtr[T](任意指针) - // - CVoid(void*) - // - 包含 CPtr/CVoid 的 TypeSet(如 CVoid | CPtr) - // 这允许 _send_control(..., data_buf: CVoid | CPtr) 接受 None 作为参数。 - // CPtr/CVoid 是 Viper 特有类型,目标包含它们即为 Viper 代码,无需 dialect 检查。 - // 函数指针类型(FunctionType,如 irq_handler_t = t.Callable[[], t.CInt])同样可接受 - // None(NULL 函数指针语义),允许 def isr0() -> irq_handler_t: pass 或 return None。 - // 返回类型包含 C 声明标记(t.State/t.CExport 等)的也接受 None(无返回值), - // 允许 def CloseHandle() -> BOOL | t.State: pass 这类声明性函数的隐式 None。 - // str/bytes 在 Viper 中表示 C 字符串指针(i8*),同样接受 None(NULL 字符串指针), - // 允许 def strcpy(...) -> str: ... return None 这类返回 str 的函数返回空指针。 - if (isNoneInstance(srcType)) { - if (isPureCModifierType(destType)) { - return true; - } - const destVariants = isUnion(destType) ? destType.priv.subtypes : [destType]; - const hasPtrLike = destVariants.some( - (variant) => - isCPtrType(variant) || - isCVoidType(variant) || - isFunction(variant) || - (isClassInstance(variant) && ClassType.isBuiltIn(variant, ['str', 'bytes'])) - ); - if (hasPtrLike) { - return true; - } - } - - // Viper 扩展:t.CArray 自动降级语义。 - // C 字符数组 t.CArray[t.CChar, N] 可自动降级为 C 字符串 str 或字节序列 bytes, - // 实现 serial.puts(ncpu_cr) 等 API 的兼容传参。 - // 降级方向:CArray → str/bytes(单向,str/bytes 不自动升级为 CArray)。 - if (isCArrayType(srcType)) { - const destIsStrBytes = - (isClassInstance(destType) && ClassType.isBuiltIn(destType, 'str')) || - (isClassInstance(destType) && ClassType.isBuiltIn(destType, 'bytes')); - if (destIsStrBytes) { - return true; - } - } - - // Viper 扩展:函数名退化为函数指针(C 语义)。 - // C 语言中函数名在需要函数指针的位置自动退化为指针,如 - // create_thread(desktop_thread, ...) 中 desktop_thread 退化为 CPtr 传入 func: CVoid | CPtr。 - // 类似数组名退化为指针,函数名退化为函数指针是 C 标准行为。 - if (isFunction(srcType)) { - const destVariants = isUnion(destType) ? destType.priv.subtypes : [destType]; - const hasPtrLike = destVariants.some( - (variant) => isCPtrType(variant) || isCVoidType(variant) - ); - if (hasPtrLike) { - return true; - } - } - - // Viper 扩展:跨级指针级数守卫(双向,精确同级匹配)。 - // 规则:允许同级指针目标类型隐式转换;任何不同级的指针间隐式转换都拒绝, - // 无论 src 高→dest 低 还是 src 低→dest 高("不是同级都不行")。 - // 实现:src 为非联合指针时,收集 dest 所有指针子类型的级数集合, - // 若 dest 含指针但无与 src 同级者,则跨级拒绝。 - // 联合 src(含 TypeSet)由 assignType 后续拆分逻辑递归处理,子类型重新进入本守卫。 - // 命中场景: - // - a: t.CPtr = t.CPtr(x, t.CPtr) (src 2级,dest 指针级数集合 {1}) - // - a: t.CPtr[t.CPtr[T]] = t.CPtr(x) (src 1级,dest 指针级数集合 {2}) - // - t.CPtr(x, t.CPtr) 传 arg: CVoid|CPtr (src 2级,dest 指针级数集合 {1}) - // 不影响:值↔值、同级指针、值↔指针现有逻辑(dest 无指针子类型时不介入)。 - // 多级 CPtr 是 Viper 特有类型,无需 dialect 检查。 - if (!isUnion(srcType)) { - const srcPtrLevel = getCPtrLevel(srcType); - if (srcPtrLevel >= 1) { - const destVariants = isUnion(destType) ? destType.priv.subtypes : [destType]; - const destPtrLevels = new Set(); - for (const variant of destVariants) { - const lvl = getCPtrLevel(variant); - if (lvl >= 1) { - destPtrLevels.add(lvl); - } - } - // dest 含指针但无与 src 同级者 → 跨级拒绝 - if (destPtrLevels.size > 0 && !destPtrLevels.has(srcPtrLevel)) { - diag?.addMessage( - `跨级指针隐式转换不允许:源为 ${srcPtrLevel} 级指针,` + - `目标指针级数为 [${Array.from(destPtrLevels).join(', ')}](Viper 要求同级)` - ); - return false; - } - } - } - - // Viper 扩展:整数 ↔ 指针隐式转换语义。 - // 在 C/Viper 语义中,整数(地址值)与指针类型可相互转换: - // - 整数地址 → CPtr/CVoid(如 framebuffer_addr: CUnsignedLong 赋值给 CVoid | CPtr) - // - 指针 → 整数(如 uintptr_t = (uintptr_t)ptr) - // 这模拟 C 语言的整数-指针转换规则。 - const destIsPtrLike = isCPtrType(destType) || isCVoidType(destType); - const srcIsPtrLike = isCPtrType(srcType) || isCVoidType(srcType); - if (destIsPtrLike && isViperIntegerType(srcType)) { - return true; - } - if (srcIsPtrLike && isViperIntegerType(destType)) { - return true; - } - - // Viper 扩展:字符串/字节字面量 → C 字符指针联合类型的转换。 - // 在 Viper 语义中,str/bytes 可表示 i8*(char*),因此可赋值给包含 - // CConst/CPtr/CVoid 的联合类型(如 CConst | CChar | CPtr 表示 const char*)。 - // 这允许 msg: t.CConst | t.CChar | t.CPtr = "Hello" 这样的赋值。 - // CConst/CPtr/CVoid 是 Viper 特有类型,目标包含它们即说明是 Viper 代码。 - const srcIsStrBytes = isClassInstance(srcType) && - (ClassType.isBuiltIn(srcType, 'str') || ClassType.isBuiltIn(srcType, 'bytes')); - if (srcIsStrBytes) { - const destVariants = isUnion(destType) ? destType.priv.subtypes : [destType]; - const hasCStringLike = destVariants.some((variant) => - isCConstType(variant) || isCPtrType(variant) || isCVoidType(variant) - ); - if (hasCStringLike) { - return true; - } - } - - // Viper 扩展:CPtr[CChar] → str/bytes 转换(char* → C 字符串)。 - // C 语义中 char* 可以赋值给字符串变量(如 c.Addr(arr[0]) → str 参数)。 - // 覆盖场景:c.Addr(win.title[0]) 返回 CPtr[CChar],赋值给 _draw_text 的 str 参数。 - // CPtr 是 Viper 特有类型,源包含它即为 Viper 代码,无需 dialect 检查。 - if ( - isClassInstance(srcType) && isCPtrType(srcType) && - srcType.priv.typeArgs && srcType.priv.typeArgs.length > 0 - ) { - const targetType = srcType.priv.typeArgs[0]; - if (isClassInstance(targetType)) { - const targetName = targetType.shared.name; - if ( - targetName === 'CChar' || targetName === 'CSignedChar' || - targetName === 'CUnsignedChar' || targetName === 'CChar8T' || - targetName === 'CChar16T' || targetName === 'CChar32T' || - targetName === 'CWCharT' - ) { - const destIsStrBytes = - (isClassInstance(destType) && ClassType.isBuiltIn(destType, 'str')) || - (isClassInstance(destType) && ClassType.isBuiltIn(destType, 'bytes')); - if (destIsStrBytes) { - return true; - } - } - } - } - + // Viper 扩展:构造带 t.CChar 类型别名信息的 int 类型(表示 C 的 char 类型)。 + // Viper 中 str/bytes 是 i8* 指针,下标访问/迭代/字符运算返回 t.CChar(int 别名)。 + // 优先从 t 模块查找 CChar 类型别名以保留显示信息;找不到时回退为 + // 构造带 typeAliasInfo 的 int 类型。查找失败返回 undefined。 + function getViperCCharType(node: ExpressionNode): Type | undefined { + const tSymbolWithScope = lookUpSymbolRecursive(node, 't', /* honorCodeFlow */ true); + if (tSymbolWithScope) { + const tEffectiveType = getEffectiveTypeOfSymbolForUsage( + tSymbolWithScope.symbol, + /* usageNode */ undefined, + /* useLastDecl */ true + ).type; + if (tEffectiveType && isModule(tEffectiveType)) { + const ccharSymbol = ModuleType.getField(tEffectiveType, 'CChar'); + if (ccharSymbol) { + // 优先使用声明类型(保留 typeAliasInfo) + const ccharDeclType = getDeclaredTypeOfSymbol(ccharSymbol)?.type; + if (ccharDeclType) { + return convertToInstance(ccharDeclType); + } + // 回退到有效类型 + const ccharType = getEffectiveTypeOfSymbolForUsage( + ccharSymbol, + /* usageNode */ undefined, + /* useLastDecl */ true + ).type; + if (ccharType) { + return convertToInstance(ccharType); + } + } + } + } + // 回退:构造带 CChar 别名信息的 int 类型 + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + const intType = getBuiltInObject(node, 'int'); + const ccharAliasInfo: TypeAliasInfo = { + shared: { + name: 'CChar', + fullName: 't.CChar', + moduleName: 't', + fileUri: fileInfo.fileUri, + typeVarScopeId: '', + isTypeAliasType: false, + typeParams: undefined, + computedVariance: undefined, + }, + typeArgs: undefined, + }; + return TypeBase.cloneForTypeAlias(intType, ccharAliasInfo); + } + + function getTypeOfIndexedObjectOrClass( + node: IndexNode, + baseType: ClassType, + selfType: ClassType | TypeVarType | undefined, + usage: EvaluatorUsage + ): TypeResult { + // Handle index operations for TypedDict classes specially. + if (isClassInstance(baseType) && ClassType.isTypedDictClass(baseType)) { + const typeFromTypedDict = getTypeOfIndexedTypedDict(evaluatorInterface, node, baseType, usage); + if (typeFromTypedDict) { + return typeFromTypedDict; + } + } + + const magicMethodName = getIndexAccessMagicMethodName(usage); + + // Viper 扩展:CPtr[T] 指针下标解引用语义。 + // 当对 CPtr[T] 实例执行下标访问 ptr[i] 时(模拟 C 语言的 ptr[i] = *(ptr + i)): + // 1. 如果 T 本身有 __getitem__/__setitem__/__delitem__,优先调用 T 的方法 + // (例如 T 是 CArray,ptr[i] 应转发到 CArray.__getitem__) + // 2. 否则,指针下标直接返回 T 类型(解引用语义) + // 这与成员访问(getTypeOfMemberAccessWithBaseType 第 5963 行)的 CPtr 解引用逻辑对应。 + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + if ( + IsViperDialect(fileInfo.executionEnvironment.dialect) && + isCPtrType(baseType) && + baseType.priv.typeArgs && + baseType.priv.typeArgs.length > 0 + ) { + const pointeeType = baseType.priv.typeArgs[0]; + if (isClass(pointeeType)) { + // T 可能是类对象(如 CPtr[Scheduler])或类实例,统一转为实例 + const pointeeInstance = TypeBase.isInstance(pointeeType) + ? pointeeType + : ClassType.cloneAsInstance(pointeeType); + // 检查 T 是否有自己的 item access magic method + const pointeeMethodType = getBoundMagicMethod( + pointeeInstance, + magicMethodName, + selfType, + node.d.leftExpr + ); + if (pointeeMethodType) { + // T 有自己的 __getitem__ 等,转发给 T(将 baseType 替换为 T 实例) + baseType = pointeeInstance; + } else { + // T 没有 __getitem__ 等,指针下标返回 T 类型(解引用) + return { type: pointeeInstance }; + } + } + } + + const itemMethodType = getBoundMagicMethod(baseType, magicMethodName, selfType, node.d.leftExpr); + + if (!itemMethodType) { + // Viper 扩展:类型组合(TypeSet)语义下的静默跳过。 + // Viper 中 `|` 表示类型组合(如 CConst | str = const char*), + // 而非 Python 的联合类型(任选其一)。修饰类型(CConst/CStatic/CPtr/ + // CVoid/CDefine/CArray)本身不提供 __getitem__ 等方法,但联合类型中 + // 其他子类型(如 str)会提供。此处对修饰类型静默返回 Unknown, + // 不报错,让 mapSubtypesExpandTypeVars 处理其他子类型时获得正确方法。 + if ( + IsViperDialect(fileInfo.executionEnvironment.dialect) && + (isCConstType(baseType) || isCStaticType(baseType) || isCPtrType(baseType) || isCVoidType(baseType) || + isCDefineType(baseType) || isCArrayType(baseType)) + ) { + return { type: UnknownType.create() }; + } + addDiagnostic( + DiagnosticRule.reportIndexIssue, + LocMessage.methodNotDefinedOnType().format({ + name: magicMethodName, + type: printType(baseType), + }), + node.d.leftExpr + ); + return { type: UnknownType.create() }; + } + + // Handle the special case where the object is a tuple and + // the index is a constant number (integer) or a slice with integer + // start and end values. In these cases, we can determine + // the exact type by indexing into the tuple type array. + if ( + node.d.items.length === 1 && + !node.d.trailingComma && + !node.d.items[0].d.name && + node.d.items[0].d.argCategory === ArgCategory.Simple && + isClassInstance(baseType) + ) { + const index0Expr = node.d.items[0].d.valueExpr; + const valueType = getTypeOfExpression(index0Expr).type; + + if ( + isClassInstance(valueType) && + ClassType.isBuiltIn(valueType, 'int') && + isLiteralType(valueType) && + typeof valueType.priv.literalValue === 'number' + ) { + const indexValue = valueType.priv.literalValue; + const tupleType = getSpecializedTupleType(baseType); + + if (tupleType && tupleType.priv.tupleTypeArgs) { + if (isTupleIndexUnambiguous(tupleType, indexValue)) { + if (indexValue >= 0 && indexValue < tupleType.priv.tupleTypeArgs.length) { + return { type: tupleType.priv.tupleTypeArgs[indexValue].type }; + } else if (indexValue < 0 && tupleType.priv.tupleTypeArgs.length + indexValue >= 0) { + return { + type: tupleType.priv.tupleTypeArgs[tupleType.priv.tupleTypeArgs.length + indexValue] + .type, + }; + } + } + } + } else if (isClassInstance(valueType) && ClassType.isBuiltIn(valueType, 'slice')) { + const tupleType = getSpecializedTupleType(baseType); + + if (tupleType && index0Expr.nodeType === ParseNodeType.Slice) { + const slicedTupleType = getSlicedTupleType(evaluatorInterface, tupleType, index0Expr); + if (slicedTupleType) { + return { type: slicedTupleType }; + } + } + } + } + + const positionalArgs = node.d.items.filter((item) => item.d.argCategory === ArgCategory.Simple); + const unpackedListArgs = node.d.items.filter((item) => item.d.argCategory === ArgCategory.UnpackedList); + + let positionalIndexType: Type; + let isPositionalIndexTypeIncomplete = false; + + if (positionalArgs.length === 1 && unpackedListArgs.length === 0 && !node.d.trailingComma) { + // Handle the common case where there is a single positional argument. + const typeResult = getTypeOfExpression(positionalArgs[0].d.valueExpr); + positionalIndexType = typeResult.type; + if (typeResult.isIncomplete) { + isPositionalIndexTypeIncomplete = true; + } + } else { + // Package up all of the positionals into a tuple. + const tupleTypeArgs: TupleTypeArg[] = []; + + const getDeterministicTupleEntries = (type: Type): TupleTypeArg[] | undefined => { + let aggregatedArgs: TupleTypeArg[] | undefined; + let isDeterministic = true; + + doForEachSubtype(type, (subtype) => { + if (!isDeterministic) { + return; + } + + const tupleType = getSpecializedTupleType(subtype); + const tupleTypeArgs = tupleType?.priv.tupleTypeArgs; + + if ( + !tupleTypeArgs || + tupleTypeArgs.some((entry) => entry.isUnbounded || isTypeVarTuple(entry.type)) + ) { + isDeterministic = false; + return; + } + + if (!aggregatedArgs) { + aggregatedArgs = tupleTypeArgs.map((entry) => ({ type: entry.type, isUnbounded: false })); + return; + } + + if (aggregatedArgs.length !== tupleTypeArgs.length) { + isDeterministic = false; + return; + } + + for (let i = 0; i < aggregatedArgs.length; i++) { + aggregatedArgs[i] = { + type: combineTypes([aggregatedArgs[i].type, tupleTypeArgs[i].type]), + isUnbounded: false, + }; + } + }); + + if (!isDeterministic || !aggregatedArgs) { + return undefined; + } + + return aggregatedArgs; + }; + + node.d.items.forEach((arg) => { + if (arg.d.argCategory === ArgCategory.Simple) { + const typeResult = getTypeOfExpression(arg.d.valueExpr); + tupleTypeArgs.push({ type: typeResult.type, isUnbounded: false }); + if (typeResult.isIncomplete) { + isPositionalIndexTypeIncomplete = true; + } + return; + } + + if (arg.d.argCategory === ArgCategory.UnpackedList) { + const typeResult = getTypeOfExpression(arg.d.valueExpr); + if (typeResult.isIncomplete) { + isPositionalIndexTypeIncomplete = true; + } + + const deterministicEntries = getDeterministicTupleEntries(typeResult.type); + if (deterministicEntries) { + appendArray(tupleTypeArgs, deterministicEntries); + return; + } + + const iterableType = + getTypeOfIterator(typeResult, /* isAsync */ false, arg.d.valueExpr)?.type ?? + UnknownType.create(); + tupleTypeArgs.push({ type: iterableType, isUnbounded: true }); + } + }); + + const unboundedCount = tupleTypeArgs.filter((typeArg) => typeArg.isUnbounded).length; + if (unboundedCount > 1) { + const firstUnboundedIndex = tupleTypeArgs.findIndex((typeArg) => typeArg.isUnbounded); + const removedEntries = tupleTypeArgs.splice(firstUnboundedIndex); + tupleTypeArgs.push({ + type: combineTypes(removedEntries.map((entry) => entry.type)), + isUnbounded: true, + }); + } + + positionalIndexType = makeTupleObject(evaluatorInterface, tupleTypeArgs); + } + + const argList: Arg[] = [ + { + argCategory: ArgCategory.Simple, + typeResult: { type: positionalIndexType, isIncomplete: isPositionalIndexTypeIncomplete }, + }, + ]; + + if (usage.method === 'set') { + let setType = usage.setType?.type ?? AnyType.create(); + + // Expand constrained type variables. + if (isTypeVar(setType) && TypeVarType.hasConstraints(setType)) { + const conditionFilter = isClassInstance(baseType) ? baseType.props?.condition : undefined; + setType = makeTopLevelTypeVarsConcrete( + setType, + /* makeParamSpecsConcrete */ undefined, + conditionFilter + ); + } + + argList.push({ + argCategory: ArgCategory.Simple, + typeResult: { + type: setType, + isIncomplete: !!usage.setType?.isIncomplete, + }, + }); + } + + const callResult = validateCallArgs( + node, + argList, + { type: itemMethodType }, + /* constraints */ undefined, + /* skipUnknownArgCheck */ true, + /* inferenceContext */ undefined + ); + + return { + type: callResult.returnType ?? UnknownType.create(), + isIncomplete: !!callResult.isTypeIncomplete, + }; + } + + function getTypeArgs(node: IndexNode, flags: EvalFlags, options?: GetTypeArgsOptions): TypeResultWithNode[] { + const typeArgs: TypeResultWithNode[] = []; + let adjFlags = flags | EvalFlags.NoConvertSpecialForm; + adjFlags &= ~EvalFlags.TypeFormArg; + + const allowFinalClassVar = () => { + // If the annotation is a variable within the body of a dataclass, a + // Final is allowed with a ClassVar annotation. In all other cases, + // it's disallowed. + const enclosingClassNode = ParseTreeUtils.getEnclosingClass(node, /* stopeAtFunction */ true); + if (enclosingClassNode) { + const classTypeInfo = getTypeOfClass(enclosingClassNode); + if (classTypeInfo && ClassType.isDataClass(classTypeInfo.classType)) { + return true; + } + } + return false; + }; + + if (options?.isFinalAnnotation) { + adjFlags |= EvalFlags.NoFinal; + + if (!allowFinalClassVar()) { + adjFlags |= EvalFlags.NoClassVar; + } + } else if (options?.isClassVarAnnotation) { + adjFlags |= EvalFlags.NoClassVar; + + if (!allowFinalClassVar()) { + adjFlags |= EvalFlags.NoFinal; + } + } else { + adjFlags &= ~( + EvalFlags.NoSpecialize | + EvalFlags.NoParamSpec | + EvalFlags.NoTypeVarTuple | + EvalFlags.AllowRequired | + EvalFlags.EnforceVarianceConsistency + ); + + if (!options?.isAnnotatedClass) { + adjFlags |= EvalFlags.NoClassVar | EvalFlags.NoFinal; + } + + adjFlags |= EvalFlags.AllowUnpackedTuple | EvalFlags.AllowConcatenate; + } + + // Create a local function that validates a single type argument. + const getTypeArgTypeResult = (expr: ExpressionNode, argIndex: number) => { + let typeResult: TypeResultWithNode; + + // If it's a custom __class_getitem__, none of the arguments should be + // treated as types. + if (options?.hasCustomClassGetItem) { + adjFlags = + EvalFlags.NoParamSpec | EvalFlags.NoTypeVarTuple | EvalFlags.NoSpecialize | EvalFlags.NoClassVar; + typeResult = { + ...getTypeOfExpression(expr, adjFlags), + node: expr, + }; + } else if (options?.isAnnotatedClass && argIndex > 0) { + // If it's an Annotated[a, b, c], only the first index should be + // treated as a type. The others can be regular (non-type) objects. + adjFlags = + EvalFlags.NoParamSpec | EvalFlags.NoTypeVarTuple | EvalFlags.NoSpecialize | EvalFlags.NoClassVar; + if (isAnnotationEvaluationPostponed(AnalyzerNodeInfo.getFileInfo(node))) { + adjFlags |= EvalFlags.ForwardRefs; + } + + typeResult = { + ...getTypeOfExpression(expr, adjFlags), + node: expr, + }; + } else { + typeResult = getTypeArg(expr, adjFlags, !!options?.supportsTypedDictTypeArg && argIndex === 0); + } + + return typeResult; + }; + + // A tuple is treated the same as a list of items in the index. + if ( + node.d.items.length === 1 && + !node.d.trailingComma && + !node.d.items[0].d.name && + node.d.items[0].d.valueExpr.nodeType === ParseNodeType.Tuple + ) { + node.d.items[0].d.valueExpr.d.items.forEach((item, index) => { + typeArgs.push(getTypeArgTypeResult(item, index)); + }); + + // Set the node's type so it isn't reevaluated later. + setTypeResultForNode(node.d.items[0].d.valueExpr, { type: UnknownType.create() }); + + return typeArgs; + } + + node.d.items.forEach((arg, index) => { + const typeResult = getTypeArgTypeResult(arg.d.valueExpr, index); + + if (arg.d.argCategory !== ArgCategory.Simple) { + if (arg.d.argCategory === ArgCategory.UnpackedList) { + if (!options?.isAnnotatedClass || index === 0) { + const unpackedType = applyUnpackToTupleLike(typeResult.type); + + if (unpackedType) { + typeResult.type = unpackedType; + } else { + if ((flags & EvalFlags.TypeExpression) !== 0) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.unpackNotAllowed(), + arg.d.valueExpr + ); + typeResult.typeErrors = true; + } else { + typeResult.type = UnknownType.create(); + } + } + } + } + } + + if (arg.d.name) { + if ((flags & EvalFlags.TypeExpression) !== 0) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.keywordArgInTypeArgument(), + arg.d.valueExpr + ); + typeResult.typeErrors = true; + } else { + typeResult.type = UnknownType.create(); + } + } + + if ( + arg.d.valueExpr.nodeType !== ParseNodeType.Error || + arg.d.valueExpr.d.category !== ErrorExpressionCategory.MissingIndexOrSlice + ) { + typeArgs.push(typeResult); + } + }); + + return typeArgs; + } + + function applyUnpackToTupleLike(type: Type): Type | undefined { + if (isTypeVarTuple(type)) { + if (!type.priv.isUnpacked) { + return TypeVarType.cloneForUnpacked(type); + } + + return undefined; + } + + if (isParamSpec(type)) { + return undefined; + } + + // Is this a TypeVar that has a tuple upper bound? + if (isTypeVar(type)) { + const upperBound = type.shared.boundType; + + if (upperBound && isClassInstance(upperBound) && isTupleClass(upperBound)) { + return TypeVarType.cloneForUnpacked(type); + } + + return undefined; + } + + if (isInstantiableClass(type) && !type.priv.includeSubclasses) { + if (isTupleClass(type)) { + return ClassType.cloneForUnpacked(type); + } + } + + return undefined; + } + + function getTypeArg(node: ExpressionNode, flags: EvalFlags, supportsDictExpression: boolean): TypeResultWithNode { + let typeResult: TypeResultWithNode; + + let adjustedFlags = + flags | EvalFlags.InstantiableType | EvalFlags.ConvertEllipsisToAny | EvalFlags.StrLiteralAsType; + + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + if (fileInfo.isStubFile) { + adjustedFlags |= EvalFlags.ForwardRefs; + } + + if (node.nodeType === ParseNodeType.List) { + typeResult = { + type: UnknownType.create(), + typeList: node.d.items.map((entry) => { + return { ...getTypeOfExpression(entry, adjustedFlags), node: entry }; + }), + node, + }; + + // Set the node's type so it isn't reevaluated later. + setTypeResultForNode(node, { type: UnknownType.create() }); + } else if (node.nodeType === ParseNodeType.Dictionary && supportsDictExpression) { + const inlinedTypeDict = + prefetched?.typedDictClass && isInstantiableClass(prefetched.typedDictClass) + ? createTypedDictTypeInlined(evaluatorInterface, node, prefetched.typedDictClass) + : undefined; + const keyTypeFallback = + prefetched?.strClass && isInstantiableClass(prefetched.strClass) + ? prefetched.strClass + : UnknownType.create(); + + typeResult = { + type: keyTypeFallback, + inlinedTypeDict, + node, + }; + } else { + typeResult = { ...getTypeOfExpression(node, adjustedFlags), node }; + + if (node.nodeType === ParseNodeType.Dictionary) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.dictInAnnotation(), node); + } + + if ((flags & EvalFlags.NoClassVar) !== 0) { + // "ClassVar" is not allowed as a type argument. + if (isClass(typeResult.type) && ClassType.isBuiltIn(typeResult.type, 'ClassVar')) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.classVarNotAllowed(), node); + } + } + } + + return typeResult; + } + + function buildTupleTypesList( + entryTypeResults: TypeResult[], + stripLiterals: boolean, + convertModule: boolean + ): TupleTypeArg[] { + const entryTypes: TupleTypeArg[] = []; + + for (const typeResult of entryTypeResults) { + let possibleUnpackedTuple: Type | undefined; + if (typeResult.unpackedType) { + possibleUnpackedTuple = typeResult.unpackedType; + } else if (isUnpacked(typeResult.type)) { + possibleUnpackedTuple = typeResult.type; + } + + // Is this an unpacked tuple? If so, we can append the individual + // unpacked entries onto the new tuple. If it's not an upacked tuple + // but some other iterator (e.g. a List), we won't know the number of + // items, so we'll need to leave the Tuple open-ended. + if ( + possibleUnpackedTuple && + isClassInstance(possibleUnpackedTuple) && + possibleUnpackedTuple.priv.tupleTypeArgs + ) { + const typeArgs = possibleUnpackedTuple.priv.tupleTypeArgs; + + if (!typeArgs) { + entryTypes.push({ type: UnknownType.create(), isUnbounded: true }); + } else { + appendArray(entryTypes, typeArgs); + } + } else if (isNever(typeResult.type) && typeResult.isIncomplete && !typeResult.unpackedType) { + entryTypes.push({ type: UnknownType.create(/* isIncomplete */ true), isUnbounded: false }); + } else { + let entryType = convertSpecialFormToRuntimeValue(typeResult.type, EvalFlags.None, convertModule); + entryType = stripLiterals ? stripTypeForm(stripLiteralValue(entryType)) : entryType; + entryTypes.push({ type: entryType, isUnbounded: !!typeResult.unpackedType }); + } + } + + // If there are multiple unbounded entries, combine all of them into a single + // unbounded entry to avoid violating the invariant that there can be at most + // one unbounded entry in a tuple. + if (entryTypes.filter((t) => t.isUnbounded).length > 1) { + const firstUnboundedEntryIndex = entryTypes.findIndex((t) => t.isUnbounded); + const removedEntries = entryTypes.splice(firstUnboundedEntryIndex); + entryTypes.push({ type: combineTypes(removedEntries.map((t) => t.type)), isUnbounded: true }); + } + + return entryTypes; + } + + // Viper 扩展:判断调用节点是否为 Viper C 整数类型的构造调用。 + // 支持两种形式: + // 1. 直接名称调用:CUInt32T(addr, t.CPtr) + // 2. 成员访问调用:t.CUInt32T(addr, t.CPtr) + // 通过提取调用表达式左侧的名称(CUInt32T),查询 _viperCIntegerTypeNames 白名单判断。 + function isViperCIntegerCall(node: CallNode): boolean { + const leftExpr = node.d.leftExpr; + let name: string | undefined; + + if (leftExpr.nodeType === ParseNodeType.Name) { + // 形式 1:直接名称调用 + name = leftExpr.d.value; + } else if (leftExpr.nodeType === ParseNodeType.MemberAccess) { + // 形式 2:成员访问调用(如 t.CUInt32T) + name = leftExpr.d.member.d.value; + } + + return name !== undefined && isViperCIntegerTypeName(name); + } + + // Viper 扩展:判断调用节点是否为 __sizeof__ 方法调用。 + // 支持两种形式: + // 1. 类调用:Type.__sizeof__() + // 2. 实例调用:instance.__sizeof__() + // Viper 中 __sizeof__ 是类方法(sizeof(Type) -> int),不需要 self 参数。 + function isViperSizeOfCall(node: CallNode): boolean { + const leftExpr = node.d.leftExpr; + if (leftExpr.nodeType === ParseNodeType.MemberAccess) { + return leftExpr.d.member.d.value === '__sizeof__'; + } + return false; + } + + // Viper 扩展:检查类型注解 AST 节点是否包含 CPtr 引用。 + // 用于判断循环引用是否被指针打破(如 next: 'Node' | t.CPtr)。 + // 递归遍历类型注解表达式,查找名称为 CPtr/CNeedPtr/CAutoPtr/CArrayPtr 的标识符。 + function typeAnnotationContainsCPtr(node: ExpressionNode): boolean { + return checkCPtrRecursively(node); + } + + function checkCPtrRecursively(node: ParseNode): boolean { + if (!node) { + return false; + } + + // 检查名称节点是否为 CPtr 系列 + if (node.nodeType === ParseNodeType.Name) { + const name = (node as NameNode).d.value; + return name === 'CPtr' || name === 'CNeedPtr' || name === 'CAutoPtr' || name === 'CArrayPtr'; + } + + // 检查成员访问节点(如 t.CPtr),检查成员名 + if (node.nodeType === ParseNodeType.MemberAccess) { + const memberAccess = node as MemberAccessNode; + const memberName = memberAccess.d.member.d.value; + if ( + memberName === 'CPtr' || memberName === 'CNeedPtr' || + memberName === 'CAutoPtr' || memberName === 'CArrayPtr' + ) { + return true; + } + // 继续检查左侧表达式 + return checkCPtrRecursively(memberAccess.d.leftExpr); + } + + // 检查二进制操作节点(如 'Node' | t.CPtr),递归检查两侧 + if (node.nodeType === ParseNodeType.BinaryOperation) { + const binaryOp = node as BinaryOperationNode; + return checkCPtrRecursively(binaryOp.d.leftExpr) || checkCPtrRecursively(binaryOp.d.rightExpr); + } + + // 检查索引节点(如 CPtr[Node]),递归检查左侧和索引项 + if (node.nodeType === ParseNodeType.Index) { + const indexNode = node as IndexNode; + if (checkCPtrRecursively(indexNode.d.leftExpr)) { + return true; + } + for (const item of indexNode.d.items) { + if (checkCPtrRecursively(item.d.valueExpr)) { + return true; + } + } + return false; + } + + // 检查字符串列表节点(字符串注解如 'Node'),检查字符串内容 + if (node.nodeType === ParseNodeType.StringList) { + const stringList = node as StringListNode; + for (const stringNode of stringList.d.strings) { + if (stringNode.nodeType === ParseNodeType.String) { + const strValue = (stringNode as any).d?.value ?? ''; + // 检查字符串注解中是否包含 CPtr 引用 + if (/\b(CPtr|CNeedPtr|CAutoPtr|CArrayPtr)\b/.test(strValue)) { + return true; + } + } + } + return false; + } + + // 检查类型注解节点(如 x: Type),递归检查类型表达式 + if (node.nodeType === ParseNodeType.TypeAnnotation) { + const typeAnnot = node as TypeAnnotationNode; + return checkCPtrRecursively(typeAnnot.d.annotation); + } + + // 对于其他节点类型,尝试遍历子节点 + const d = (node as any).d; + if (d && typeof d === 'object') { + for (const key of Object.keys(d)) { + const value = d[key]; + if (value && typeof value === 'object' && value.nodeType !== undefined) { + if (checkCPtrRecursively(value)) { + return true; + } + } else if (Array.isArray(value)) { + for (const item of value) { + if (item && typeof item === 'object' && item.nodeType !== undefined) { + if (checkCPtrRecursively(item)) { + return true; + } + } + } + } + } + } + + return false; + } + + // Viper 扩展:检测 C 整数类型构造调用的第二参数是否为 t.CPtr。 + // 若是,则返回 CPtr 类的 instantiable 形式,用于构造 CPtr[T] 指针类型; + // 否则返回 undefined。 + // 示例:t.CUInt32T(addr, t.CPtr) → 返回 CPtr 类,外层构造 CPtr[CUInt32T]。 + // + // 判断方式:评估第二参数表达式的类型,若为 instantiable CPtr 类则匹配。 + // 这样可以正确处理 t.CPtr、CPtr(直接引用)以及别名导入等各种形式。 + function tryGetCPtrTypeFromSecondArg( + node: CallNode + ): ClassType | undefined { + if (node.d.args.length < 2) { + return undefined; + } + + const secondArgExpr = node.d.args[1].d.valueExpr; + // 若是关键字参数且名字不是 base,则跳过(仅识别位置参数或 base=... 形式) + const argName = node.d.args[1].d.name; + if (argName && argName.d.value !== 'base') { + return undefined; + } + + // 评估第二参数表达式的类型 + const secondArgTypeResult = getTypeOfExpression( + secondArgExpr, + EvalFlags.CallBaseDefaults + ); + const secondArgType = secondArgTypeResult.type; + + // 期望第二参数为 CPtr 类的 instantiable 形式(type[CPtr]) + if (isInstantiableClass(secondArgType) && isCPtrType(secondArgType)) { + return secondArgType; + } + + return undefined; + } + + // Viper 扩展:统计构造调用中作为"指针级数提升"的 t.CPtr 类型参数个数。 + // 语义:每个作为位置参数(或 base=... 形式)的 t.CPtr 类都表示再包一层指针。 + // 用于多级指针构造: + // - t.CUInt32T(x, t.CPtr) → 1 个 t.CPtr → 1 级 CPtr[CUInt32T] + // - t.CUInt32T(x, t.CPtr, t.CPtr) → 2 个 t.CPtr → 2 级 CPtr[CPtr[CUInt32T]] + // - t.CPtr(x, t.CPtr) → 1 个 t.CPtr → CPtr 自身 1 级 + 1 = 2 级 + // 遇到非 t.CPtr 类型参数即停止统计(级数提升参数须为连续的 t.CPtr)。 + function countCPtrTypeArgs(node: CallNode): number { + if (node.d.args.length < 2) { + return 0; + } + let count = 0; + for (let i = 1; i < node.d.args.length; i++) { + const arg = node.d.args[i]; + // 仅识别位置参数或 base=... 形式(与 tryGetCPtrTypeFromSecondArg 一致) + const argName = arg.d.name; + if (argName && argName.d.value !== 'base') { + break; + } + const argExpr = arg.d.valueExpr; + if (!argExpr) { + break; + } + const argType = getTypeOfExpression(argExpr, EvalFlags.CallBaseDefaults).type; + if (isInstantiableClass(argType) && isCPtrType(argType)) { + count++; + } else { + break; + } + } + return count; + } + + // Viper 扩展:构造 N 级嵌套 CPtr 实例类型。 + // 内层为 innermostType,外层包裹 N 层 CPtr: + // buildNestedCPtrType(cptrClass, integerT, 0) → integerT + // buildNestedCPtrType(cptrClass, integerT, 1) → CPtr[integerT] + // buildNestedCPtrType(cptrClass, integerT, 2) → CPtr[CPtr[integerT]] + // cptrClass 为 CPtr 的 instantiable 形式(type[CPtr])。 + function buildNestedCPtrType(cptrClass: ClassType, innermostType: Type, levels: number): Type { + let result = innermostType; + for (let i = 0; i < levels; i++) { + const cptrInstance = ClassType.cloneAsInstance(cptrClass); + result = ClassType.specialize(cptrInstance, [result]); + } + return result; + } + + function getTypeOfCall( + node: CallNode, + flags: EvalFlags, + inferenceContext: InferenceContext | undefined + ): TypeResult { + let baseTypeResult: TypeResult | undefined; + + // Check for the use of `type(x)` within a type annotation. This isn't + // allowed, and it's a common mistake, so we want to emit a diagnostic + // that guides the user to the right solution. + if ( + (flags & EvalFlags.TypeExpression) !== 0 && + node.d.leftExpr.nodeType === ParseNodeType.Name && + node.d.leftExpr.d.value === 'type' + ) { + const diag = new DiagnosticAddendum(); + diag.addMessage(LocAddendum.useTypeInstead()); + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.typeCallNotAllowed() + diag.getString(), + node + ); + } + + // Handle immediate calls of lambdas specially. + if (node.d.leftExpr.nodeType === ParseNodeType.Lambda) { + baseTypeResult = getTypeOfLambdaForCall(node, inferenceContext); + } else { + baseTypeResult = getTypeOfExpression( + node.d.leftExpr, + EvalFlags.CallBaseDefaults | (flags & EvalFlags.ForwardRefs) + ); + } + + const argList = ParseTreeUtils.getArgsByRuntimeOrder(node).map((arg) => { + const functionArg: Arg = { + valueExpression: arg.d.valueExpr, + argCategory: arg.d.argCategory, + node: arg, + name: arg.d.name, + }; + return functionArg; + }); + + let typeResult: TypeResult = { type: UnknownType.create() }; + + baseTypeResult.type = ensureSignatureIsUnique(baseTypeResult.type, node); + + if (!isTypeAliasPlaceholder(baseTypeResult.type)) { + if (node.d.leftExpr.nodeType === ParseNodeType.Name && node.d.leftExpr.d.value === 'super') { + // Handle the built-in "super" call specially. + typeResult = getTypeOfSuperCall(node); + } else if ( + isAnyOrUnknown(baseTypeResult.type) && + node.d.leftExpr.nodeType === ParseNodeType.Name && + node.d.leftExpr.d.value === 'reveal_type' + ) { + // Handle the implicit "reveal_type" call. + typeResult = getTypeOfRevealType(node, inferenceContext); + } else if (isFunction(baseTypeResult.type) && FunctionType.isBuiltIn(baseTypeResult.type, 'reveal_type')) { + // Handle the "typing.reveal_type" call. + typeResult = getTypeOfRevealType(node, inferenceContext); + } else if (isFunction(baseTypeResult.type) && FunctionType.isBuiltIn(baseTypeResult.type, 'assert_type')) { + // Handle the "typing.assert_type" call. + typeResult = getTypeOfAssertType(node, inferenceContext); + } else if (isClass(baseTypeResult.type) && ClassType.isBuiltIn(baseTypeResult.type, 'TypeForm')) { + // Handle the "typing.TypeForm" call. + typeResult = getTypeOfTypeForm(node, baseTypeResult.type); + } else if ( + isAnyOrUnknown(baseTypeResult.type) && + node.d.leftExpr.nodeType === ParseNodeType.Name && + node.d.leftExpr.d.value === 'reveal_locals' + ) { + if (node.d.args.length === 0) { + // Handle the special-case "reveal_locals" call. + typeResult.type = getTypeOfRevealLocals(node); + } else { + addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.revealLocalsArgs(), node); + } + } else if ( + // Viper 扩展:拦截 C 整数类型的构造调用(如 t.CUInt32T(addr, t.CPtr))。 + // 这些类型在 vipershed-fallback/stdlib/t.vpi 中定义为 TypeAlias = int, + // 但在 Viper 语义中它们是独立的 C 整数类型,构造调用应支持指针强转语义, + // 而不是走 int.__new__ 的参数类型检查(int.__new__ 期望 str|bytes|bytearray, + // 而 Viper 强转常用指针地址作为参数)。 + IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect) && + isInstantiableClass(baseTypeResult.type) && + ClassType.isBuiltIn(baseTypeResult.type, 'int') && + isViperCIntegerCall(node) + ) { + // 跳过 int.__new__ 的参数类型检查,直接返回该整数类型的实例。 + // Viper 的 C 整数类型构造调用本质是 C 语言的强转 (uint32_t)addr, + // 参数类型检查由 Viper 编译器在语义分析阶段处理,LSP 不需要重复检查。 + // + // 指针级数提升形式:每个作为位置参数的 t.CPtr 都表示再包一层指针: + // t.CUInt32T(addr) → 0 个 t.CPtr → CUInt32T 实例(值) + // t.CUInt32T(addr, t.CPtr) → 1 个 t.CPtr → CPtr[CUInt32T] (uint32_t*) + // t.CUInt32T(addr, t.CPtr, t.CPtr) → 2 个 t.CPtr → CPtr[CPtr[CUInt32T]] (uint32_t**) + const integerInstanceType = ClassType.cloneAsInstance(baseTypeResult.type); + const ptrLevels = countCPtrTypeArgs(node); + if (ptrLevels > 0) { + // 取 CPtr 的 instantiable 形式用于构造嵌套指针类型 + const cptrClass = tryGetCPtrTypeFromSecondArg(node); + if (cptrClass) { + typeResult.type = buildNestedCPtrType(cptrClass, integerInstanceType, ptrLevels); + } else { + typeResult.type = integerInstanceType; + } + } else { + typeResult.type = integerInstanceType; + } + } else if ( + // Viper 扩展:拦截 t.CPtr(...) 自身构造调用。 + // CPtr 不是 built-in int,不会进入上面的整数分支;若走默认 validateCallArgs, + // 按 __init__(value, *types) 只返回无级数信息的 CPtr 实例,丢失指针级数语义, + // 导致后续跨级赋值(如 a: t.CPtr = t.CPtr(x, t.CPtr))无法被检测。 + // 此处按 t.CPtr 参数个数构造正确的多级指针类型: + // t.CPtr(x) → 1 级(CPtr 实例) + // t.CPtr(x, t.CPtr) → 2 级(CPtr[CPtr]) + // t.CPtr(x, t.CPtr, t.CPtr) → 3 级(CPtr[CPtr[CPtr]]) + // CPtr 自身已是 1 级,额外每个 t.CPtr 参数再 +1 级。 + IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect) && + isInstantiableClass(baseTypeResult.type) && + isCPtrType(baseTypeResult.type) + ) { + const extraLevels = countCPtrTypeArgs(node); + // 最内层为 CPtr 实例(1 级),外层包裹 extraLevels 层 CPtr + const cptrInstance = ClassType.cloneAsInstance(baseTypeResult.type); + typeResult.type = buildNestedCPtrType(baseTypeResult.type, cptrInstance, extraLevels); + } else if ( + // Viper 扩展:拦截非整数、非 CPtr 的 CType 子类指针构造调用。 + // 覆盖 t.CVoid(0, t.CPtr)、t.CFloat(x, t.CPtr) 等形式: + // 这些类型不是 built-in int(不进入 9477 整数分支),也不是 CPtr(不进入 9509), + // 若走默认 validateCallArgs 只返回值实例,丢失 t.CPtr 参数的指针级数语义。 + // 按 Viper 的 C 强转语义,t.CVoid(0, t.CPtr) 应产生 CPtr[CVoid](void* 指针), + // t.CVoid(0, t.CPtr, t.CPtr) 应产生 CPtr[CPtr[CVoid]](void** 指针)。 + // 这与 t.CUInt32T(addr, t.CPtr)→CPtr[CUInt32T] 的指针级数提升语义一致。 + // 命中场景:t.CVoid(0, t.CPtr) 赋值给 UINT32PTR(uint32_t*)时同级指针转换。 + IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect) && + isInstantiableClass(baseTypeResult.type) && + isCTypeSubclassType(baseTypeResult.type) && + !isCPtrType(baseTypeResult.type) && + countCPtrTypeArgs(node) > 0 + ) { + const instanceType = ClassType.cloneAsInstance(baseTypeResult.type); + const ptrLevels = countCPtrTypeArgs(node); + const cptrClass = tryGetCPtrTypeFromSecondArg(node); + if (cptrClass) { + typeResult.type = buildNestedCPtrType(cptrClass, instanceType, ptrLevels); + } else { + typeResult.type = instanceType; + } + } else if ( + // Viper 扩展:拦截 __sizeof__ 调用。 + // Viper 中 __sizeof__ 语义是 C 语言的 sizeof(Type) -> int, + // 是类方法/静态方法,不需要 self 参数。但 object.__sizeof__ 是实例方法, + // 当通过类调用 Type.__sizeof__() 时会报 "Argument missing for parameter self"。 + // 此处跳过参数检查,直接返回 int 类型。 + IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect) && + isViperSizeOfCall(node) + ) { + // Viper 的 __sizeof__ 是类方法(sizeof(Type)),返回类型大小(字节数)。 + // 跳过 object.__sizeof__ 的实例方法参数检查,直接返回 int。 + typeResult.type = getBuiltInObject(node, 'int'); + } else { + const callResult = validateCallArgs( + node, + argList, + baseTypeResult, + /* constraints */ undefined, + /* skipUnknownArgCheck */ false, + inferenceContext + ); + + typeResult.type = callResult.returnType ?? UnknownType.create(); + + if (callResult.argumentErrors) { + typeResult.typeErrors = true; + } else { + typeResult.overloadsUsedForCall = callResult.overloadsUsedForCall; + } + + if (callResult.isTypeIncomplete) { + typeResult.isIncomplete = true; + } + } + + if (baseTypeResult.isIncomplete) { + typeResult.isIncomplete = true; + } + } else { + typeResult.isIncomplete = true; + } + + // Don't bother evaluating the arguments if we're speculatively evaluating the call + // or the base type is incomplete. + if (!isSpeculativeModeInUse(node) && !baseTypeResult.isIncomplete) { + // Touch all of the args so they're marked accessed even if there were errors. + // We skip this if it's a TypeVar() call in the typing.pyi module because + // this results in a cyclical type resolution problem whereby we try to + // retrieve the str class, which inherits from Sequence, which inherits from + // Iterable, which uses a TypeVar. Without this, Iterable and Sequence classes + // have invalid type parameters. + const isCyclicalTypeVarCall = + isInstantiableClass(baseTypeResult.type) && + ClassType.isBuiltIn(baseTypeResult.type, 'TypeVar') && + AnalyzerNodeInfo.getFileInfo(node).isTypingStubFile; + + if (!isCyclicalTypeVarCall) { + argList.forEach((arg) => { + if ( + arg.valueExpression && + arg.valueExpression.nodeType !== ParseNodeType.StringList && + !isTypeCached(arg.valueExpression) + ) { + getTypeOfExpression(arg.valueExpression); + } + }); + } + } + + if ((flags & EvalFlags.TypeExpression) !== 0) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeAnnotationCall(), node); + + typeResult = { type: UnknownType.create() }; + } + + return typeResult; + } + + // This function is used in cases where a lambda is defined and immediately + // called. In this case, we can't use normal bidirectional type inference + // to determine the lambda's type. It needs to be inferred from the argument + // types instead. + function getTypeOfLambdaForCall(node: CallNode, inferenceContext: InferenceContext | undefined): TypeResult { + assert(node.d.leftExpr.nodeType === ParseNodeType.Lambda); + + const expectedType = FunctionType.createSynthesizedInstance(''); + expectedType.shared.declaredReturnType = inferenceContext + ? inferenceContext.expectedType + : UnknownType.create(); + + let isArgTypeIncomplete = false; + node.d.args.forEach((arg, index) => { + const argTypeResult = getTypeOfExpression(arg.d.valueExpr); + if (argTypeResult.isIncomplete) { + isArgTypeIncomplete = true; + } + + FunctionType.addParam( + expectedType, + FunctionParam.create( + ParamCategory.Simple, + argTypeResult.type, + FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared, + `p${index.toString()}` + ) + ); + }); + + // If the lambda's param list ends with a "/" positional parameter separator, + // add a corresponding separator to the expected type. + const lambdaParams = node.d.leftExpr.d.params; + if (lambdaParams.length > 0) { + const lastParam = lambdaParams[lambdaParams.length - 1]; + if (lastParam.d.category === ParamCategory.Simple && !lastParam.d.name) { + FunctionType.addPositionOnlyParamSeparator(expectedType); + } + } + + function getLambdaType() { + return getTypeOfExpression(node.d.leftExpr, EvalFlags.CallBaseDefaults, makeInferenceContext(expectedType)); + } + + // If one or more of the arguments are incomplete, use speculative mode + // for the lambda evaluation because it may need to be reevaluated once + // the arg types are complete. + let typeResult = + isArgTypeIncomplete || isSpeculativeModeInUse(node) || inferenceContext?.isTypeIncomplete + ? useSpeculativeMode(node.d.leftExpr, getLambdaType) + : getLambdaType(); + + // If bidirectional type inference failed, use normal type inference instead. + if (typeResult.typeErrors) { + typeResult = getTypeOfExpression(node.d.leftExpr, EvalFlags.CallBaseDefaults); + } + + return typeResult; + } + + function getTypeOfTypeForm(node: CallNode, typeFormClass: ClassType): TypeResult { + if ( + node.d.args.length !== 1 || + node.d.args[0].d.argCategory !== ArgCategory.Simple || + node.d.args[0].d.name !== undefined + ) { + addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.typeFormArgs(), node); + return { type: UnknownType.create() }; + } + + const typeFormResult = getTypeOfArgExpectingType(convertNodeToArg(node.d.args[0]), { + typeFormArg: true, + noNonTypeSpecialForms: true, + typeExpression: true, + }); + + if (!typeFormResult.typeErrors && typeFormResult.type.props?.typeForm) { + typeFormResult.type = convertToInstance( + ClassType.specialize(typeFormClass, [convertToInstance(typeFormResult.type.props.typeForm)]) + ); + } + + return typeFormResult; + } + + function getTypeOfAssertType(node: CallNode, inferenceContext: InferenceContext | undefined): TypeResult { + if ( + node.d.args.length !== 2 || + node.d.args[0].d.argCategory !== ArgCategory.Simple || + node.d.args[0].d.name !== undefined || + node.d.args[0].d.argCategory !== ArgCategory.Simple || + node.d.args[1].d.name !== undefined + ) { + addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.assertTypeArgs(), node); + return { type: UnknownType.create() }; + } + + const arg0TypeResult = getTypeOfExpression(node.d.args[0].d.valueExpr, /* flags */ undefined, inferenceContext); + if (arg0TypeResult.isIncomplete) { + return { type: UnknownType.create(/* isIncomplete */ true), isIncomplete: true }; + } + + const assertedType = convertToInstance( + getTypeOfArgExpectingType(convertNodeToArg(node.d.args[1]), { + typeExpression: true, + }).type + ); + + // We'll replace TypeGuard and TypeIs with bool for purposes of assert_type testing. + // The spec is unclear on whether this is the correct behavior, but it seems to be + // what mypy does -- and what various library authors expect. + const arg0Type = stripTypeGuard(arg0TypeResult.type); + + if ( + !isTypeSame(assertedType, arg0Type, { + treatAnySameAsUnknown: true, + ignorePseudoGeneric: true, + ignoreConditions: true, + }) + ) { + const srcDestTypes = printSrcDestTypes(arg0TypeResult.type, assertedType, { expandTypeAlias: true }); + + addDiagnostic( + DiagnosticRule.reportAssertTypeFailure, + LocMessage.assertTypeTypeMismatch().format({ + expected: srcDestTypes.destType, + received: srcDestTypes.sourceType, + }), + node.d.args[0].d.valueExpr + ); + } + + return { type: arg0TypeResult.type }; + } + + function convertNodeToArg(node: ArgumentNode): ArgWithExpression { + return { + argCategory: node.d.argCategory, + name: node.d.name, + valueExpression: node.d.valueExpr, + }; + } + + function getTypeOfRevealType(node: CallNode, inferenceContext: InferenceContext | undefined): TypeResult { + let arg0Value: ExpressionNode | undefined; + let expectedRevealTypeNode: ExpressionNode | undefined; + let expectedRevealType: Type | undefined; + let expectedTextNode: ExpressionNode | undefined; + let expectedText: string | undefined; + + // Make sure there is only one positional argument passed as arg 0. + node.d.args.forEach((arg, index) => { + if (index === 0) { + if (arg.d.argCategory === ArgCategory.Simple && !arg.d.name) { + arg0Value = arg.d.valueExpr; + } + } else if (arg.d.argCategory !== ArgCategory.Simple || !arg.d.name) { + arg0Value = undefined; + } else if (arg.d.name.d.value === 'expected_text') { + expectedTextNode = arg.d.valueExpr; + const expectedTextType = getTypeOfExpression(arg.d.valueExpr).type; + + if ( + !isClassInstance(expectedTextType) || + !ClassType.isBuiltIn(expectedTextType, 'str') || + typeof expectedTextType.priv.literalValue !== 'string' + ) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.revealTypeExpectedTextArg(), + arg.d.valueExpr + ); + } else { + expectedText = expectedTextType.priv.literalValue; + } + } else if (arg.d.name.d.value === 'expected_type') { + expectedRevealTypeNode = arg.d.valueExpr; + expectedRevealType = convertToInstance( + getTypeOfArgExpectingType(convertNodeToArg(arg), { + typeExpression: true, + }).type + ); + } + }); + + if (!arg0Value) { + addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.revealTypeArgs(), node); + return { type: UnknownType.create() }; + } + + const typeResult = getTypeOfExpression(arg0Value, /* flags */ undefined, inferenceContext); + const type = typeResult.type; + + const exprString = ParseTreeUtils.printExpression(arg0Value); + const typeString = printType(type, { expandTypeAlias: true }); + + if (!typeResult.isIncomplete) { + if (expectedText !== undefined) { + if (expectedText !== typeString) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.revealTypeExpectedTextMismatch().format({ + expected: expectedText, + received: typeString, + }), + expectedTextNode ?? arg0Value + ); + } + } + + if (expectedRevealType) { + if (!isTypeSame(expectedRevealType, type, { ignorePseudoGeneric: true })) { + const expectedRevealTypeText = printType(expectedRevealType); + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.revealTypeExpectedTypeMismatch().format({ + expected: expectedRevealTypeText, + received: typeString, + }), + expectedRevealTypeNode ?? arg0Value + ); + } + } + + addInformation(LocAddendum.typeOfSymbol().format({ name: exprString, type: typeString }), node.d.args[0]); + } + + return { type, isIncomplete: typeResult.isIncomplete }; + } + + function getTypeOfRevealLocals(node: CallNode) { + let curNode: ParseNode | undefined = node; + let scope: Scope | undefined; + + while (curNode) { + scope = ScopeUtils.getScopeForNode(curNode); + + // Stop when we get a valid scope that's not a list comprehension + // scope. That includes lambdas, functions, classes, and modules. + if (scope && scope.type !== ScopeType.Comprehension) { + break; + } + + curNode = curNode.parent; + } + + const infoMessages: string[] = []; + + if (scope) { + scope.symbolTable.forEach((symbol, name) => { + if (!symbol.isIgnoredForProtocolMatch()) { + const typeOfSymbol = getEffectiveTypeOfSymbol(symbol); + infoMessages.push( + LocAddendum.typeOfSymbol().format({ + name, + type: printType(typeOfSymbol, { expandTypeAlias: true }), + }) + ); + } + }); + } + + if (infoMessages.length > 0) { + addInformation(infoMessages.join('\n'), node); + } else { + addInformation(LocMessage.revealLocalsNone(), node); + } + + return getNoneType(); + } + + function getTypeOfSuperCall(node: CallNode): TypeResult { + if (node.d.args.length > 2) { + addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.superCallArgCount(), node.d.args[2]); + } + + const enclosingFunction = ParseTreeUtils.getEnclosingFunctionEvaluationScope(node); + const enclosingClass = enclosingFunction ? ParseTreeUtils.getEnclosingClass(enclosingFunction) : undefined; + const enclosingClassType = enclosingClass ? getTypeOfClass(enclosingClass)?.classType : undefined; + + // Determine which class the "super" call is applied to. If + // there is no first argument, then the class is implicit. + let targetClassType: Type; + if (node.d.args.length > 0) { + targetClassType = getTypeOfExpression(node.d.args[0].d.valueExpr).type; + const concreteTargetClassType = makeTopLevelTypeVarsConcrete(targetClassType); + + if ( + !isAnyOrUnknown(concreteTargetClassType) && + !isInstantiableClass(concreteTargetClassType) && + !isMetaclassInstance(concreteTargetClassType) + ) { + addDiagnostic( + DiagnosticRule.reportArgumentType, + LocMessage.superCallFirstArg().format({ type: printType(targetClassType) }), + node.d.args[0].d.valueExpr + ); + } + } else { + if (enclosingClassType) { + targetClassType = enclosingClassType ?? UnknownType.create(); + + // Zero-argument forms of super are not allowed within static methods. + // This results in a runtime exception. + if (enclosingFunction) { + const functionInfo = getFunctionInfoFromDecorators( + evaluatorInterface, + enclosingFunction, + /* isInClass */ true + ); + + if ((functionInfo?.flags & FunctionTypeFlags.StaticMethod) !== 0) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.superCallZeroArgFormStaticMethod(), + node.d.leftExpr + ); + } + } + } else { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.superCallZeroArgForm(), + node.d.leftExpr + ); + targetClassType = UnknownType.create(); + } + } + + const concreteTargetClassType = makeTopLevelTypeVarsConcrete(targetClassType); + + // Determine whether to further narrow the type. + let secondArgType: Type | undefined; + let bindToType: ClassType | undefined; + + if (node.d.args.length > 1) { + secondArgType = getTypeOfExpression(node.d.args[1].d.valueExpr).type; + const secondArgConcreteType = makeTopLevelTypeVarsConcrete(secondArgType); + + let reportError = false; + + doForEachSubtype(secondArgConcreteType, (secondArgSubtype) => { + if (isAnyOrUnknown(secondArgSubtype)) { + // Ignore unknown or any types. + } else if (isClassInstance(secondArgSubtype)) { + if (isInstantiableClass(concreteTargetClassType)) { + if ( + !derivesFromClassRecursive( + ClassType.cloneAsInstantiable(secondArgSubtype), + concreteTargetClassType, + /* ignoreUnknown */ true + ) + ) { + reportError = true; + } + } + bindToType = secondArgSubtype; + } else if (isInstantiableClass(secondArgSubtype)) { + if (isInstantiableClass(concreteTargetClassType)) { + if ( + !ClassType.isBuiltIn(concreteTargetClassType, 'type') && + !derivesFromClassRecursive( + secondArgSubtype, + concreteTargetClassType, + /* ignoreUnknown */ true + ) + ) { + reportError = true; + } + } + bindToType = secondArgSubtype; + } else { + reportError = true; + } + }); + + if (reportError) { + addDiagnostic( + DiagnosticRule.reportArgumentType, + LocMessage.superCallSecondArg().format({ type: printType(targetClassType) }), + node.d.args[1].d.valueExpr + ); + + return { type: UnknownType.create() }; + } + } else if (enclosingClassType) { + bindToType = ClassType.cloneAsInstance(enclosingClassType); + + // Get the type from the self or cls parameter if it is explicitly annotated. + // If it's a TypeVar, change the bindToType into a conditional type. + const enclosingMethod = ParseTreeUtils.getEnclosingFunction(node); + let implicitBindToType: Type | undefined; + + if (enclosingMethod) { + const methodTypeInfo = getTypeOfFunction(enclosingMethod); + if (methodTypeInfo) { + const methodType = methodTypeInfo.functionType; + if ( + FunctionType.isClassMethod(methodType) || + FunctionType.isConstructorMethod(methodType) || + FunctionType.isInstanceMethod(methodType) + ) { + if ( + methodType.shared.parameters.length > 0 && + FunctionParam.isTypeDeclared(methodType.shared.parameters[0]) + ) { + let paramType = FunctionType.getParamType(methodType, 0); + const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(node); + paramType = makeTypeVarsBound(paramType, liveScopeIds); + implicitBindToType = makeTopLevelTypeVarsConcrete(paramType); + } + } + } + } + + if (bindToType && implicitBindToType) { + const typeCondition = getTypeCondition(implicitBindToType); + if (typeCondition) { + bindToType = addConditionToType(bindToType, typeCondition); + } else if (isClass(implicitBindToType)) { + bindToType = implicitBindToType; + } + } + } + + // Determine whether super() should return an instance of the class or + // the class itself. It depends on whether the super() call is located + // within an instance method or not. + let resultIsInstance = true; + if (node.d.args.length <= 1) { + const enclosingMethod = ParseTreeUtils.getEnclosingFunction(node); + if (enclosingMethod) { + const methodType = getTypeOfFunction(enclosingMethod); + if (methodType) { + if ( + FunctionType.isStaticMethod(methodType.functionType) || + FunctionType.isConstructorMethod(methodType.functionType) || + FunctionType.isClassMethod(methodType.functionType) + ) { + resultIsInstance = false; + } + } + } + } + + // Python docs indicate that super() isn't valid for + // operations other than member accesses or attribute lookups. + const parentNode = node.parent; + if (parentNode?.nodeType === ParseNodeType.MemberAccess) { + const memberName = parentNode.d.member.d.value; + let effectiveTargetClass = isClass(concreteTargetClassType) ? concreteTargetClassType : undefined; + + // If the bind-to type is a protocol, don't use the effective target class. + // This pattern is used for mixins, where the mixin type is a protocol class + // that is used to decorate the "self" or "cls" parameter. + let isProtocolClass = false; + if ( + bindToType && + ClassType.isProtocolClass(bindToType) && + effectiveTargetClass && + !ClassType.isSameGenericClass( + TypeBase.isInstance(bindToType) ? ClassType.cloneAsInstantiable(bindToType) : bindToType, + effectiveTargetClass + ) + ) { + isProtocolClass = true; + effectiveTargetClass = undefined; + } + + if (bindToType) { + bindToType = selfSpecializeClass(bindToType, { useBoundTypeVars: true }); + } + + const lookupResults = bindToType + ? lookUpClassMember(bindToType, memberName, MemberAccessFlags.Default, effectiveTargetClass) + : undefined; + + let resultType: Type; + if (lookupResults && isInstantiableClass(lookupResults.classType)) { + resultType = lookupResults.classType; + + if (isProtocolClass) { + // If the bindToType is a protocol class, set the "include subclasses" flag + // so we don't enforce that called methods are implemented within the protocol. + resultType = ClassType.cloneIncludeSubclasses(resultType); + } + } else if ( + effectiveTargetClass && + !isAnyOrUnknown(effectiveTargetClass) && + !derivesFromAnyOrUnknown(effectiveTargetClass) + ) { + resultType = prefetched?.objectClass ?? UnknownType.create(); + } else { + resultType = UnknownType.create(); + } + + let bindToSelfType: ClassType | TypeVarType | undefined; + if (bindToType) { + if (secondArgType) { + // If a TypeVar was passed as the second argument, use it + // to derive the the self type. + if (isTypeVar(secondArgType)) { + bindToSelfType = convertToInstance(secondArgType); + } + } else { + // If this is a zero-argument form of super(), synthesize + // a Self type to bind to. + bindToSelfType = TypeBase.cloneForCondition( + TypeVarType.cloneAsBound( + synthesizeTypeVarForSelfCls( + ClassType.cloneIncludeSubclasses(bindToType, /* includeSubclasses */ false), + /* isClsParam */ false + ) + ), + bindToType.props?.condition + ); + } + } + + const type = resultIsInstance ? convertToInstance(resultType, /* includeSubclasses */ false) : resultType; + + return { type, bindToSelfType }; + } + + // Handle the super() call when used outside of a member access expression. + if (isInstantiableClass(concreteTargetClassType)) { + // We don't know which member is going to be accessed, so we cannot + // deterministically determine the correct type in this case. We'll + // use a heuristic that produces the "correct" (desired) behavior in + // most cases. If there's a bindToType and the targetClassType is one + // of the base classes of the bindToType, we'll return the next base + // class. + if (bindToType) { + let nextBaseClassType: Type | undefined; + + if ( + ClassType.isSameGenericClass( + TypeBase.isInstance(bindToType) ? ClassType.cloneAsInstantiable(bindToType) : bindToType, + concreteTargetClassType + ) + ) { + if (bindToType.shared.baseClasses.length > 0) { + nextBaseClassType = bindToType.shared.baseClasses[0]; + } + } else { + const baseClassIndex = bindToType.shared.baseClasses.findIndex( + (baseClass) => + isClass(baseClass) && + ClassType.isSameGenericClass(baseClass, concreteTargetClassType as ClassType) + ); + + if (baseClassIndex >= 0 && baseClassIndex < bindToType.shared.baseClasses.length - 1) { + nextBaseClassType = bindToType.shared.baseClasses[baseClassIndex + 1]; + } + } + + if (nextBaseClassType) { + if (isInstantiableClass(nextBaseClassType)) { + nextBaseClassType = specializeForBaseClass(bindToType, nextBaseClassType); + } + return { type: resultIsInstance ? convertToInstance(nextBaseClassType) : nextBaseClassType }; + } + + // There's not much we can say about the type. Simply return object or type. + if (prefetched?.typeClass && isInstantiableClass(prefetched.typeClass)) { + return { + type: resultIsInstance ? getObjectType() : convertToInstance(prefetched.typeClass), + }; + } + } else { + // If the class derives from one or more unknown classes, + // return unknown here to prevent spurious errors. + if (concreteTargetClassType.shared.mro.some((mroBase) => isAnyOrUnknown(mroBase))) { + return { type: UnknownType.create() }; + } + + const baseClasses = concreteTargetClassType.shared.baseClasses; + if (baseClasses.length > 0) { + const baseClassType = baseClasses[0]; + if (isInstantiableClass(baseClassType)) { + return { + type: resultIsInstance ? ClassType.cloneAsInstance(baseClassType) : baseClassType, + }; + } + } + } + } + + return { type: UnknownType.create() }; + } + + // When evaluating a call, the errorNode is typically the call node, which + // encompasses all of the argument expressions. This means we can normally + // use the errorNode as the root for speculative evaluation. However, there + // are some cases where we don't have a call node (e.g. in the case of an + // __init_subclass__ validation). Here we need to find some other parent + // node of the error node that encompasses all of the arguments. + function getSpeculativeNodeForCall(errorNode: ExpressionNode): ParseNode { + // If the error node is within an arg, expand to include the parent of the arg list. + const argParent = ParseTreeUtils.getParentNodeOfType(errorNode, ParseNodeType.Argument); + if (argParent?.parent) { + return argParent.parent; + } + + // If the error node is the name in a class declaration, expand to include the class node. + if ( + errorNode.nodeType === ParseNodeType.Name && + errorNode.parent?.nodeType === ParseNodeType.Class && + errorNode.parent.d.name === errorNode + ) { + return errorNode.parent; + } + + return errorNode; + } + + // Attempts to find an overloaded function for each set of argument + // types in the expandedArgTypes list. If an argument type is undefined, + // its type is evaluated from the argument's expression using the + // corresponding parameter's expected type. The first time this is called, + // there will be only one argument list in expandedArgTypes, and all entries + // (one for each argument) will be undefined. On subsequent calls, this + // list will grow to include union expansions. + function validateOverloadsWithExpandedTypes( + errorNode: ExpressionNode, + expandedArgTypes: (Type | undefined)[][], + argParamMatches: MatchArgsToParamsResult[], + constraints: ConstraintTracker | undefined, + skipUnknownArgCheck: boolean | undefined, + inferenceContext: InferenceContext | undefined + ): CallResult { + const returnTypes: Type[] = []; + let matchedOverloads: MatchedOverloadInfo[] = []; + let isTypeIncomplete = false; + let overloadsUsedForCall: FunctionType[] = []; + let isDefinitiveMatchFound = false; + const speculativeNode = getSpeculativeNodeForCall(errorNode); + + for (let expandedTypesIndex = 0; expandedTypesIndex < expandedArgTypes.length; expandedTypesIndex++) { + let matchedOverload: FunctionType | undefined; + const argTypeOverride = expandedArgTypes[expandedTypesIndex]; + const hasArgTypeOverride = argTypeOverride.some((a) => a !== undefined); + let possibleMatchResults: MatchedOverloadInfo[] = []; + let possibleMatchInvolvesIncompleteUnknown = false; + isDefinitiveMatchFound = false; + + for (let overloadIndex = 0; overloadIndex < argParamMatches.length; overloadIndex++) { + const overload = argParamMatches[overloadIndex].overload; + + let matchResults = argParamMatches[overloadIndex]; + if (hasArgTypeOverride) { + matchResults = { ...argParamMatches[overloadIndex] }; + matchResults.argParams = matchResults.argParams.map((argParam, argIndex) => { + if (!argTypeOverride[argIndex]) { + return argParam; + } + const argParamCopy = { ...argParam }; + argParamCopy.argType = argTypeOverride[argIndex]; + return argParamCopy; + }); + } + + // Clone the constraints so we don't modify the original. + const effectiveConstraints = constraints?.clone() ?? new ConstraintTracker(); + + // Use speculative mode so we don't output any diagnostics or + // record any final types in the type cache. + const callResult = useSpeculativeMode(speculativeNode, () => { + return validateArgTypesWithContext( + errorNode, + matchResults, + effectiveConstraints, + /* skipUnknownArgCheck */ true, + inferenceContext + ); + }); + + if (callResult.isTypeIncomplete) { + isTypeIncomplete = true; + } + + if (!callResult.argumentErrors && callResult.returnType) { + overloadsUsedForCall.push(overload); + + matchedOverload = overload; + const matchedOverloadInfo: MatchedOverloadInfo = { + overload: matchedOverload, + matchResults, + constraints: effectiveConstraints, + returnType: callResult.returnType, + argResults: callResult.argResults ?? [], + }; + matchedOverloads.push(matchedOverloadInfo); + + if (callResult.anyOrUnknownArg || matchResults.unpackedArgOfUnknownLength) { + possibleMatchResults.push(matchedOverloadInfo); + + if (callResult.anyOrUnknownArg) { + if (isIncompleteUnknown(callResult.anyOrUnknownArg)) { + possibleMatchInvolvesIncompleteUnknown = true; + } + } + } else { + returnTypes.push(callResult.returnType); + isDefinitiveMatchFound = true; + break; + } + } + } + + // If we didn't find a definitive match that doesn't depend on + // an Any or Unknown argument, fall back on the possible match. + // If there were multiple possible matches, evaluate the type as + // Unknown, but include the "possible types" to allow for completion + // suggestions. + if (!isDefinitiveMatchFound && possibleMatchResults.length > 0) { + possibleMatchResults = filterOverloadMatchesForUnpackedArgs(possibleMatchResults); + possibleMatchResults = filterOverloadMatchesForAnyArgs(possibleMatchResults); + + // Did the filtering produce a single result? If so, we're done. + if (possibleMatchResults.length === 1) { + overloadsUsedForCall = [possibleMatchResults[0].overload]; + returnTypes.push(possibleMatchResults[0].returnType); + matchedOverloads = [possibleMatchResults[0]]; + } else { + // Eliminate any return types that are subsumed by other return types. + let dedupedMatchResults: Type[] = []; + let dedupedResultsIncludeAny = false; + + possibleMatchResults.forEach((result) => { + let isSubtypeSubsumed = false; + + for (let dedupedIndex = 0; dedupedIndex < dedupedMatchResults.length; dedupedIndex++) { + if (assignType(dedupedMatchResults[dedupedIndex], result.returnType)) { + const anyOrUnknown = containsAnyOrUnknown( + dedupedMatchResults[dedupedIndex], + /* recurse */ false + ); + if (!anyOrUnknown) { + isSubtypeSubsumed = true; + } else if (isAny(anyOrUnknown)) { + dedupedResultsIncludeAny = true; + } + break; + } else if (assignType(result.returnType, dedupedMatchResults[dedupedIndex])) { + const anyOrUnknown = containsAnyOrUnknown(result.returnType, /* recurse */ false); + if (!anyOrUnknown) { + dedupedMatchResults[dedupedIndex] = NeverType.createNever(); + } else if (isAny(anyOrUnknown)) { + dedupedResultsIncludeAny = true; + } + break; + } + } + + if (!isSubtypeSubsumed) { + dedupedMatchResults.push(result.returnType); + } + }); + + dedupedMatchResults = dedupedMatchResults.filter((t) => !isNever(t)); + const combinedTypes = combineTypes(dedupedMatchResults); + + let returnType = combinedTypes; + if (dedupedMatchResults.length > 1) { + // If one or more of the deduped types is Any or contains Any, + // we will assume that the person who defined the overload really + // wanted Any rather than Unknown. In cases where the deduped types + // simply contains conflicting results without an Any, we'll use + // an UnknownType. + if (dedupedResultsIncludeAny) { + returnType = AnyType.create(); + } else { + returnType = UnknownType.createPossibleType( + combinedTypes, + possibleMatchInvolvesIncompleteUnknown + ); + } + } + + returnTypes.push(returnType); + } + } + + if (!matchedOverload) { + return { argumentErrors: true, isTypeIncomplete, overloadsUsedForCall }; + } + } + + // We found a match for all of the expanded argument lists. Copy the + // resulting type var context back into the caller's type var context. + // Use the type var context from the last matched overload because it + // includes the type var solutions for all earlier matched overloads. + if (constraints && isDefinitiveMatchFound) { + constraints.copyFromClone(matchedOverloads[matchedOverloads.length - 1].constraints); + } + + // And run through the first expanded argument list one more time to + // populate the type cache. + const finalConstraints = constraints ?? matchedOverloads[0].constraints; + const finalCallResult = validateArgTypesWithContext( + errorNode, + matchedOverloads[0].matchResults, + finalConstraints, + skipUnknownArgCheck, + inferenceContext + ); + + if (finalCallResult.isTypeIncomplete) { + isTypeIncomplete = true; + } + + return { + argumentErrors: finalCallResult.argumentErrors, + anyOrUnknownArg: finalCallResult.anyOrUnknownArg, + returnType: combineTypes(returnTypes), + isTypeIncomplete, + specializedInitSelfType: finalCallResult.specializedInitSelfType, + overloadsUsedForCall, + }; + } + + // Determines whether one or more overloads can be eliminated because they + // rely on an unpacked argument of unknown length when there is at least + // one overload that doesn't because it maps to an *args parameter. + function filterOverloadMatchesForUnpackedArgs(matches: MatchedOverloadInfo[]): MatchedOverloadInfo[] { + if (matches.length < 2) { + return matches; + } + + // Is there at least one overload that relies on unpacked args for a match? + const unpackedArgsOverloads = matches.filter((match) => match.matchResults.unpackedArgMapsToVariadic); + if (unpackedArgsOverloads.length === matches.length || unpackedArgsOverloads.length === 0) { + return matches; + } + + return unpackedArgsOverloads; + } + + // Determines whether multiple incompatible overloads match + // due to an Any or Unknown argument type. + function filterOverloadMatchesForAnyArgs(matches: MatchedOverloadInfo[]): MatchedOverloadInfo[] { + if (matches.length < 2) { + return matches; + } + + // If all of the return types match, select the first one. + if ( + areTypesSame( + matches.map((match) => match.returnType), + { treatAnySameAsUnknown: true } + ) + ) { + return [matches[0]]; + } + + const firstArgResults = matches[0].argResults; + if (!firstArgResults) { + return matches; + } + + let foundAmbiguousAnyArg = false; + for (let i = 0; i < firstArgResults.length; i++) { + // If the arg is Any or Unknown, see if the corresponding + // parameter types differ in any way. + if (isAnyOrUnknown(firstArgResults[i].argType)) { + const paramTypes = matches.map((match) => + i < match.matchResults.argParams.length + ? match.matchResults.argParams[i].paramType + : UnknownType.create() + ); + if (!areTypesSame(paramTypes, { treatAnySameAsUnknown: true })) { + foundAmbiguousAnyArg = true; + } + } + } + + // If the first overload has a different number of effective arguments + // than latter overloads, don't filter any of them. This typically means + // that one of the arguments is an unpacked iterator, and it maps to + // an indeterminate number of parameters, which means that the overload + // selection is ambiguous. + if (foundAmbiguousAnyArg || matches.some((match) => match.argResults.length !== firstArgResults.length)) { + return matches; + } + + return [matches[0]]; + } + + function getBestOverloadForArgs( + errorNode: ExpressionNode, + typeResult: TypeResult, + argList: Arg[] + ): FunctionType | undefined { + let overloadIndex = 0; + const matches: MatchArgsToParamsResult[] = []; + const speculativeNode = getSpeculativeNodeForCall(errorNode); + + useSignatureTracker(errorNode, () => { + // Create a list of potential overload matches based on arguments. + OverloadedType.getOverloads(typeResult.type).forEach((overload) => { + useSpeculativeMode(speculativeNode, () => { + const matchResults = matchArgsToParams( + errorNode, + argList, + { type: overload, isIncomplete: typeResult.isIncomplete }, + overloadIndex + ); + + if (!matchResults.argumentErrors) { + matches.push(matchResults); + } + + overloadIndex++; + }); + }); + }); + + let winningOverloadIndex: number | undefined; + + matches.forEach((match, matchIndex) => { + if (winningOverloadIndex === undefined) { + useSpeculativeMode(speculativeNode, () => { + const callResult = validateArgTypes( + errorNode, + match, + new ConstraintTracker(), + /* skipUnknownArgCheck */ true + ); + + if (callResult && !callResult.argumentErrors) { + winningOverloadIndex = matchIndex; + } + }); + } + }); + + return winningOverloadIndex === undefined ? undefined : matches[winningOverloadIndex].overload; + } + + function validateOverloadedArgTypes( + errorNode: ExpressionNode, + argList: Arg[], + typeResult: TypeResult, + constraints: ConstraintTracker | undefined, + skipUnknownArgCheck: boolean | undefined, + inferenceContext: InferenceContext | undefined + ): CallResult { + const filteredMatchResults: MatchArgsToParamsResult[] = []; + let contextFreeArgTypes: Type[] | undefined; + let isTypeIncomplete = !!typeResult.isIncomplete; + const type = typeResult.type; + const speculativeNode = getSpeculativeNodeForCall(errorNode); + + // Start by evaluating the types of the arguments without any expected + // type. Also, filter the list of overloads based on the number of + // positional and keyword arguments that are present. We do all of this + // speculatively because we don't want to record any types in the type + // cache or record any diagnostics at this stage. + useSpeculativeMode(speculativeNode, () => { + let overloadIndex = 0; + OverloadedType.getOverloads(type).forEach((overload) => { + // Consider only the functions that have the @overload decorator, + // not the final function that omits the overload. This is the + // intended behavior according to PEP 484. + const matchResults = matchArgsToParams( + errorNode, + argList, + { type: overload, isIncomplete: typeResult.isIncomplete }, + overloadIndex + ); + + if (!matchResults.argumentErrors) { + filteredMatchResults.push(matchResults); + } + + overloadIndex++; + }); + }); + + // If there are no possible arg/param matches among the overloads, + // emit an error that includes the argument types. + if (filteredMatchResults.length === 0) { + // Skip the error message if we're in speculative mode because it's very + // expensive, and we're going to suppress the diagnostic anyway. + if (!canSkipDiagnosticForNode(errorNode)) { + const overloads = OverloadedType.getOverloads(type); + const functionName = + overloads.length > 0 && overloads[0].shared.name + ? overloads[0].shared.name + : ''; + const diagAddendum = new DiagnosticAddendum(); + const argTypes = argList.map((t) => { + const typeString = printType(getTypeOfArg(t, /* inferenceContext */ undefined).type); + + if (t.argCategory === ArgCategory.UnpackedList) { + return `*${typeString}`; + } + + if (t.argCategory === ArgCategory.UnpackedDictionary) { + return `**${typeString}`; + } + + return typeString; + }); + + diagAddendum.addMessage(LocAddendum.argumentTypes().format({ types: argTypes.join(', ') })); + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.noOverload().format({ name: functionName }) + diagAddendum.getString(), + errorNode + ); + } + + return { argumentErrors: true, isTypeIncomplete, overloadsUsedForCall: [] }; + } + + // Create a helper function that evaluates the overload that best + // matches the arg/param lists. + function evaluateUsingBestMatchingOverload(skipUnknownArgCheck: boolean, emitNoOverloadFoundError: boolean) { + // Find the match with the smallest argument match score. If there + // are more than one with the same score, use the one with the + // largest index. Later overloads tend to be more general. + const bestMatch = filteredMatchResults.reduce((previous, current) => { + if (current.argumentMatchScore === previous.argumentMatchScore) { + return current.overloadIndex > previous.overloadIndex ? current : previous; + } + return current.argumentMatchScore < previous.argumentMatchScore ? current : previous; + }); + + // If there is more than one filtered match, report that no match + // was possible and emit a diagnostic that provides the most likely. + if (emitNoOverloadFoundError) { + const functionName = bestMatch.overload.shared.name || ''; + const diagnostic = addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.noOverload().format({ name: functionName }), + errorNode + ); + + const overrideDecl = bestMatch.overload.shared.declaration; + if (diagnostic && overrideDecl) { + diagnostic.addRelatedInfo( + LocAddendum.overloadIndex().format({ index: bestMatch.overloadIndex + 1 }), + overrideDecl.uri, + overrideDecl.range + ); + } + } + + const effectiveConstraints = constraints ?? new ConstraintTracker(); + + return validateArgTypesWithContext( + errorNode, + bestMatch, + effectiveConstraints, + skipUnknownArgCheck, + inferenceContext + ); + } + + // If there is only one possible arg/param match among the overloads, + // use the normal type matching mechanism because it is faster and + // will provide a clearer error message. + if (filteredMatchResults.length === 1) { + return evaluateUsingBestMatchingOverload( + /* skipUnknownArgCheck */ false, + /* emitNoOverloadFoundError */ false + ); + } + + let expandedArgTypes: (Type | undefined)[][] | undefined = [argList.map((arg) => undefined)]; + + while (true) { + const callResult = validateOverloadsWithExpandedTypes( + errorNode, + expandedArgTypes, + filteredMatchResults, + constraints, + skipUnknownArgCheck, + inferenceContext + ); + + if (callResult.isTypeIncomplete) { + isTypeIncomplete = true; + } + + if (!callResult.argumentErrors) { + return callResult; + } + + // We didn't find an overload match. Try to expand the next union + // argument type into individual types and retry with the expanded types. + if (!contextFreeArgTypes) { + useSpeculativeMode(getSpeculativeNodeForCall(errorNode), () => { + // Evaluate the types of each argument expression without regard to + // the context. We'll use this to determine whether we need to do + // union expansion. + contextFreeArgTypes = argList.map((arg) => { + if (arg.typeResult) { + return arg.typeResult.type; + } + + if (arg.valueExpression) { + const valueExpressionNode = arg.valueExpression; + return useSpeculativeMode(valueExpressionNode, () => { + return getTypeOfExpression(valueExpressionNode).type; + }); + } + + return AnyType.create(); + }); + }); + } + + expandedArgTypes = expandArgTypes(contextFreeArgTypes!, expandedArgTypes); + + // Check for combinatoric explosion and break out of loop. + if (!expandedArgTypes || expandedArgTypes.length > maxTotalOverloadArgTypeExpansionCount) { + break; + } + } + + // We couldn't find any valid overloads. Skip the error message if we're + // in speculative mode because it's very expensive, and we're going to + // suppress the diagnostic anyway. + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { + const result = evaluateUsingBestMatchingOverload( + /* skipUnknownArgCheck */ true, + /* emitNoOverloadFoundError */ true + ); + + // Replace the result with an unknown type since we don't know + // what overload should have been used. + result.returnType = UnknownType.create(); + return { ...result, argumentErrors: true }; + } + + return { argumentErrors: true, isTypeIncomplete, overloadsUsedForCall: [] }; + } + + // Replaces each item in the expandedArgTypes with n items where n is + // the number of subtypes in a union or other expandable type. + // The contextFreeArgTypes parameter represents the types of the arguments + // evaluated with no bidirectional type inference (i.e. without the help of + // the corresponding parameter's expected type). If the function returns + // undefined, that indicates that all types have been expanded, and no + // more expansion is possible. + function expandArgTypes( + contextFreeArgTypes: Type[], + expandedArgTypes: (Type | undefined)[][] + ): (Type | undefined)[][] | undefined { + // Find the rightmost already-expanded argument. + let indexToExpand = contextFreeArgTypes.length - 1; + while (indexToExpand >= 0 && !expandedArgTypes[0][indexToExpand]) { + indexToExpand--; + } + + // Move to the next candidate for expansion. + indexToExpand++; + + if (indexToExpand >= contextFreeArgTypes.length) { + return undefined; + } + + let expandedTypes: Type[] | undefined; + while (indexToExpand < contextFreeArgTypes.length) { + // Is this a union type? If so, we can expand it. + const argType = contextFreeArgTypes[indexToExpand]; + + expandedTypes = expandArgType(argType); + if (expandedTypes) { + break; + } + indexToExpand++; + } + + // We have nothing left to expand. + if (!expandedTypes) { + return undefined; + } + + // Expand entry indexToExpand. + const newExpandedArgTypes: (Type | undefined)[][] = []; + + expandedArgTypes.forEach((preExpandedTypes) => { + expandedTypes.forEach((subtype) => { + const expandedTypes = [...preExpandedTypes]; + expandedTypes[indexToExpand] = subtype; + newExpandedArgTypes.push(expandedTypes); + }); + }); + + return newExpandedArgTypes; + } + + function expandArgType(type: Type): Type[] | undefined { + const expandedTypes: Type[] = []; + + // Expand any top-level type variables with constraints. + type = makeTopLevelTypeVarsConcrete(type); + + doForEachSubtype(type, (subtype) => { + if (isClassInstance(subtype)) { + // Expand any bool or Enum literals. + const expandedLiteralTypes = enumerateLiteralsForType(evaluatorInterface, subtype); + if (expandedLiteralTypes && expandedLiteralTypes.length <= maxSingleOverloadArgTypeExpansionCount) { + appendArray(expandedTypes, expandedLiteralTypes); + return; + } + + // Expand any fixed-size tuples. + const expandedTuples = expandTuple(subtype, maxSingleOverloadArgTypeExpansionCount); + if (expandedTuples) { + appendArray(expandedTypes, expandedTuples); + return; + } + } + + expandedTypes.push(subtype); + }); + + return expandedTypes.length > 1 ? expandedTypes : undefined; + } + + // Validates that the arguments can be assigned to the call's parameter + // list, specializes the call based on arg types, and returns the + // specialized type of the return value. If it detects an error along + // the way, it emits a diagnostic and sets argumentErrors to true. + function validateCallArgs( + errorNode: ExpressionNode, + argList: Arg[], + callTypeResult: TypeResult, + constraints: ConstraintTracker | undefined, + skipUnknownArgCheck: boolean | undefined, + inferenceContext: InferenceContext | undefined, + recursionCount = 0 + ): CallResult { + let argumentErrors = false; + let isTypeIncomplete = false; + let specializedInitSelfType: Type | undefined; + const overloadsUsedForCall: FunctionType[] = []; + + if (recursionCount > maxTypeRecursionCount) { + return { returnType: UnknownType.create(), argumentErrors: true, overloadsUsedForCall }; + } + recursionCount++; + + // Viper 方言允许联合类型强转,例如 `(memory_block | t.CPtr)(addr)` + // 或 `(t.CInt | t.CPtr)(x)`。Vipeye 会把联合类型标记为 specialForm + // 并在上方报 objectNotCallable;Viper 是 AOT 静态语言,这类联合强转是 + // 合法语义(C 风格类型转换),因此在 Viper 模式下直接放行并返回 Unknown, + // 避免对联合类型逐子类型校验时产生的误报。 + if ( + isUnion(callTypeResult.type) && + IsViperDialect(AnalyzerNodeInfo.getFileInfo(errorNode).executionEnvironment.dialect) + ) { + return { returnType: UnknownType.create(), argumentErrors: false, overloadsUsedForCall }; + } + + // Viper 扩展:类型变量调用 `T(v)` 是 C 风格强转语义。 + // 泛型类方法中 `self.value = T(v)` 用类型参数做类型转换函数。 + // PEP 695 type param `T` 在值表达式(调用位置)求值为 `type[T]`: + // - callTypeResult.type 是 Class 类别、printType 为 type[TypeVar] + // - 其 props.typeForm 是 TypeVar 类的 instantiable Class 形式 + // (shared.name === 'TypeVar',见 getTypeOfTypeParam 的 runtimeClassName) + // 若走 validateCallForInstantiableClass/validateCallForClassInstance 会查 + // `__call__` 成员并误报 "Object of type TypeVar is not callable"。 + // Viper 方言下放行并返回 Unknown,转换结果在泛型实例化时才确定。 + const typeFormType = callTypeResult.type.props?.typeForm; + const isTypeParamTypeForm = + typeFormType !== undefined && + isClass(typeFormType) && + typeFormType.shared.name === 'TypeVar'; + const isTypeVarClassType = + isClass(callTypeResult.type) && (callTypeResult.type as any).shared?.name === 'TypeVar'; + if ( + (isTypeVar(callTypeResult.type) || isTypeParamTypeForm || isTypeVarClassType) && + IsViperDialect(AnalyzerNodeInfo.getFileInfo(errorNode).executionEnvironment.dialect) + ) { + return { returnType: UnknownType.create(), argumentErrors: false, overloadsUsedForCall }; + } + + // Special forms are not callable. + if (callTypeResult.type.props?.specialForm) { + const exprNode = errorNode.nodeType === ParseNodeType.Call ? errorNode.d.leftExpr : errorNode; + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.objectNotCallable().format({ + type: printType(callTypeResult.type.props.specialForm, { expandTypeAlias: true }), + }), + exprNode + ); + return { returnType: UnknownType.create(), argumentErrors: true, overloadsUsedForCall }; + } + + let returnType = mapSubtypesExpandTypeVars( + callTypeResult.type, + { sortSubtypes: true }, + (expandedSubtype, unexpandedSubtype, isLastIteration) => { + return useSpeculativeMode( + isLastIteration ? undefined : getSpeculativeNodeForCall(errorNode), + () => { + const callResult = validateCallArgsForSubtype( + errorNode, + argList, + expandedSubtype, + unexpandedSubtype, + !!callTypeResult.isIncomplete, + constraints, + skipUnknownArgCheck, + inferenceContext, + recursionCount + ); + + if (callResult.argumentErrors) { + argumentErrors = true; + } + + if (callResult.isTypeIncomplete) { + isTypeIncomplete = true; + } + + if (callResult.overloadsUsedForCall) { + appendArray(overloadsUsedForCall, callResult.overloadsUsedForCall); + } + + specializedInitSelfType = callResult.specializedInitSelfType; + + return callResult.returnType; + }, + { + allowDiagnostics: true, + } + ); + } + ); + + // If we ended up with a "Never" type because all code paths returned + // undefined due to argument errors, transform the result into an Unknown + // to avoid subsequent false positives. + if (argumentErrors && isNever(returnType) && !returnType.priv.isNoReturn) { + returnType = UnknownType.create(); + } + + return { + argumentErrors, + returnType, + isTypeIncomplete, + specializedInitSelfType, + overloadsUsedForCall, + }; + } + + function validateCallArgsForSubtype( + errorNode: ExpressionNode, + argList: Arg[], + expandedCallType: Type, + unexpandedCallType: Type, + isCallTypeIncomplete: boolean, + constraints: ConstraintTracker | undefined, + skipUnknownArgCheck: boolean | undefined, + inferenceContext: InferenceContext | undefined, + recursionCount: number + ): CallResult { + function touchArgTypes() { + if (!isCallTypeIncomplete) { + argList.forEach((arg) => { + if (arg.valueExpression && !isSpeculativeModeInUse(arg.valueExpression)) { + getTypeOfArg(arg, /* inferenceContext */ undefined); + } + }); + } + } + + switch (expandedCallType.category) { + case TypeCategory.Never: + case TypeCategory.Unknown: + case TypeCategory.Any: { + // Create a dummy callable that accepts all arguments and validate + // that the argument expressions are valid. + const dummyFunctionType = FunctionType.createInstance('', '', '', FunctionTypeFlags.None); + FunctionType.addDefaultParams(dummyFunctionType); + + const dummyCallResult = validateCallForFunction( + errorNode, + argList, + dummyFunctionType, + isCallTypeIncomplete, + constraints, + skipUnknownArgCheck, + inferenceContext + ); + + return { ...dummyCallResult, returnType: expandedCallType }; + } + + case TypeCategory.Function: { + return validateCallForFunction( + errorNode, + argList, + expandedCallType, + isCallTypeIncomplete, + constraints, + skipUnknownArgCheck, + inferenceContext + ); + } + + case TypeCategory.Overloaded: { + return validateCallForOverloaded( + errorNode, + argList, + expandedCallType, + isCallTypeIncomplete, + constraints, + skipUnknownArgCheck, + inferenceContext + ); + } + + case TypeCategory.Class: { + if (isNoneInstance(expandedCallType)) { + addDiagnostic(DiagnosticRule.reportOptionalCall, LocMessage.noneNotCallable(), errorNode); + + touchArgTypes(); + return { argumentErrors: true }; + } + + if (TypeBase.isInstantiable(expandedCallType)) { + return validateCallForInstantiableClass( + errorNode, + argList, + expandedCallType, + unexpandedCallType, + skipUnknownArgCheck, + inferenceContext + ); + } + + return validateCallForClassInstance( + errorNode, + argList, + expandedCallType, + unexpandedCallType, + constraints, + skipUnknownArgCheck, + inferenceContext, + recursionCount + ); + } + + // TypeVars should have been expanded in most cases, + // but we still need to handle the case of Type[T] where + // T is a constrained type that contains a union. We also + // need to handle recursive type aliases. + case TypeCategory.TypeVar: { + // Viper 扩展:类型变量调用 `T(v)` 是 C 风格强转语义。 + // 泛型类方法中 `self.value = T(v)` 用类型参数做类型转换函数, + // T 在方法内是未实例化的 TypeVar,不可调用属正常;转换结果 + // 在泛型实例化时才确定。因此 Viper 方言下放行并返回 Unknown, + // 避免误报 "Object of type TypeVar is not callable"。 + if (IsViperDialect(AnalyzerNodeInfo.getFileInfo(errorNode).executionEnvironment.dialect)) { + return { returnType: UnknownType.create(), argumentErrors: false, overloadsUsedForCall: [] }; + } + + return validateCallArgs( + errorNode, + argList, + { type: transformPossibleRecursiveTypeAlias(expandedCallType), isIncomplete: isCallTypeIncomplete }, + constraints, + skipUnknownArgCheck, + inferenceContext, + recursionCount + ); + } + + case TypeCategory.Module: { + addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.moduleNotCallable(), errorNode); + + touchArgTypes(); + return { argumentErrors: true }; + } + } + + touchArgTypes(); + return { argumentErrors: true }; + } + + function validateCallForFunction( + errorNode: ExpressionNode, + argList: Arg[], + type: FunctionType, + isCallTypeIncomplete: boolean, + constraints: ConstraintTracker | undefined, + skipUnknownArgCheck: boolean | undefined, + inferenceContext: InferenceContext | undefined + ): CallResult { + if (TypeBase.isInstantiable(type)) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.callableNotInstantiable().format({ + type: printType(type), + }), + errorNode + ); + return { returnType: undefined, argumentErrors: true }; + } + + // The stdlib collections/__init__.pyi stub file defines namedtuple + // as a function rather than a class, so we need to check for it here. + if (FunctionType.isBuiltIn(type, 'namedtuple')) { + addDiagnostic(DiagnosticRule.reportUntypedNamedTuple, LocMessage.namedTupleNoTypes(), errorNode); + + const result: CallResult = { + returnType: createNamedTupleType(evaluatorInterface, errorNode, argList, /* includesTypes */ false), + }; + + validateArgs(errorNode, argList, { type: type }, constraints, skipUnknownArgCheck, inferenceContext); + + return result; + } + + // Handle the NewType specially, replacing the normal return type. + if (FunctionType.isBuiltIn(type, 'NewType')) { + return { returnType: createNewType(errorNode, argList) }; + } + + const functionResult = validateArgs( + errorNode, + argList, + { type, isIncomplete: isCallTypeIncomplete }, + constraints, + skipUnknownArgCheck, + inferenceContext + ); + + let isTypeIncomplete = !!functionResult.isTypeIncomplete; + let returnType = functionResult.returnType; + + let argumentErrors = !!functionResult.argumentErrors; + if (!argumentErrors) { + // Call the function transform logic to handle special-cased functions. + const transformed = applyFunctionTransform(evaluatorInterface, errorNode, argList, type, { + argumentErrors: !!functionResult.argumentErrors, + returnType: functionResult.returnType ?? UnknownType.create(isTypeIncomplete), + isTypeIncomplete, + }); + + returnType = transformed.returnType; + if (transformed.isTypeIncomplete) { + isTypeIncomplete = true; + } + if (transformed.argumentErrors) { + argumentErrors = true; + } + } + + if (FunctionType.isBuiltIn(type, '__import__')) { + // For the special __import__ type, we'll override the return type to be "Any". + // This is required because we don't know what module was imported, and we don't + // want to fail type checks when accessing members of the resulting module type. + returnType = AnyType.create(); + } + + return { + returnType, + isTypeIncomplete, + argumentErrors, + overloadsUsedForCall: functionResult.overloadsUsedForCall, + specializedInitSelfType: functionResult.specializedInitSelfType, + }; + } + + // Determines whether a symbol is abstract. In an ABC class, this means a function + // is specifically decorated with @abstractmethod. In a protocol class, the rules + // are more complicated and depend on whether the method is defined in a stub file. + function getAbstractSymbolInfo(classType: ClassType, symbolName: string): AbstractSymbol | undefined { + const isProtocolClass = ClassType.isProtocolClass(classType); + + const symbol = ClassType.getSymbolTable(classType).get(symbolName); + if (!symbol) { + return undefined; + } + + // Ignore instance variables. Also, ignore named tuple members, which are + // modeled in vipeye as instance variables, but their runtime implementation + // uses a descriptor object. + if (!symbol.isClassMember() && !symbol.isNamedTupleMemberMember()) { + return undefined; + } + + const lastDecl = getLastTypedDeclarationForSymbol(symbol); + if (!lastDecl) { + return undefined; + } + + // Handle protocol variables specially. + if (isProtocolClass && lastDecl.type === DeclarationType.Variable) { + // If none of the declarations involve assignments, assume it's + // not implemented in the protocol. + const allDecls = symbol.getDeclarations(); + if (!allDecls.some((decl) => decl.type === DeclarationType.Variable && !!decl.inferredTypeSource)) { + return { symbol, symbolName, classType, hasImplementation: false }; + } + } + + if (lastDecl.type !== DeclarationType.Function) { + return undefined; + } + + let isAbstract = false; + const lastFunctionInfo = getFunctionInfoFromDecorators(evaluatorInterface, lastDecl.node, /* isInClass */ true); + if ((lastFunctionInfo.flags & FunctionTypeFlags.AbstractMethod) !== 0) { + isAbstract = true; + } + + const isStubFile = AnalyzerNodeInfo.getFileInfo(lastDecl.node).isStubFile; + + // In an overloaded method, the first overload can also be marked abstract. + // In stub files, there is no implementation, so this is the only way to mark + // an overloaded method as abstract. + const firstDecl = symbol.getDeclarations()[0]; + let firstFunctionInfo: FunctionDecoratorInfo | undefined; + + if (firstDecl !== lastDecl && firstDecl.type === DeclarationType.Function) { + firstFunctionInfo = getFunctionInfoFromDecorators(evaluatorInterface, firstDecl.node, /* isInClass */ true); + if ((firstFunctionInfo.flags & FunctionTypeFlags.AbstractMethod) !== 0) { + isAbstract = true; + } + + // If there's no implementation, assume it's unimplemented. + if (isProtocolClass && (lastFunctionInfo.flags & FunctionTypeFlags.Overloaded) !== 0) { + // If this is a protocol class method defined in a stub file and + // it's not marked abstract, assume it's not abstract and implemented. + if (isProtocolClass && !isAbstract && isStubFile) { + return undefined; + } + + return { symbol, symbolName, classType, hasImplementation: false }; + } + } + + // In a non-protocol class, if the method isn't explicitly marked abstract, + // then it's not abstract. + if (!isProtocolClass && !isAbstract) { + return undefined; + } + + const hasImplementation = + !ParseTreeUtils.isSuiteEmpty(lastDecl.node.d.suite) && !methodAlwaysRaisesNotImplemented(lastDecl); + + // If this is a protocol class, the method isn't explicitly marked + // as abstract, and there is an implementation, then it's a default + // implementation, and it's not considered abstract. If it's in a stub + // file, assume it's implemented in this case. + if (isProtocolClass && !isAbstract) { + if (hasImplementation || isStubFile) { + return undefined; + } + } + + return { symbol, symbolName, classType, hasImplementation }; + } + + function validateCallForOverloaded( + errorNode: ExpressionNode, + argList: Arg[], + expandedCallType: OverloadedType, + isCallTypeIncomplete: boolean, + constraints: ConstraintTracker | undefined, + skipUnknownArgCheck: boolean | undefined, + inferenceContext: InferenceContext | undefined + ): CallResult { + const overloads = OverloadedType.getOverloads(expandedCallType); + // Handle the 'cast' call as a special case. + if ( + overloads.length > 0 && + FunctionType.isBuiltIn(overloads[0], ['typing.cast', 'typing_extensions.cast']) && + argList.length === 2 + ) { + return { returnType: evaluateCastCall(argList, errorNode) }; + } + + const callResult = validateOverloadedArgTypes( + errorNode, + argList, + { type: expandedCallType, isIncomplete: isCallTypeIncomplete }, + constraints, + skipUnknownArgCheck, + inferenceContext + ); + + let returnType = callResult.returnType ?? UnknownType.create(); + let isTypeIncomplete = !!callResult.isTypeIncomplete; + let argumentErrors = !!callResult.argumentErrors; + + if (!argumentErrors) { + // Call the function transform logic to handle special-cased functions. + const transformed = applyFunctionTransform(evaluatorInterface, errorNode, argList, expandedCallType, { + argumentErrors: !!callResult.argumentErrors, + returnType: callResult.returnType ?? UnknownType.create(isTypeIncomplete), + isTypeIncomplete, + }); + + returnType = transformed.returnType; + if (transformed.isTypeIncomplete) { + isTypeIncomplete = true; + } + + if (transformed.argumentErrors) { + argumentErrors = true; + } + } + + return { + returnType, + isTypeIncomplete, + argumentErrors, + overloadsUsedForCall: callResult.overloadsUsedForCall, + specializedInitSelfType: callResult.specializedInitSelfType, + }; + } + + function validateCallForInstantiableClass( + errorNode: ExpressionNode, + argList: Arg[], + expandedCallType: ClassType, + unexpandedCallType: Type, + skipUnknownArgCheck: boolean | undefined, + inferenceContext: InferenceContext | undefined + ): CallResult { + if (expandedCallType.priv.literalValue !== undefined) { + addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.literalNotCallable(), errorNode); + + return { returnType: UnknownType.create(), argumentErrors: true }; + } + + if (ClassType.isBuiltIn(expandedCallType)) { + const className = expandedCallType.priv.aliasName ?? expandedCallType.shared.name; + + // Handle a call to a metaclass explicitly. + if (isInstantiableMetaclass(expandedCallType)) { + // Viper 扩展:类型变量调用 `T(v)` 是 C 风格强转语义。 + // 泛型类方法中 `self.value = T(v)` 用类型参数做类型转换函数, + // T 在方法内是未实例化的 TypeVar,求值结果为 type[T](instantiable), + // isInstantiableMetaclass 会误判为 metaclass 调用并报 + // "Object of type TypeVar is not callable"。Viper 方言下放行 + // 并返回 Unknown,转换结果在泛型实例化时才确定。 + if (IsViperDialect(AnalyzerNodeInfo.getFileInfo(errorNode).executionEnvironment.dialect)) { + // 参数仍按普通参数校验(避免参数表达式未求值导致漏报), + // 但不校验可调用性,返回 Unknown。 + if (argList) { + argList.forEach((arg) => { + if (arg.valueExpression && !isSpeculativeModeInUse(arg.valueExpression)) { + getTypeOfArg(arg, /* inferenceContext */ undefined); + } + }); + } + return { returnType: UnknownType.create(), argumentErrors: false }; + } + + if (expandedCallType.priv.typeArgs && expandedCallType.priv.isTypeArgExplicit) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.objectNotCallable().format({ + type: printType(expandedCallType), + }), + errorNode + ); + return { returnType: UnknownType.create(), argumentErrors: true }; + } + + // Validate the constructor arguments. + validateConstructorArgs( + evaluatorInterface, + errorNode, + argList, + expandedCallType, + skipUnknownArgCheck, + inferenceContext + ); + + // The one-parameter form of "type" returns the class + // for the specified object. + if (expandedCallType.shared.name === 'type' && argList.length === 1) { + const argTypeResult = getTypeOfArg(argList[0], /* inferenceContext */ undefined); + const argType = argTypeResult.type; + const returnType = mapSubtypes(argType, (subtype) => { + if (isNever(subtype)) { + return subtype; + } + + if (isClass(subtype)) { + // Specifically handle the case where the subtype is a class-like + // object created by calling NewType. At runtime, it's actually + // a FunctionType object. + if ( + isClassInstance(subtype) && + ClassType.isNewTypeClass(subtype) && + !subtype.priv.includeSubclasses + ) { + if (prefetched?.functionClass) { + return prefetched.functionClass; + } + } + + return convertToInstantiable(stripLiteralValue(subtype)); + } + + if (TypeBase.isInstance(subtype)) { + if (isFunction(subtype) || isTypeVar(subtype)) { + return convertToInstantiable(subtype); + } + } + + return ClassType.specialize(ClassType.cloneAsInstance(expandedCallType), [ + UnknownType.create(), + ]); + }); + + return { returnType, isTypeIncomplete: argTypeResult.isIncomplete }; + } + + if (argList.length >= 2) { + // The two-parameter form of a call to a metaclass returns a new class + // built from the specified base types. + return { + returnType: createClassFromMetaclass(errorNode, argList, expandedCallType) || AnyType.create(), + }; + } + + // If the parameter to type() is not statically known, + // fall back to Any. + return { returnType: AnyType.create() }; + } + + if (className === 'TypeVar') { + return { + returnType: createTypeVarType(errorNode, expandedCallType, argList), + }; + } + + if (className === 'TypeVarTuple') { + return { + returnType: createTypeVarTupleType(errorNode, expandedCallType, argList), + }; + } + + if (className === 'ParamSpec') { + return { + returnType: createParamSpecType(errorNode, expandedCallType, argList), + }; + } + + if (className === 'TypeAliasType') { + const newTypeAlias = createTypeAliasType(errorNode, argList); + if (newTypeAlias) { + return { returnType: newTypeAlias }; + } + } + + if (className === 'NamedTuple') { + const result: CallResult = { + returnType: createNamedTupleType(evaluatorInterface, errorNode, argList, /* includesTypes */ true), + }; + + const initTypeResult = getBoundInitMethod( + evaluatorInterface, + errorNode, + ClassType.cloneAsInstance(expandedCallType), + /* diag */ undefined, + /* additionalFlags */ MemberAccessFlags.Default + ); + + if (initTypeResult && isOverloaded(initTypeResult.type)) { + validateOverloadedArgTypes( + errorNode, + argList, + { type: initTypeResult.type }, + /* constraints */ undefined, + skipUnknownArgCheck, + /* inferenceContext */ undefined + ); + } + + return result; + } + + if (className === 'NewType') { + return { returnType: createNewType(errorNode, argList) }; + } + + // Handle sentinel calls specially. + if ( + expandedCallType.shared.fullName === 'builtins.sentinel' || + expandedCallType.shared.fullName === 'typing_extensions.sentinel' || + expandedCallType.shared.fullName === 'typing_extensions.Sentinel' + ) { + return { returnType: createSentinelType(evaluatorInterface, errorNode, argList) }; + } + + if (ClassType.isSpecialFormClass(expandedCallType)) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.typeNotIntantiable().format({ type: className }), + errorNode + ); + + return { returnType: UnknownType.create(), argumentErrors: true }; + } + + if (className === 'TypedDict') { + return { returnType: createTypedDictType(evaluatorInterface, errorNode, expandedCallType, argList) }; + } + + if (className === 'auto' && argList.length === 0) { + return { returnType: getEnumAutoValueType(evaluatorInterface, errorNode) }; + } + } + + // Is it a call to an Enum class factory? + if ( + isClass(expandedCallType) && + expandedCallType.shared.effectiveMetaclass && + isClass(expandedCallType.shared.effectiveMetaclass) && + isEnumMetaclass(expandedCallType.shared.effectiveMetaclass) && + !isEnumClassWithMembers(evaluatorInterface, expandedCallType) + ) { + return { + returnType: + createEnumType(evaluatorInterface, errorNode, expandedCallType, argList) ?? + convertToInstance(unexpandedCallType), + }; + } + + if (ClassType.supportsAbstractMethods(expandedCallType)) { + const abstractSymbols = getAbstractSymbols(expandedCallType); + + if ( + abstractSymbols.length > 0 && + !expandedCallType.priv.includeSubclasses && + !isTypeVar(unexpandedCallType) + ) { + // If the class is abstract, it can't be instantiated. + const diagAddendum = new DiagnosticAddendum(); + const errorsToDisplay = 2; + + abstractSymbols.forEach((abstractMethod, index) => { + if (index === errorsToDisplay) { + diagAddendum.addMessage( + LocAddendum.memberIsAbstractMore().format({ + count: abstractSymbols.length - errorsToDisplay, + }) + ); + } else if (index < errorsToDisplay) { + if (isInstantiableClass(abstractMethod.classType)) { + const className = abstractMethod.classType.shared.name; + diagAddendum.addMessage( + LocAddendum.memberIsAbstract().format({ + type: className, + name: abstractMethod.symbolName, + }) + ); + } + } + }); + + addDiagnostic( + DiagnosticRule.reportAbstractUsage, + LocMessage.instantiateAbstract().format({ + type: expandedCallType.shared.name, + }) + diagAddendum.getString(), + errorNode + ); + } + } + + if (ClassType.isProtocolClass(expandedCallType) && !expandedCallType.priv.includeSubclasses) { + // If the class is a protocol, it can't be instantiated. + addDiagnostic( + DiagnosticRule.reportAbstractUsage, + LocMessage.instantiateProtocol().format({ + type: expandedCallType.shared.name, + }), + errorNode + ); + } + + // Assume this is a call to the constructor. + const constructorResult = validateConstructorArgs( + evaluatorInterface, + errorNode, + argList, + expandedCallType, + skipUnknownArgCheck, + inferenceContext + ); + + const overloadsUsedForCall = constructorResult.overloadsUsedForCall; + const argumentErrors = constructorResult.argumentErrors; + const isTypeIncomplete = constructorResult.isTypeIncomplete; + + let returnType = constructorResult.returnType; + + // If the expandedCallType originated from a TypeVar, convert + // the constructed type back to the TypeVar. For example, if + // we have `cls: Type[_T]` followed by `_T()`. + if (isTypeVar(unexpandedCallType)) { + returnType = convertToInstance(unexpandedCallType); + } + + // If we instantiated the "deprecated" class, attach the deprecation + // message to the instance. + if ( + errorNode.nodeType === ParseNodeType.Call && + returnType && + isClassInstance(returnType) && + ClassType.isBuiltIn(returnType, 'deprecated') + ) { + returnType = ClassType.cloneForDeprecatedInstance(returnType, getDeprecatedMessageFromCall(errorNode)); + } + + // If we instantiated a type, transform it into a class. + // This can happen if someone directly instantiates a metaclass + // deriving from type. + if ( + returnType && + isClassInstance(returnType) && + returnType.shared.mro.some( + (baseClass) => isInstantiableClass(baseClass) && ClassType.isBuiltIn(baseClass, 'type') + ) + ) { + let newClassName = '__class_' + returnType.shared.name; + if (argList.length === 3) { + const firstArgType = getTypeOfArg(argList[0], /* inferenceContext */ undefined).type; + + if ( + isClassInstance(firstArgType) && + ClassType.isBuiltIn(firstArgType, 'str') && + typeof firstArgType.priv.literalValue === 'string' + ) { + newClassName = firstArgType.priv.literalValue; + } + } + + const newClassType = ClassType.createInstantiable( + newClassName, + '', + '', + AnalyzerNodeInfo.getFileInfo(errorNode).fileUri, + ClassTypeFlags.None, + ParseTreeUtils.getTypeSourceId(errorNode), + ClassType.cloneAsInstantiable(returnType), + ClassType.cloneAsInstantiable(returnType) + ); + newClassType.shared.baseClasses.push(getBuiltInType(errorNode, 'object')); + newClassType.shared.effectiveMetaclass = expandedCallType; + newClassType.shared.declaration = returnType.shared.declaration; + + computeMroLinearization(newClassType); + returnType = newClassType; + } + + return { returnType, overloadsUsedForCall, argumentErrors, isTypeIncomplete }; + } + + function validateCallForClassInstance( + errorNode: ExpressionNode, + argList: Arg[], + expandedCallType: ClassType, + unexpandedCallType: Type, + constraints: ConstraintTracker | undefined, + skipUnknownArgCheck: boolean | undefined, + inferenceContext: InferenceContext | undefined, + recursionCount: number + ): CallResult { + const callDiag = new DiagnosticAddendum(); + const callMethodResult = getTypeOfBoundMember( + errorNode, + expandedCallType, + '__call__', + /* usage */ undefined, + callDiag, + MemberAccessFlags.SkipInstanceMembers | MemberAccessFlags.SkipAttributeAccessOverride, + /* selfType */ undefined, + recursionCount + ); + const callMethodType = callMethodResult?.type; + + if (!callMethodType || callMethodResult.typeErrors) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.objectNotCallable().format({ + type: printType(expandedCallType), + }) + callDiag.getString(), + errorNode + ); + + return { returnType: UnknownType.create(), argumentErrors: true }; + } + + const callResult = validateCallArgs( + errorNode, + argList, + { type: callMethodType }, + constraints, + skipUnknownArgCheck, + inferenceContext, + recursionCount + ); + + let returnType = callResult.returnType ?? UnknownType.create(); + if ( + isTypeVar(unexpandedCallType) && + TypeBase.isInstantiable(unexpandedCallType) && + isClass(expandedCallType) && + ClassType.isBuiltIn(expandedCallType, 'type') + ) { + // Handle the case where a type[T] is being called. We presume this + // will instantiate an object of type T. + returnType = convertToInstance(unexpandedCallType); + } + + return { + returnType, + argumentErrors: callResult.argumentErrors, + overloadsUsedForCall: callResult.overloadsUsedForCall, + }; + } + + // Evaluates the type of the "cast" call. + function evaluateCastCall(argList: Arg[], errorNode: ExpressionNode) { + if (argList[0].argCategory !== ArgCategory.Simple && argList[0].valueExpression) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.unpackInAnnotation(), + argList[0].valueExpression + ); + } + + // Verify that the cast is necessary. + let castToType = getTypeOfArgExpectingType(argList[0], { typeExpression: true }).type; + + const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(errorNode); + castToType = makeTypeVarsBound(castToType, liveScopeIds); + + let castFromType = getTypeOfArg(argList[1], /* inferenceContext */ undefined).type; + + if (castFromType.props?.specialForm) { + castFromType = castFromType.props.specialForm; + } + + if (TypeBase.isInstantiable(castToType) && !isUnknown(castToType)) { + if ( + isTypeSame(convertToInstance(castToType), castFromType, { + ignorePseudoGeneric: true, + }) + ) { + addDiagnostic( + DiagnosticRule.reportUnnecessaryCast, + LocMessage.unnecessaryCast().format({ + type: printType(castFromType), + }), + errorNode + ); + } + } + + return convertToInstance(castToType); + } + + // Expands any unpacked tuples within an argument list. + function expandArgList(argList: Arg[]): Arg[] { + const expandedArgList: Arg[] = []; + + for (const arg of argList) { + if (arg.argCategory === ArgCategory.UnpackedList) { + const argType = getTypeOfArg(arg, /* inferenceContext */ undefined).type; + + // If this is a tuple with specified element types, use those + // specified types rather than using the more generic iterator + // type which will be a union of all element types. + const combinedArgType = combineSameSizedTuples( + makeTopLevelTypeVarsConcrete(argType), + prefetched?.tupleClass + ); + + if (isClassInstance(combinedArgType) && isTupleClass(combinedArgType)) { + const tupleTypeArgs = combinedArgType.priv.tupleTypeArgs ?? []; + + if (tupleTypeArgs.length !== 1 || !tupleTypeArgs[0].isUnbounded) { + for (const tupleTypeArg of tupleTypeArgs) { + if (tupleTypeArg.isUnbounded) { + expandedArgList.push({ + ...arg, + argCategory: ArgCategory.UnpackedList, + valueExpression: undefined, + typeResult: { + type: makeTupleObject(evaluatorInterface, [tupleTypeArg]), + }, + }); + } else { + expandedArgList.push({ + ...arg, + argCategory: ArgCategory.Simple, + valueExpression: undefined, + typeResult: { + type: tupleTypeArg.type, + }, + }); + } + } + continue; + } + } + } + + expandedArgList.push(arg); + } + + return expandedArgList; + } + + // Matches the arguments passed to a function to the corresponding parameters in that + // function. This matching is done based on positions and keywords. Type evaluation and + // validation is left to the caller. + // This logic is based on PEP 3102: https://www.python.org/dev/peps/pep-3102/ + function matchArgsToParams( + errorNode: ExpressionNode, + argList: Arg[], + typeResult: TypeResult, + overloadIndex: number + ): MatchArgsToParamsResult { + const overload = typeResult.type; + const paramDetails = getParamListDetails(overload, { disallowExtraKwargsForTd: true }); + const paramSpec = FunctionType.getParamSpecFromArgsKwargs(overload); + + let argIndex = 0; + let unpackedArgOfUnknownLength = false; + let unpackedArgMapsToVariadic = false; + let reportedArgError = false; + let isTypeIncomplete = !!typeResult.isIncomplete; + let isTypeVarTupleFullyMatched = false; + + // Expand any unpacked tuples in the arg list. + argList = expandArgList(argList); + + // Construct an object that racks which parameters have been assigned arguments. + const paramTracker = new ParamAssignmentTracker(paramDetails.params); + + let positionalOnlyLimitIndex = paramDetails.positionOnlyParamCount; + let positionParamLimitIndex = paramDetails.firstKeywordOnlyIndex ?? paramDetails.params.length; + + const varArgListParamIndex = paramDetails.argsIndex; + const varArgDictParamIndex = paramDetails.kwargsIndex; + + // Is this an function that uses the *args and **kwargs + // from a param spec? If so, we need to treat all positional parameters + // prior to the *args as positional-only according to PEP 612. + let paramSpecArgList: Arg[] | undefined; + let paramSpecTarget: ParamSpecType | undefined; + let hasParamSpecArgsKwargs = false; + + // Determine how many positional args are being passed before + // we see a keyword arg. + let positionalArgCount = argList.findIndex( + (arg) => arg.argCategory === ArgCategory.UnpackedDictionary || arg.name !== undefined + ); + if (positionalArgCount < 0) { + positionalArgCount = argList.length; + } + + if (varArgListParamIndex !== undefined && varArgDictParamIndex !== undefined) { + assert(paramDetails.params[varArgListParamIndex], 'varArgListParamIndex params entry is undefined'); + const varArgListParamType = paramDetails.params[varArgListParamIndex].type; + assert(paramDetails.params[varArgDictParamIndex], 'varArgDictParamIndex params entry is undefined'); + const varArgDictParamType = paramDetails.params[varArgDictParamIndex].type; + + if ( + isParamSpec(varArgListParamType) && + varArgListParamType.priv.paramSpecAccess === 'args' && + isParamSpec(varArgDictParamType) && + varArgDictParamType.priv.paramSpecAccess === 'kwargs' && + varArgListParamType.shared.name === varArgDictParamType.shared.name + ) { + hasParamSpecArgsKwargs = true; + + // Does this function define the param spec, or is it an inner + // function nested within another function that defines the param + // spec? We need to handle these two cases differently. + const paramSpecScopeId = varArgListParamType.priv.scopeId; + + if (getTypeVarScopeIds(overload).some((id) => id === paramSpecScopeId)) { + paramSpecArgList = []; + paramSpecTarget = TypeVarType.cloneForParamSpecAccess(varArgListParamType, /* access */ undefined); + } else { + positionalOnlyLimitIndex = varArgListParamIndex; + positionalArgCount = Math.min(varArgListParamIndex, positionalArgCount); + positionParamLimitIndex = varArgListParamIndex; + } + } + } else if (paramSpec) { + if (getTypeVarScopeIds(overload).some((id) => id === paramSpec.priv.scopeId)) { + hasParamSpecArgsKwargs = true; + paramSpecArgList = []; + paramSpecTarget = paramSpec; + } + } + + // If there are keyword arguments present after a *args argument, + // the keyword arguments may target one or more parameters that are positional. + // In this case, we will limit the number of positional parameters so the + // *args doesn't consume them all. + if (argList.some((arg) => arg.argCategory === ArgCategory.UnpackedList)) { + argList.forEach((arg) => { + if (arg.name) { + const keywordParamIndex = paramDetails.params.findIndex((paramInfo) => { + assert(paramInfo, 'paramInfo entry is undefined fork kwargs check'); + return ( + paramInfo.param.name === arg.name!.d.value && + paramInfo.param.category === ParamCategory.Simple + ); + }); + + // Is this a parameter that can be interpreted as either a keyword or a positional? + // If so, we'll treat it as a keyword parameter in this case because it's being + // targeted by a keyword argument. + if (keywordParamIndex >= 0 && keywordParamIndex >= positionalOnlyLimitIndex) { + if (positionParamLimitIndex < 0 || keywordParamIndex < positionParamLimitIndex) { + positionParamLimitIndex = keywordParamIndex; + } + } + } + }); + } + + // If we didn't see any special cases, then all parameters are positional. + if (positionParamLimitIndex < 0) { + positionParamLimitIndex = paramDetails.params.length; + } + + let validateArgTypeParams: ValidateArgTypeParams[] = []; + + let activeParam: FunctionParam | undefined; + function trySetActive(arg: Arg, param: FunctionParam) { + if (arg.active) { + activeParam = param; + } + } + + const foundUnpackedListArg = argList.find((arg) => arg.argCategory === ArgCategory.UnpackedList) !== undefined; + + // Map the positional args to parameters. + let paramIndex = 0; + + while (argIndex < positionalArgCount) { + if (argIndex < positionalOnlyLimitIndex && argList[argIndex].name) { + const nameNode = argList[argIndex].name; + if (nameNode) { + addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.argPositional(), nameNode); + reportedArgError = true; + } + } + + const remainingArgCount = positionalArgCount - argIndex; + const remainingParamCount = positionParamLimitIndex - paramIndex - 1; + + if (paramIndex >= positionParamLimitIndex) { + if (paramSpecArgList) { + // Push the remaining positional args onto the param spec arg list. + while (argIndex < positionalArgCount) { + paramSpecArgList.push(argList[argIndex]); + argIndex++; + } + } else { + let tooManyPositionals = false; + + if (argList[argIndex].argCategory === ArgCategory.UnpackedList) { + // If this is an unpacked iterable, we will conservatively assume that it + // might have zero iterations unless we can tell from its type that it + // definitely has at least one iterable value. + const argType = getTypeOfArg(argList[argIndex], /* inferenceContext */ undefined).type; + + if ( + isClassInstance(argType) && + isTupleClass(argType) && + !isUnboundedTupleClass(argType) && + argType.priv.tupleTypeArgs !== undefined && + argType.priv.tupleTypeArgs.length > 0 + ) { + tooManyPositionals = true; + } else { + unpackedArgOfUnknownLength = true; + } + } else { + tooManyPositionals = true; + } + + if (tooManyPositionals) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + positionParamLimitIndex === 1 + ? LocMessage.argPositionalExpectedOne() + : LocMessage.argPositionalExpectedCount().format({ + expected: positionParamLimitIndex, + }), + argList[argIndex].valueExpression ?? errorNode + ); + } + reportedArgError = true; + } + } + break; + } + + if (paramIndex >= paramDetails.params.length) { + break; + } + + assert(paramDetails.params[paramIndex], 'paramIndex params entry is undefined'); + const paramInfo = paramDetails.params[paramIndex]; + const paramType = paramInfo.type; + const paramName = paramInfo.param.name; + + const isParamVariadic = paramInfo.param.category === ParamCategory.ArgsList && isUnpacked(paramType); + + if (argList[argIndex].argCategory === ArgCategory.UnpackedList) { + let isArgCompatibleWithVariadic = false; + + const argTypeResult = getTypeOfArg(argList[argIndex], /* inferenceContext */ undefined); + + let listElementType: Type | undefined; + let enforceIterable = false; + let advanceToNextArg = false; + + // Handle the case where *args is being passed to a function defined + // with a ParamSpec and a Concatenate operator. PEP 612 indicates that + // all positional parameters specified in the Concatenate must be + // filled explicitly. + if (paramIndex < positionParamLimitIndex) { + if ( + isParamSpec(argTypeResult.type) && + argTypeResult.type.priv.paramSpecAccess === 'args' && + paramInfo.param.category !== ParamCategory.ArgsList + ) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + positionParamLimitIndex === 1 + ? LocMessage.argPositionalExpectedOne() + : LocMessage.argPositionalExpectedCount().format({ + expected: positionParamLimitIndex, + }), + argList[argIndex].valueExpression ?? errorNode + ); + } + reportedArgError = true; + } + } + + const argType = argTypeResult.type; + + if (isParamVariadic && isUnpackedTypeVarTuple(argType)) { + // Allow an unpacked TypeVarTuple arg to satisfy an + // unpacked TypeVarTuple param. + listElementType = argType; + isArgCompatibleWithVariadic = true; + advanceToNextArg = true; + isTypeVarTupleFullyMatched = true; + } else if ( + isClassInstance(argType) && + isTupleClass(argType) && + argType.priv.tupleTypeArgs && + argType.priv.tupleTypeArgs.length === 1 && + isUnpackedTypeVarTuple(argType.priv.tupleTypeArgs[0].type) + ) { + // Handle the case where an unpacked TypeVarTuple has + // been packaged into a tuple. + listElementType = argType.priv.tupleTypeArgs[0].type; + isArgCompatibleWithVariadic = true; + advanceToNextArg = true; + isTypeVarTupleFullyMatched = true; + } else if (isParamVariadic && isClassInstance(argType) && isTupleClass(argType)) { + // Handle the case where an unpacked tuple argument is + // matched to a TypeVarTuple parameter. + isArgCompatibleWithVariadic = true; + advanceToNextArg = true; + + // Determine whether we should treat the variadic type as fully matched. + // This depends on how many args and unmatched parameters exist. + if (remainingArgCount < remainingParamCount) { + isTypeVarTupleFullyMatched = true; + } + + listElementType = ClassType.cloneForUnpacked(argType); + } else if (isParamSpec(argType) && argType.priv.paramSpecAccess === 'args') { + listElementType = undefined; + } else { + listElementType = getTypeOfIterator( + { type: argType, isIncomplete: argTypeResult.isIncomplete }, + /* isAsync */ false, + errorNode, + /* emitNotIterableError */ false + )?.type; + + if (!listElementType) { + enforceIterable = true; + } + + unpackedArgOfUnknownLength = true; + + if (paramInfo.param.category === ParamCategory.ArgsList) { + unpackedArgMapsToVariadic = true; + } + + if (isParamVariadic && listElementType) { + isArgCompatibleWithVariadic = true; + listElementType = makeTupleObject( + evaluatorInterface, + [{ type: listElementType, isUnbounded: true }], + /* isUnpacked */ true + ); + } + } + + const funcArg: Arg | undefined = listElementType + ? { + argCategory: ArgCategory.Simple, + typeResult: { type: listElementType, isIncomplete: argTypeResult.isIncomplete }, + } + : { ...argList[argIndex], enforceIterable }; + + if (argTypeResult.isIncomplete) { + isTypeIncomplete = true; + } + + // It's not allowed to use unpacked arguments with a variadic *args + // parameter unless the argument is a variadic arg as well. + if (isParamVariadic && !isArgCompatibleWithVariadic) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.unpackedArgWithVariadicParam(), + argList[argIndex].valueExpression || errorNode + ); + } + reportedArgError = true; + } else { + if (paramSpecArgList && paramInfo.param.category !== ParamCategory.Simple) { + paramSpecArgList.push(argList[argIndex]); + } + + if (funcArg) { + validateArgTypeParams.push({ + paramCategory: paramInfo.param.category, + paramType, + requiresTypeVarMatching: requiresSpecialization(paramType), + argument: funcArg, + errorNode: argList[argIndex].valueExpression ?? errorNode, + paramName, + isParamNameSynthesized: FunctionParam.isNameSynthesized(paramInfo.param), + mapsToVarArgList: isParamVariadic && remainingArgCount > remainingParamCount, + }); + } + } + + trySetActive(argList[argIndex], paramDetails.params[paramIndex].param); + + // Note that the parameter has received an argument. + if (paramName && paramDetails.params[paramIndex].param.category === ParamCategory.Simple) { + paramTracker.markArgReceived(paramInfo); + } + + if (advanceToNextArg || paramDetails.params[paramIndex].param.category === ParamCategory.ArgsList) { + argIndex++; + } + + if ( + isTypeVarTupleFullyMatched || + paramDetails.params[paramIndex].param.category !== ParamCategory.ArgsList + ) { + paramIndex++; + } + } else if (paramDetails.params[paramIndex].param.category === ParamCategory.ArgsList) { + trySetActive(argList[argIndex], paramDetails.params[paramIndex].param); + + if (paramSpecArgList) { + paramSpecArgList.push(argList[argIndex]); + argIndex++; + } else { + let paramCategory = paramDetails.params[paramIndex].param.category; + let effectiveParamType = paramType; + const paramName = paramDetails.params[paramIndex].param.name; + + if ( + isUnpackedClass(paramType) && + paramType.priv.tupleTypeArgs && + paramType.priv.tupleTypeArgs.length > 0 + ) { + effectiveParamType = paramType.priv.tupleTypeArgs[0].type; + } + + paramCategory = isUnpacked(effectiveParamType) ? ParamCategory.ArgsList : ParamCategory.Simple; + + if (remainingArgCount <= remainingParamCount) { + if (remainingArgCount < remainingParamCount) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { + // Have we run out of arguments and still have parameters left to fill? + addDiagnostic( + DiagnosticRule.reportCallIssue, + remainingArgCount === 1 + ? LocMessage.argMorePositionalExpectedOne() + : LocMessage.argMorePositionalExpectedCount().format({ + expected: remainingArgCount, + }), + argList[argIndex].valueExpression || errorNode + ); + } + reportedArgError = true; + } + + paramIndex++; + } else { + validateArgTypeParams.push({ + paramCategory, + paramType: effectiveParamType, + requiresTypeVarMatching: requiresSpecialization(paramType), + argument: argList[argIndex], + errorNode: argList[argIndex].valueExpression || errorNode, + paramName, + isParamNameSynthesized: FunctionParam.isNameSynthesized( + paramDetails.params[paramIndex].param + ), + mapsToVarArgList: true, + }); + + argIndex++; + } + } + } else { + const paramInfo = paramDetails.params[paramIndex]; + const paramName = paramInfo.param.name; + + validateArgTypeParams.push({ + paramCategory: paramInfo.param.category, + paramType, + requiresTypeVarMatching: requiresSpecialization(paramType), + argument: argList[argIndex], + errorNode: argList[argIndex].valueExpression || errorNode, + paramName, + isParamNameSynthesized: FunctionParam.isNameSynthesized(paramInfo.param), + }); + trySetActive(argList[argIndex], paramInfo.param); + + // Note that the parameter has received an argument. + paramTracker.markArgReceived(paramInfo); + + argIndex++; + paramIndex++; + } + } + + // If there weren't enough positional arguments to populate all of the + // positional-only parameters and the next positional-only parameter is + // an unbounded tuple, skip past it. + let skippedArgsParam = false; + if ( + positionalOnlyLimitIndex >= 0 && + paramIndex < positionalOnlyLimitIndex && + paramIndex < paramDetails.params.length && + paramDetails.params[paramIndex].param.category === ParamCategory.ArgsList && + !isParamSpec(paramDetails.params[paramIndex].type) + ) { + paramIndex++; + skippedArgsParam = true; + } + + // Check if there weren't enough positional arguments to populate all of + // the positional-only parameters. + if ( + positionalOnlyLimitIndex >= 0 && + paramIndex < positionalOnlyLimitIndex && + (!foundUnpackedListArg || hasParamSpecArgsKwargs) + ) { + const firstParamWithDefault = paramDetails.params.findIndex((paramInfo) => !!paramInfo.defaultType); + const positionOnlyWithoutDefaultsCount = + firstParamWithDefault >= 0 && firstParamWithDefault < positionalOnlyLimitIndex + ? firstParamWithDefault + : positionalOnlyLimitIndex; + + // Calculate the number of remaining positional parameters to report. + let argsRemainingCount = positionOnlyWithoutDefaultsCount - positionalArgCount; + if (skippedArgsParam) { + // If we skipped an args parameter above, reduce the count by one + // because it's permitted to pass zero arguments to *args. + argsRemainingCount--; + } + + const firstArgsParam = paramDetails.params.findIndex( + (paramInfo) => paramInfo.param.category === ParamCategory.ArgsList && !isParamSpec(paramInfo.type) + ); + if (firstArgsParam >= paramIndex && firstArgsParam < positionalOnlyLimitIndex) { + // If there is another args parameter beyond the current param index, + // reduce the count by one because it's permitted to pass zero arguments + // to *args. + argsRemainingCount--; + } + + if (argsRemainingCount > 0) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + argsRemainingCount === 1 + ? LocMessage.argMorePositionalExpectedOne() + : LocMessage.argMorePositionalExpectedCount().format({ + expected: argsRemainingCount, + }), + argList.length > positionalArgCount + ? argList[positionalArgCount].valueExpression || errorNode + : errorNode + ); + } + reportedArgError = true; + } + } + + if (!reportedArgError) { + let unpackedDictKeyNames: string[] | undefined; + let unpackedDictArgType: Type | undefined; + + // Now consume any keyword arguments. + while (argIndex < argList.length) { + if (argList[argIndex].argCategory === ArgCategory.UnpackedDictionary) { + // Verify that the type used in this expression is a SupportsKeysAndGetItem[str, T]. + const argTypeResult = getTypeOfArg( + argList[argIndex], + makeInferenceContext(paramDetails.unpackedKwargsTypedDictType) + ); + const argType = argTypeResult.type; + + if (argTypeResult.isIncomplete) { + isTypeIncomplete = true; + } + + if (isAnyOrUnknown(argType)) { + unpackedDictArgType = argType; + } else if (isClassInstance(argType) && ClassType.isTypedDictClass(argType)) { + // Handle the special case where it is a TypedDict and we know which + // keys are present. + const tdEntries = getTypedDictMembersForClass(evaluatorInterface, argType); + const diag = new DiagnosticAddendum(); + + tdEntries.knownItems.forEach((entry, name) => { + const paramEntry = paramTracker.lookupName(name); + if (paramEntry) { + if (paramEntry.argsReceived > 0) { + diag.addMessage(LocMessage.paramAlreadyAssigned().format({ name })); + } else { + paramEntry.argsReceived++; + + const paramInfoIndex = paramDetails.params.findIndex( + (paramInfo) => paramInfo.param.name === name + ); + assert(paramInfoIndex >= 0); + const paramType = paramDetails.params[paramInfoIndex].type; + + validateArgTypeParams.push({ + paramCategory: ParamCategory.Simple, + paramType, + requiresTypeVarMatching: requiresSpecialization(paramType), + argument: { + argCategory: ArgCategory.Simple, + typeResult: { type: entry.valueType }, + }, + errorNode: argList[argIndex].valueExpression ?? errorNode, + paramName: name, + }); + } + } else if (paramDetails.kwargsIndex !== undefined) { + const paramType = paramDetails.params[paramDetails.kwargsIndex].type; + validateArgTypeParams.push({ + paramCategory: ParamCategory.KwargsDict, + paramType, + requiresTypeVarMatching: requiresSpecialization(paramType), + argument: { + argCategory: ArgCategory.Simple, + typeResult: { type: entry.valueType }, + }, + errorNode: argList[argIndex].valueExpression ?? errorNode, + paramName: name, + }); + + // Remember that this parameter has already received a value. + paramTracker.addKeywordParam(name, paramDetails.params[paramDetails.kwargsIndex]); + } else { + // If the function doesn't have a **kwargs parameter, we need to emit an error. + // However, it's possible that there was a **kwargs but it was eliminated by + // getParamListDetails because it was associated with an unpacked TypedDict. + // In this case, we can skip the error. + if (!paramDetails.hasUnpackedTypedDict) { + diag.addMessage(LocMessage.paramNameMissing().format({ name })); + } + } + }); + + const extraItemsType = tdEntries.extraItems?.valueType ?? getObjectType(); + if (!isNever(extraItemsType)) { + if (paramDetails.kwargsIndex !== undefined) { + const kwargsParam = paramDetails.params[paramDetails.kwargsIndex]; + + validateArgTypeParams.push({ + paramCategory: ParamCategory.KwargsDict, + paramType: kwargsParam.type, + requiresTypeVarMatching: requiresSpecialization(kwargsParam.type), + argument: { + argCategory: ArgCategory.UnpackedDictionary, + typeResult: { type: extraItemsType }, + }, + errorNode: argList[argIndex].valueExpression ?? errorNode, + paramName: kwargsParam.param.name, + }); + } + } + + if (!diag.isEmpty()) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.unpackedTypedDictArgument() + diag.getString(), + argList[argIndex].valueExpression || errorNode + ); + } + reportedArgError = true; + } + } else if (paramSpec && isParamSpecKwargs(paramSpec, argType)) { + unpackedDictArgType = AnyType.create(); + + if (!paramSpecArgList) { + validateArgTypeParams.push({ + paramCategory: ParamCategory.KwargsDict, + paramType: paramSpec, + requiresTypeVarMatching: false, + argument: argList[argIndex], + argType: isParamSpec(argType) ? undefined : AnyType.create(), + errorNode: argList[argIndex].valueExpression || errorNode, + }); + } + } else { + const strObjType = getBuiltInObject(errorNode, 'str'); + + if ( + prefetched?.supportsKeysAndGetItemClass && + isInstantiableClass(prefetched.supportsKeysAndGetItemClass) && + strObjType && + isClassInstance(strObjType) + ) { + const mappingConstraints = new ConstraintTracker(); + let isValidMappingType = false; + + // If this was a TypeVar (e.g. for pseudo-generic classes), + // don't emit this error. + if (isTypeVar(argType)) { + isValidMappingType = true; + } else if ( + assignType( + ClassType.cloneAsInstance(prefetched.supportsKeysAndGetItemClass), + argType, + /* diag */ undefined, + mappingConstraints + ) + ) { + const specializedMapping = solveAndApplyConstraints( + prefetched.supportsKeysAndGetItemClass, + mappingConstraints + ) as ClassType; + const typeArgs = specializedMapping.priv.typeArgs; + if (typeArgs && typeArgs.length >= 2) { + if (assignType(strObjType, typeArgs[0])) { + isValidMappingType = true; + } + + unpackedDictKeyNames = []; + doForEachSubtype(typeArgs[0], (keyType) => { + if (isClassInstance(keyType) && typeof keyType.priv.literalValue === 'string') { + unpackedDictKeyNames?.push(keyType.priv.literalValue); + } else { + unpackedDictKeyNames = undefined; + } + }); + + unpackedDictArgType = typeArgs[1]; + } else { + isValidMappingType = true; + unpackedDictArgType = UnknownType.create(); + } + } + + unpackedArgOfUnknownLength = true; + + if (paramDetails.kwargsIndex !== undefined && unpackedDictArgType) { + const paramType = paramDetails.params[paramDetails.kwargsIndex].type; + validateArgTypeParams.push({ + paramCategory: ParamCategory.Simple, + paramType, + requiresTypeVarMatching: requiresSpecialization(paramType), + argType: unpackedDictArgType, + argument: argList[argIndex], + errorNode: argList[argIndex].valueExpression || errorNode, + paramName: paramDetails.params[paramDetails.kwargsIndex].param.name, + }); + + unpackedArgMapsToVariadic = true; + } + + if (!isValidMappingType) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.unpackedDictArgumentNotMapping(), + argList[argIndex].valueExpression || errorNode + ); + } + reportedArgError = true; + } + } + } + + if (paramSpecArgList) { + paramSpecArgList.push(argList[argIndex]); + } + } else { + // Protect against the case where a non-keyword argument appears after + // a keyword argument. This will have already been reported as a parse + // error, but we need to protect against it here. + const paramName = argList[argIndex].name; + if (paramName) { + const paramNameValue = paramName.d.value; + const paramEntry = paramTracker.lookupName(paramNameValue); + + if (paramEntry) { + if (paramEntry.argsReceived > 0) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.paramAlreadyAssigned().format({ name: paramNameValue }), + paramName + ); + } + reportedArgError = true; + } else { + paramEntry.argsReceived++; + + const paramInfoIndex = paramDetails.params.findIndex( + (paramInfo) => + paramInfo.param.name === paramNameValue && + paramInfo.kind !== ParamKind.Positional + ); + assert(paramInfoIndex >= 0); + const paramType = paramDetails.params[paramInfoIndex].type; + + validateArgTypeParams.push({ + paramCategory: ParamCategory.Simple, + paramType, + requiresTypeVarMatching: requiresSpecialization(paramType), + argument: argList[argIndex], + errorNode: argList[argIndex].valueExpression ?? errorNode, + paramName: paramNameValue, + }); + trySetActive(argList[argIndex], paramDetails.params[paramInfoIndex].param); + } + } else if (paramSpecArgList) { + paramSpecArgList.push(argList[argIndex]); + } else if (paramDetails.kwargsIndex !== undefined) { + const paramType = paramDetails.params[paramDetails.kwargsIndex].type; + if (isParamSpec(paramType)) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.paramNameMissing().format({ name: paramName.d.value }), + paramName + ); + } + reportedArgError = true; + } else { + validateArgTypeParams.push({ + paramCategory: ParamCategory.KwargsDict, + paramType, + requiresTypeVarMatching: requiresSpecialization(paramType), + argument: argList[argIndex], + errorNode: argList[argIndex].valueExpression ?? errorNode, + paramName: paramNameValue, + }); + + assert( + paramDetails.params[paramDetails.kwargsIndex], + 'paramDetails.kwargsIndex params entry is undefined' + ); + + // Remember that this parameter has already received a value. + paramTracker.addKeywordParam( + paramNameValue, + paramDetails.params[paramDetails.kwargsIndex] + ); + } + trySetActive(argList[argIndex], paramDetails.params[paramDetails.kwargsIndex].param); + } else { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.paramNameMissing().format({ name: paramName.d.value }), + paramName + ); + } + reportedArgError = true; + } + } else if (argList[argIndex].argCategory === ArgCategory.Simple) { + if (paramSpecArgList) { + paramSpecArgList.push(argList[argIndex]); + } else { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + positionParamLimitIndex === 1 + ? LocMessage.argPositionalExpectedOne() + : LocMessage.argPositionalExpectedCount().format({ + expected: positionParamLimitIndex, + }), + argList[argIndex].valueExpression || errorNode + ); + } + reportedArgError = true; + } + } else if (argList[argIndex].argCategory === ArgCategory.UnpackedList) { + // Handle the case where a *args: P.args (or *args: Any) is passed as an + // argument to a function that accepts a ParamSpec. + if (paramSpec) { + const argTypeResult = getTypeOfArg(argList[argIndex], /* inferenceContext */ undefined); + const argType = argTypeResult.type; + + if (argTypeResult.isIncomplete) { + isTypeIncomplete = true; + } + + if (isParamSpecArgs(paramSpec, argType)) { + validateArgTypeParams.push({ + paramCategory: ParamCategory.ArgsList, + paramType: paramSpec, + requiresTypeVarMatching: false, + argument: argList[argIndex], + argType: isParamSpec(argType) ? undefined : AnyType.create(), + errorNode: argList[argIndex].valueExpression ?? errorNode, + }); + } + } + } + } + + argIndex++; + } + + // If there are keyword-only parameters that haven't been matched but we + // have an unpacked dictionary arg, assume that it applies to them. + if (unpackedDictArgType && (!foundUnpackedListArg || paramDetails.argsIndex !== undefined)) { + // Don't consider any position-only parameters, since they cannot be matched to + // **kwargs arguments. Consider parameters that are either positional or keyword + // if there is no *args argument. + paramDetails.params.forEach((paramInfo, paramIndex) => { + const param = paramInfo.param; + if ( + paramIndex >= paramDetails.firstPositionOrKeywordIndex && + param.category === ParamCategory.Simple && + param.name && + paramTracker.lookupDetails(paramInfo).argsReceived === 0 + ) { + const paramType = paramDetails.params[paramIndex].type; + + if (!unpackedDictKeyNames || unpackedDictKeyNames.includes(param.name)) { + validateArgTypeParams.push({ + paramCategory: ParamCategory.Simple, + paramType, + requiresTypeVarMatching: requiresSpecialization(paramType), + argument: { + argCategory: ArgCategory.Simple, + typeResult: { type: unpackedDictArgType! }, + }, + errorNode: + argList.find((arg) => arg.argCategory === ArgCategory.UnpackedDictionary) + ?.valueExpression ?? errorNode, + paramName: param.name, + isParamNameSynthesized: FunctionParam.isNameSynthesized(param), + }); + + paramTracker.markArgReceived(paramDetails.params[paramIndex]); + } + } + }); + } + + // Determine whether there are any parameters that require arguments + // but have not yet received them. If we received a dictionary argument + // (i.e. an arg starting with a "**"), we will assume that all parameters + // are matched. + if (!unpackedDictArgType && !FunctionType.isDefaultParamCheckDisabled(overload)) { + const unassignedParams = paramTracker.getUnassignedParams(); + + if (unassignedParams.length > 0) { + if (!canSkipDiagnosticForNode(errorNode)) { + const missingParamNames = unassignedParams.map((p) => `"${p}"`).join(', '); + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + unassignedParams.length === 1 + ? LocMessage.argMissingForParam().format({ name: missingParamNames }) + : LocMessage.argMissingForParams().format({ names: missingParamNames }), + errorNode + ); + } + } + reportedArgError = true; + } + + // Add any implicit (default) arguments that are needed for resolving + // generic types. For example, if the function is defined as + // def foo(v1: _T = 'default') + // and _T is a TypeVar, we need to match the TypeVar to the default + // value's type if it's not provided by the caller. + paramDetails.params.forEach((paramInfo) => { + const param = paramInfo.param; + if (param.category === ParamCategory.Simple && param.name) { + const entry = paramTracker.lookupDetails(paramInfo); + + if (entry.argsNeeded === 0 && entry.argsReceived === 0) { + const defaultArgType = paramInfo.defaultType; + + if ( + defaultArgType && + !isEllipsisType(defaultArgType) && + requiresSpecialization(paramInfo.declaredType, { ignorePseudoGeneric: true }) + ) { + validateArgTypeParams.push({ + paramCategory: param.category, + paramType: paramInfo.type, + requiresTypeVarMatching: true, + argument: { + argCategory: ArgCategory.Simple, + typeResult: { type: defaultArgType }, + }, + isDefaultArg: true, + errorNode, + paramName: param.name, + isParamNameSynthesized: FunctionParam.isNameSynthesized(param), + }); + } + } + } + }); + } + } + + // If we're in speculative mode and an arg/param mismatch has already been reported, don't + // bother doing the extra work here. This occurs frequently when attempting to find the + // correct overload. + if (!reportedArgError || !isSpeculativeModeInUse(undefined)) { + // If there are arguments that map to a variadic *args parameter that hasn't + // already been matched, see if the type of that *args parameter is a + // TypeVarTuple. If so, we'll preprocess those arguments and combine them + // into a tuple. + assert( + paramDetails.argsIndex === undefined || paramDetails.argsIndex < paramDetails.params.length, + 'paramDetails.argsIndex params entry is invalid' + ); + if ( + paramDetails.argsIndex !== undefined && + paramDetails.argsIndex >= 0 && + FunctionParam.isTypeDeclared(paramDetails.params[paramDetails.argsIndex].param) && + !isTypeVarTupleFullyMatched + ) { + const paramType = paramDetails.params[paramDetails.argsIndex].type; + const variadicArgs = validateArgTypeParams.filter((argParam) => argParam.mapsToVarArgList); + + if (isUnpacked(paramType) && (!isTypeVarTuple(paramType) || !paramType.priv.isInUnion)) { + const tupleTypeArgs: TupleTypeArg[] = variadicArgs.map((argParam) => { + const argType = getTypeOfArg(argParam.argument, /* inferenceContext */ undefined).type; + + const containsTypeVarTuple = + isUnpackedTypeVarTuple(argType) || + (isClassInstance(argType) && + isTupleClass(argType) && + argType.priv.tupleTypeArgs && + argType.priv.tupleTypeArgs.length === 1 && + isUnpackedTypeVarTuple(argType.priv.tupleTypeArgs[0].type)); + + if ( + containsTypeVarTuple && + argParam.argument.argCategory !== ArgCategory.UnpackedList && + !argParam.mapsToVarArgList + ) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.typeVarTupleMustBeUnpacked(), + argParam.argument.valueExpression ?? errorNode + ); + } + reportedArgError = true; + } + + return { + type: argType, + isUnbounded: argParam.argument.argCategory === ArgCategory.UnpackedList, + }; + }); + + let specializedTuple: Type | undefined; + if (tupleTypeArgs.length === 1 && !tupleTypeArgs[0].isUnbounded) { + const entryType = tupleTypeArgs[0].type; + + if (isUnpacked(entryType)) { + specializedTuple = makePacked(entryType); + } + } + + if (!specializedTuple) { + specializedTuple = makeTupleObject(evaluatorInterface, tupleTypeArgs, /* isUnpacked */ false); + } + + const combinedArg: ValidateArgTypeParams = { + paramCategory: ParamCategory.Simple, + paramType: makePacked(paramType), + requiresTypeVarMatching: true, + argument: { + argCategory: ArgCategory.Simple, + typeResult: { type: specializedTuple }, + }, + errorNode, + paramName: paramDetails.params[paramDetails.argsIndex].param.name, + isParamNameSynthesized: FunctionParam.isNameSynthesized( + paramDetails.params[paramDetails.argsIndex].param + ), + mapsToVarArgList: true, + }; + + validateArgTypeParams = [ + ...validateArgTypeParams.filter((argParam) => !argParam.mapsToVarArgList), + combinedArg, + ]; + } + } + } + + // Special-case the builtin isinstance and issubclass functions. + if (FunctionType.isBuiltIn(overload, ['isinstance', 'issubclass']) && validateArgTypeParams.length === 2) { + validateArgTypeParams[1].isinstanceParam = true; + } + + return { + overload, + overloadIndex, + argumentErrors: reportedArgError, + isTypeIncomplete, + argParams: validateArgTypeParams, + paramSpecTarget, + paramSpecArgList, + activeParam, + unpackedArgOfUnknownLength, + unpackedArgMapsToVariadic, + argumentMatchScore: 0, + }; + } + + // After having matched arguments with parameters, this function evaluates the + // types of each argument expression and validates that the resulting type is + // compatible with the declared type of the corresponding parameter. + function validateArgTypesWithContext( + errorNode: ExpressionNode, + matchResults: MatchArgsToParamsResult, + constraints: ConstraintTracker, + skipUnknownArgCheck = false, + inferenceContext: InferenceContext | undefined + ): CallResult { + const type = matchResults.overload; + + let expectedType: Type | undefined = inferenceContext?.expectedType; + + // Can we safely ignore the inference context, either because it's not provided + // or will have no effect? If so, avoid the extra work. + const returnType = inferenceContext?.returnTypeOverride ?? getEffectiveReturnType(type); + if (!returnType || !requiresSpecialization(returnType)) { + expectedType = undefined; + } + + const tryExpectedType = (expectedSubtype: Type): number => { + const clonedConstraints = constraints.clone(); + const callResult = validateArgTypesWithExpectedType( + errorNode, + matchResults, + clonedConstraints, + /* skipUnknownArgCheck */ true, + expectedSubtype, + returnType + ); + + // Use a heuristic to pick a subtype that is most likely to be correct. + // We'll look for a subtype that produces no argument errors and has + // no Unknowns in the return type. + if (!callResult.argumentErrors && callResult.returnType) { + const returnType = inferenceContext?.returnTypeOverride + ? solveAndApplyConstraints(inferenceContext.returnTypeOverride, clonedConstraints) + : callResult.returnType; + + if ( + assignType( + expectedSubtype, + returnType, + /* diag */ undefined, + /* constraints */ undefined, + AssignTypeFlags.Default + ) + ) { + const anyOrUnknown = containsAnyOrUnknown(callResult.returnType, /* recurse */ true); + // Prefer return types that have no unknown or Any. + if (!anyOrUnknown) { + return 3; + } + + // Prefer Any over Unknown. + return isAny(anyOrUnknown) ? 2 : 1; + } + } + + return 0; + }; + + // Refine the expected type by speculatively evaluating arg types. If the + // expected type is a union, we may need to perform multiple evaluations + // to determine whether one of the subtypes works. + if (expectedType) { + expectedType = useSpeculativeMode(getSpeculativeNodeForCall(errorNode), () => { + let validExpectedSubtype: Type | undefined; + let bestSubtypeScore = -1; + + // If the expected type is a union, we don't know which type is expected. + // We may or may not be able to make use of the expected type. We'll evaluate + // speculatively to see if using one of the expected subtypes works. + if (isUnion(expectedType!)) { + doForEachSubtype( + expectedType!, + (expectedSubtype) => { + if (bestSubtypeScore < 3) { + const score = tryExpectedType(expectedSubtype); + if (score > 0 && score > bestSubtypeScore) { + validExpectedSubtype = expectedSubtype; + bestSubtypeScore = score; + } + } + }, + /* sortSubtypes */ true + ); + } + + if (bestSubtypeScore < 3) { + const score = tryExpectedType(expectedType!); + if (score > 0 && score > bestSubtypeScore) { + validExpectedSubtype = expectedType; + } + } + + return validExpectedSubtype; + }); + } + + // If there is no expected type, or the expected type is Any or Unknown, + // there's nothing left to do here. + if (!expectedType || isAnyOrUnknown(expectedType) || isNever(expectedType)) { + return validateArgTypes(errorNode, matchResults, constraints, skipUnknownArgCheck); + } + + return validateArgTypesWithExpectedType( + errorNode, + matchResults, + constraints, + skipUnknownArgCheck, + expectedType, + returnType + ); + } + + function validateArgTypesWithExpectedType( + errorNode: ExpressionNode, + matchResults: MatchArgsToParamsResult, + constraints: ConstraintTracker, + skipUnknownArgCheck = false, + expectedType: Type, + returnType: Type + ): CallResult { + const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(errorNode); + let assignFlags = AssignTypeFlags.PopulateExpectedType; + if (containsLiteralType(expectedType, /* includeTypeArgs */ true)) { + assignFlags |= AssignTypeFlags.RetainLiteralsForTypeVar; + } + + // Prepopulate the constraints based on the specialized expected type. + // This will allow us to more closely match the expected type if possible. + if (isClassInstance(returnType) && isClassInstance(expectedType) && !isTypeSame(returnType, expectedType)) { + const tempConstraints = new ConstraintTracker(); + if ( + addConstraintsForExpectedType( + evaluatorInterface, + returnType, + expectedType, + tempConstraints, + liveTypeVarScopes, + errorNode.start + ) + ) { + const genericReturnType = selfSpecializeClass(returnType, { + overrideTypeArgs: true, + }); + + expectedType = solveAndApplyConstraints(genericReturnType, tempConstraints, { + replaceUnsolved: { + scopeIds: getTypeVarScopeIds(returnType), + useUnknown: true, + tupleClassType: getTupleClassType(), + }, + }); + + assignFlags |= AssignTypeFlags.SkipPopulateUnknownExpectedType; + } + } + + expectedType = transformExpectedType(expectedType, liveTypeVarScopes, errorNode.start); + + assignType(returnType, expectedType, /* diag */ undefined, constraints, assignFlags); + + return validateArgTypes(errorNode, matchResults, constraints, skipUnknownArgCheck); + } + + function validateArgTypes( + errorNode: ExpressionNode, + matchResults: MatchArgsToParamsResult, + constraints: ConstraintTracker, + skipUnknownArgCheck: boolean | undefined + ): CallResult { + const type = matchResults.overload; + let isTypeIncomplete = matchResults.isTypeIncomplete; + let argumentErrors = false; + let argumentMatchScore = 0; + let specializedInitSelfType: Type | undefined; + let anyOrUnknownArg: UnknownType | AnyType | undefined; + const speculativeNode = getSpeculativeNodeForCall(errorNode); + const typeCondition = getTypeCondition(type); + const paramSpec = FunctionType.getParamSpecFromArgsKwargs(type); + + // Check for an attempt to invoke an unimplemented abstract method. + if (type.priv.boundToType && !type.priv.boundToType.priv.includeSubclasses && type.shared.methodClass) { + const abstractSymbolInfo = getAbstractSymbolInfo(type.shared.methodClass, type.shared.name); + + if (abstractSymbolInfo && !abstractSymbolInfo.hasImplementation) { + addDiagnostic( + DiagnosticRule.reportAbstractUsage, + LocMessage.abstractMethodInvocation().format({ + method: type.shared.name, + }), + errorNode.nodeType === ParseNodeType.Call ? errorNode.d.leftExpr : errorNode + ); + } + } + + // The type annotation for the "self" parameter in an __init__ method to + // can influence the type being constructed. + if ( + type.shared.name === '__init__' && + type.priv.strippedFirstParamType && + type.priv.boundToType && + isClassInstance(type.priv.strippedFirstParamType) && + isClassInstance(type.priv.boundToType) && + ClassType.isSameGenericClass(type.priv.strippedFirstParamType, type.priv.boundToType) && + type.priv.strippedFirstParamType.priv.typeArgs + ) { + const typeParams = type.priv.strippedFirstParamType.shared.typeParams; + specializedInitSelfType = type.priv.strippedFirstParamType; + type.priv.strippedFirstParamType.priv.typeArgs.forEach((typeArg, index) => { + if (index < typeParams.length) { + const typeParam = typeParams[index]; + if (!isTypeSame(typeParam, typeArg, { ignorePseudoGeneric: true })) { + constraints.setBounds(typeParams[index], typeArg); + } + } + }); + } + + // Special-case a few built-in calls that are often used for + // casting or checking for unknown types. + if ( + FunctionType.isBuiltIn(type, [ + 'typing.cast', + 'typing_extensions.cast', + 'builtins.isinstance', + 'builtins.issubclass', + ]) + ) { + skipUnknownArgCheck = true; + } + + // Run through all args and validate them against their matched parameter. + // We'll do two phases. The first one establishes constraints for type + // variables. The second perform type validation using the solved + // types. We can skip the first pass if there are no type vars to solve. + const typeVarCount = matchResults.argParams.filter((arg) => arg.requiresTypeVarMatching).length; + if (typeVarCount > 0) { + // Do up to two passes. + let passCount = Math.min(typeVarCount, 2); + + for (let i = 0; i < passCount; i++) { + useSpeculativeMode(speculativeNode, () => { + matchResults.argParams.forEach((argParam) => { + if (!argParam.requiresTypeVarMatching) { + return; + } + + const argResult = validateArgType( + argParam, + constraints, + { type, isIncomplete: matchResults.isTypeIncomplete }, + { + skipUnknownArgCheck, + isArgFirstPass: passCount > 1 && i === 0, + conditionFilter: typeCondition, + skipReportError: true, + } + ); + + if (argResult.isTypeIncomplete) { + isTypeIncomplete = true; + } + + // If we skipped a bare type var during the first pass, add + // another pass to ensure that we handle all of the type variables. + if (i === 0 && passCount < 2 && argResult.skippedBareTypeVarExpectedType) { + passCount++; + } + }); + }); + } + } + + let sawParamSpecArgs = false; + let sawParamSpecKwargs = false; + + let condition: TypeCondition[] = []; + const argResults: ArgResult[] = []; + + matchResults.argParams.forEach((argParam, argParamIndex) => { + const argResult = validateArgType( + argParam, + constraints, + { type, isIncomplete: matchResults.isTypeIncomplete }, + { + skipUnknownArgCheck, + conditionFilter: typeCondition, + } + ); + + argResults.push(argResult); + + if (!argResult.isCompatible) { + argumentErrors = true; + + // Add the inverse index so earlier parameters represent larger errors. + // This will help the heuristics in the overload error paths to pick the + // most likely intended overload if none of them match. + argumentMatchScore += 1 + (matchResults.argParams.length - argParamIndex); + } + + if (argResult.isTypeIncomplete) { + isTypeIncomplete = true; + } + + if (argResult.condition) { + condition = TypeCondition.combine(condition, argResult.condition) ?? []; + } + + if (isAnyOrUnknown(argResult.argType)) { + anyOrUnknownArg = anyOrUnknownArg + ? preserveUnknown(argResult.argType, anyOrUnknownArg) + : argResult.argType; + } + + if (paramSpec) { + if (argParam.argument.argCategory === ArgCategory.UnpackedList) { + if (isParamSpecArgs(paramSpec, argResult.argType)) { + if (sawParamSpecArgs) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.paramSpecArgsKwargsDuplicate().format({ type: printType(paramSpec) }), + argParam.errorNode + ); + } + + sawParamSpecArgs = true; + } + } + + if (argParam.argument.argCategory === ArgCategory.UnpackedDictionary) { + if (isParamSpecKwargs(paramSpec, argResult.argType)) { + if (sawParamSpecKwargs) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.paramSpecArgsKwargsDuplicate().format({ type: printType(paramSpec) }), + argParam.errorNode + ); + } + + sawParamSpecKwargs = true; + } + } + } + }); + + let paramSpecConstraints: (ConstraintTracker | undefined)[] = []; + + // Handle the assignment of additional arguments that map to a param spec. + if (matchResults.paramSpecArgList && matchResults.paramSpecTarget) { + const paramSpecArgResult = validateArgTypesForParamSpec( + errorNode, + matchResults.paramSpecArgList, + matchResults.paramSpecTarget, + constraints + ); + + if (paramSpecArgResult.argumentErrors) { + argumentErrors = true; + argumentMatchScore += 1; + } + + paramSpecConstraints = paramSpecArgResult.constraintTrackers; + } else if (paramSpec) { + if (!sawParamSpecArgs || !sawParamSpecKwargs) { + if (!isTypeIncomplete) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.paramSpecArgsMissing().format({ type: printType(paramSpec) }), + errorNode + ); + } + argumentErrors = true; + argumentMatchScore += 1; + } + } + + // Calculate the return type. + const returnTypeResult = getEffectiveReturnTypeResult(type, { + callSiteInfo: { args: matchResults.argParams, errorNode }, + }); + let returnType = returnTypeResult.type; + if (returnTypeResult.isIncomplete) { + isTypeIncomplete = true; + } + + if (condition.length > 0) { + returnType = TypeBase.cloneForCondition(returnType, condition); + } + + let eliminateUnsolvedInUnions = true; + + // If the function is returning a callable, don't eliminate unsolved + // type vars within a union. There are legit uses for unsolved type vars + // within a callable. + if (isFunctionOrOverloaded(returnType)) { + eliminateUnsolvedInUnions = false; + } + + let specializedReturnType = solveAndApplyConstraints(returnType, constraints, { + replaceUnsolved: { + scopeIds: getTypeVarScopeIds(type), + unsolvedExemptTypeVars: getUnknownExemptTypeVarsForReturnType(type, returnType), + tupleClassType: getTupleClassType(), + eliminateUnsolvedInUnions, + }, + }); + specializedReturnType = addConditionToType(specializedReturnType, typeCondition, { skipBoundTypeVars: true }); + + // If the function includes a ParamSpec and the captured signature(s) includes + // generic types, we may need to apply those solved TypeVars. + if (paramSpecConstraints.length > 0) { + paramSpecConstraints.forEach((paramSpecConstraints) => { + if (paramSpecConstraints) { + specializedReturnType = solveAndApplyConstraints(specializedReturnType, paramSpecConstraints); + + // It's possible that one or more of the TypeVars or ParamSpecs + // in the constraints refer to TypeVars that were solved in + // the paramSpecConstraints. Apply these solved TypeVars accordingly. + applySourceSolutionToConstraints( + constraints, + solveConstraints(evaluatorInterface, paramSpecConstraints) + ); + } + }); + } + + // If the final return type is an unpacked tuple, turn it into a normal (unpacked) tuple. + if (isUnpackedClass(specializedReturnType)) { + specializedReturnType = ClassType.cloneForPacked(specializedReturnType); + } + + const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(errorNode); + specializedReturnType = adjustCallableReturnType(errorNode, specializedReturnType, liveTypeVarScopes); + + if (specializedInitSelfType) { + specializedInitSelfType = solveAndApplyConstraints(specializedInitSelfType, constraints); + } + + matchResults.argumentMatchScore = argumentMatchScore; + + return { + argumentErrors, + argResults, + anyOrUnknownArg, + returnType: specializedReturnType, + isTypeIncomplete, + activeParam: matchResults.activeParam, + specializedInitSelfType, + overloadsUsedForCall: argumentErrors ? [] : [type], + }; + } + + // In general, all in-scope type variables left in a return type should be + // replaced with Unknown. However, if the return type is a callable that uses + // type vars that are found nowhere within the function's input parameters, + // we'll treat these as though they're scoped to the callable and leave them + // unsolved. + function getUnknownExemptTypeVarsForReturnType(functionType: FunctionType, returnType: Type): TypeVarType[] { + if (isFunction(returnType) && !returnType.shared.name) { + const returnTypeScopeId = returnType.shared.typeVarScopeId; + + // If one or more type vars found within the return type are scoped to + // the functionType but don't appear anywhere else within the functionType's + // input parameters, rescope them to the return type callable so they are + // not replaced with Unknown. + if (returnTypeScopeId && functionType.shared.typeVarScopeId) { + let typeVarsInReturnType = getTypeVarArgsRecursive(returnType); + + // Remove any type variables that appear in the function's input parameters. + functionType.shared.parameters.forEach((param, index) => { + if (FunctionParam.isTypeDeclared(param)) { + const typeVarsInInputParam = getTypeVarArgsRecursive( + FunctionType.getParamType(functionType, index) + ); + typeVarsInReturnType = typeVarsInReturnType.filter( + (returnTypeVar) => + !typeVarsInInputParam.some((inputTypeVar) => isTypeSame(returnTypeVar, inputTypeVar)) + ); + } + }); + + return typeVarsInReturnType; + } + } + + return []; + } + + // If the return type includes a generic Callable type, set the type var + // scope to the scope of the function it was originally associated with + // to allow these type vars to be solved. This won't work with overloads + // or unions of callables. It's intended for a specific use case. We may + // need to make this more sophisticated in the future. + function adjustCallableReturnType( + callNode: ExpressionNode, + returnType: Type, + liveTypeVarScopes: TypeVarScopeId[] + ): Type { + if (!isFunction(returnType)) { + return returnType; + } + + // What type variables are referenced in the callable return type? Do not include any live type variables. + const typeParams = getTypeVarArgsRecursive(returnType).filter( + (t) => !liveTypeVarScopes.some((scopeId) => t.priv.scopeId === scopeId) + ); + + // If there are no unsolved type variables, we're done. If there are + // unsolved type variables, rescope them to the callable. + if (typeParams.length === 0) { + return returnType; + } + + inferReturnTypeIfNecessary(returnType); + + // Create a new scope ID based on the caller's position. This + // will guarantee uniqueness. If another caller uses the same + // call and arguments, the type vars will not conflict. + const newScopeId = ParseTreeUtils.getScopeIdForNode(callNode); + const solution = new ConstraintSolution(); + + const newTypeParams = typeParams.map((typeVar) => { + const newTypeParam = TypeVarType.cloneForScopeId( + typeVar, + newScopeId, + typeVar.priv.scopeName, + TypeVarScopeType.Function + ); + solution.setType(typeVar, newTypeParam); + return newTypeParam; + }); + + return applySolvedTypeVars( + FunctionType.cloneWithNewTypeVarScopeId( + returnType, + newScopeId, + /* constructorTypeVarScopeId */ undefined, + newTypeParams + ), + solution + ); + } + + // Tries to assign the call arguments to the function parameter + // list and reports any mismatches in types or counts. Returns the + // specialized return type of the call. + function validateArgs( + errorNode: ExpressionNode, + argList: Arg[], + typeResult: TypeResult, + constraints: ConstraintTracker | undefined, + skipUnknownArgCheck = false, + inferenceContext: InferenceContext | undefined + ): CallResult { + const matchResults = matchArgsToParams(errorNode, argList, typeResult, 0); + + if (matchResults.argumentErrors) { + // Evaluate types of all args. This will ensure that referenced symbols are + // not reported as unaccessed. Also pass the expected parameter type as + // inference context to enable proper completions even when there are errors. + matchResults.argParams.forEach((argParam) => { + if (argParam.argument.valueExpression && !isSpeculativeModeInUse(argParam.argument.valueExpression)) { + getTypeOfExpression( + argParam.argument.valueExpression, + /* flags */ undefined, + makeInferenceContext(argParam.paramType) + ); + } + }); + + // Also evaluate any arguments that weren't matched to parameters + argList.forEach((arg) => { + if (arg.valueExpression && !isSpeculativeModeInUse(arg.valueExpression)) { + // Check if this argument was already evaluated above + const wasEvaluated = matchResults.argParams.some((argParam) => argParam.argument === arg); + if (!wasEvaluated) { + getTypeOfExpression(arg.valueExpression); + } + } + }); + // Use a return type of Unknown but attach a "possible type" to it + // so the completion provider can suggest better completions. + const possibleType = FunctionType.getEffectiveReturnType(typeResult.type); + return { + returnType: + possibleType && !isAnyOrUnknown(possibleType) + ? UnknownType.createPossibleType(possibleType, /* isIncomplete */ false) + : undefined, + argumentErrors: true, + activeParam: matchResults.activeParam, + overloadsUsedForCall: [], + }; + } + + return validateArgTypesWithContext( + errorNode, + matchResults, + constraints ?? new ConstraintTracker(), + skipUnknownArgCheck, + makeInferenceContext( + inferenceContext?.expectedType, + inferenceContext?.isTypeIncomplete, + inferenceContext?.returnTypeOverride + ) + ); + } + + // Determines whether the specified argument list satisfies the function + // signature bound to the specified ParamSpec. Return value indicates success. + function validateArgTypesForParamSpec( + errorNode: ExpressionNode, + argList: Arg[], + paramSpec: ParamSpecType, + destConstraints: ConstraintTracker + ): ParamSpecArgResult { + const sets = destConstraints.getConstraintSets(); + + // Handle the common case where there is only one signature context. + if (sets.length === 1) { + return validateArgTypesForParamSpecSignature(errorNode, argList, paramSpec, sets[0]); + } + + const filteredSets: ConstraintSet[] = []; + const constraintTrackers: (ConstraintTracker | undefined)[] = []; + const speculativeNode = getSpeculativeNodeForCall(errorNode); + + sets.forEach((context) => { + // Use speculative mode to avoid emitting errors or caching types. + useSpeculativeMode(speculativeNode, () => { + const paramSpecArgResult = validateArgTypesForParamSpecSignature( + errorNode, + argList, + paramSpec, + context + ); + + if (!paramSpecArgResult.argumentErrors) { + filteredSets.push(context); + } + + appendArray(constraintTrackers, paramSpecArgResult.constraintTrackers); + }); + }); + + // Copy back any compatible signature contexts if any were compatible. + if (filteredSets.length > 0) { + destConstraints.addConstraintSets(filteredSets); + } + + // Evaluate non-speculatively to produce a final result and cache types. + const paramSpecArgResult = validateArgTypesForParamSpecSignature( + errorNode, + argList, + paramSpec, + filteredSets.length > 0 ? filteredSets[0] : sets[0] + ); + + return { argumentErrors: paramSpecArgResult.argumentErrors, constraintTrackers: constraintTrackers }; + } + + function validateArgTypesForParamSpecSignature( + errorNode: ExpressionNode, + argList: Arg[], + paramSpec: ParamSpecType, + constraintSet: ConstraintSet + ): ParamSpecArgResult { + const solutionSet = solveConstraintSet(evaluatorInterface, constraintSet); + let paramSpecType = solutionSet.getType(paramSpec); + paramSpecType = convertTypeToParamSpecValue(paramSpecType ?? paramSpec); + + const matchResults = matchArgsToParams(errorNode, argList, { type: paramSpecType }, 0); + const functionType = matchResults.overload; + const constraints = new ConstraintTracker(); + + if (matchResults.argumentErrors) { + // Evaluate types of all args. This will ensure that referenced symbols are + // not reported as unaccessed. + argList.forEach((arg) => { + if (arg.valueExpression && !isSpeculativeModeInUse(arg.valueExpression)) { + getTypeOfExpression(arg.valueExpression); + } + }); + + return { argumentErrors: true, constraintTrackers: [constraints] }; + } + + const functionParamSpec = FunctionType.getParamSpecFromArgsKwargs(functionType); + const functionWithoutParamSpec = FunctionType.cloneRemoveParamSpecArgsKwargs(functionType); + + // Handle the recursive case where we're passing (*args: P.args, **kwargs: P.args) + // a remaining function of type (*P). + if ( + functionParamSpec && + functionWithoutParamSpec.shared.parameters.length === 0 && + isTypeSame(functionParamSpec, paramSpec) + ) { + // If there are any arguments other than *args: P.args or **kwargs: P.kwargs, + // report an error. + let argsCount = 0; + let kwargsCount = 0; + let argumentErrors = false; + let argErrorNode: ExpressionNode | undefined; + + for (const arg of argList) { + const argType = getTypeOfArg(arg, /* inferenceContext */ undefined)?.type; + + if (arg.argCategory === ArgCategory.UnpackedList) { + if (isParamSpecArgs(paramSpec, argType)) { + argsCount++; + } + } else if (arg.argCategory === ArgCategory.UnpackedDictionary) { + if (isParamSpecKwargs(paramSpec, argType)) { + kwargsCount++; + } + } else { + argErrorNode = argErrorNode ?? arg.valueExpression; + argumentErrors = true; + } + } + + if (argsCount !== 1 || kwargsCount !== 1) { + argumentErrors = true; + } + + if (argumentErrors) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.paramSpecArgsMissing().format({ + type: printType(functionParamSpec), + }), + argErrorNode ?? errorNode + ); + } + + return { argumentErrors, constraintTrackers: [constraints] }; + } + + const result = validateArgTypes(errorNode, matchResults, constraints, /* skipUnknownArgCheck */ undefined); + return { argumentErrors: !!result.argumentErrors, constraintTrackers: [constraints] }; + } + + function validateArgType( + argParam: ValidateArgTypeParams, + constraints: ConstraintTracker, + typeResult: TypeResult | undefined, + options: ValidateArgTypeOptions + ): ArgResult { + let argType: Type | undefined; + let expectedTypeDiag: DiagnosticAddendum | undefined; + let isTypeIncomplete = !!typeResult?.isIncomplete; + let isCompatible = true; + const functionName = typeResult?.type.shared.name; + let skippedBareTypeVarExpectedType = false; + + if (argParam.argument.valueExpression) { + let expectedType: Type | undefined; + + // Is the expected type a "bare" in-scope TypeVar or a union of bare in-scope TypeVars? + let isExpectedTypeBareTypeVar = true; + doForEachSubtype(argParam.paramType, (subtype) => { + if (!isTypeVar(subtype) || subtype.priv.scopeId !== typeResult?.type.shared.typeVarScopeId) { + isExpectedTypeBareTypeVar = false; + } + }); + + if (!options.isArgFirstPass || !isExpectedTypeBareTypeVar) { + expectedType = argParam.paramType; + + // If the parameter type is a function with a ParamSpec, don't apply + // the solved TypeVars if the constraint tracker has more than one signature. + // This will expand the ParamSpec into an overload, which will cause problems. + const skipApplySolvedTypeVars = + isFunction(argParam.paramType) && + FunctionType.getParamSpecFromArgsKwargs(argParam.paramType) && + constraints.getConstraintSets().length > 1; + + if (!skipApplySolvedTypeVars) { + expectedType = solveAndApplyConstraints(expectedType, constraints, /* applyOptions */ undefined, { + useLowerBoundOnly: !!options.isArgFirstPass, + }); + } + } else { + skippedBareTypeVarExpectedType = true; + } + + // If the expected type is unknown, don't use an expected type. Instead, + // use default rules for evaluating the expression type. + if (expectedType && isUnknown(expectedType)) { + expectedType = undefined; + } + + // Was the argument's type precomputed by the caller? + if (argParam.argType) { + argType = argParam.argType; + } else { + const flags = argParam.isinstanceParam + ? EvalFlags.IsInstanceArgDefaults + : EvalFlags.NoFinal | EvalFlags.NoSpecialize; + const exprTypeResult = getTypeOfExpression( + argParam.argument.valueExpression, + flags, + makeInferenceContext(expectedType, !!typeResult?.isIncomplete) + ); + + argType = exprTypeResult.type; + + // If the argument is unpacked and we are supposed to enforce + // that it's an iterator, do so now. + if (argParam.argument.argCategory === ArgCategory.UnpackedList && argParam.argument.enforceIterable) { + const iteratorType = getTypeOfIterator( + exprTypeResult, + /* isAsync */ false, + argParam.argument.valueExpression + ); + // Try to prevent cascading errors if it was not iterable. + argType = iteratorType?.type ?? UnknownType.create(); + } + + if (exprTypeResult.isIncomplete) { + isTypeIncomplete = true; + } + + if (expectedType && requiresSpecialization(expectedType)) { + // Viper 扩展:str/bytes 实参传给可迭代协议参数(Iterable[_T]/Iterator[_T] 等) + // 时,泛型参数 _T 应从 str.__iter__ 的声明返回类型(Iterator[str])推断。 + // 但 Viper 中 str 是 i8* 指针,迭代元素应为 t.CChar 而非 str。 + // 因此将 argType 替换为 Iterator[CChar] 实例,使协议匹配把 _T 绑定为 CChar, + // 修复 `for i, ch in enumerate(src)` 中 ch 被推断为 str 的问题 + // (同样覆盖 zip/map/filter/reversed/sorted/list() 等 Iterable 参数场景)。 + let argTypeForConstraint = argType; + if ( + IsViperDialect(AnalyzerNodeInfo.getFileInfo(argParam.errorNode).executionEnvironment.dialect) && + isClassInstance(argType) && + ClassType.isBuiltIn(argType, ['str', 'bytes']) + ) { + const ccharType = getViperCCharType(argParam.errorNode); + const iteratorClass = getTypingType(argParam.errorNode, 'Iterator'); + if (ccharType && iteratorClass && isInstantiableClass(iteratorClass)) { + argTypeForConstraint = ClassType.cloneAsInstance( + ClassType.specialize(iteratorClass, [ccharType]) + ); + } + } + + // Assign the argument type back to the expected type to assign + // values to any unification variables. + const clonedConstraints = constraints.clone(); + if ( + assignType( + expectedType, + argTypeForConstraint, + /* diag */ undefined, + clonedConstraints, + options?.isArgFirstPass ? AssignTypeFlags.ArgAssignmentFirstPass : AssignTypeFlags.Default + ) + ) { + constraints.copyFromClone(clonedConstraints); + } else { + isCompatible = false; + } + } + + expectedTypeDiag = exprTypeResult.expectedTypeDiagAddendum; + } + + if (argParam.argument && argParam.argument.name && !isSpeculativeModeInUse(argParam.errorNode)) { + writeTypeCache( + argParam.argument.name, + { type: expectedType ?? argType, isIncomplete: isTypeIncomplete }, + EvalFlags.None + ); + } + } else { + // Was the argument's type precomputed by the caller? + if (argParam.argType) { + argType = argParam.argType; + } else { + const argTypeResult = getTypeOfArg( + argParam.argument, + makeInferenceContext(argParam.paramType, isTypeIncomplete) + ); + argType = argTypeResult.type; + if (argTypeResult.isIncomplete) { + isTypeIncomplete = true; + } + } + + // If the argument came from a parameter's default argument value, + // we may need to specialize the type. + if (argParam.isDefaultArg) { + argType = solveAndApplyConstraints(argType, constraints); + } + } + + // If we're assigning to a var arg dictionary with a TypeVar type, + // strip literals before performing the assignment. This is used in + // places like a dict constructor. + if (argParam.paramCategory === ParamCategory.KwargsDict && isTypeVar(argParam.paramType)) { + argType = stripLiteralValue(argType); + } + + // If there's a constraint filter, apply it to top-level type variables + // if appropriate. This doesn't properly handle non-top-level constrained + // type variables. + if (options.conditionFilter) { + argType = mapSubtypesExpandTypeVars( + argType, + { conditionFilter: options.conditionFilter }, + (expandedSubtype) => { + return expandedSubtype; + } + ); + } + + const condition = argType.props?.condition; + + let diag = options?.skipReportError ? undefined : new DiagnosticAddendum(); + + if (isParamSpec(argParam.paramType)) { + // Handle the case where we're assigning a *args or **kwargs argument + // to a *P.args or **P.kwargs parameter. + if (argParam.paramType.priv.paramSpecAccess !== undefined) { + return { isCompatible, argType, isTypeIncomplete, condition }; + } + + // Handle the case where we're assigning a *P.args or **P.kwargs argument + // to a *P.args or **P.kwargs parameter. + if (isParamSpec(argType) && argType.priv.paramSpecAccess !== undefined) { + return { isCompatible, argType, isTypeIncomplete, condition }; + } + } + + let assignTypeFlags = AssignTypeFlags.Default; + + if (argParam.isinstanceParam) { + assignTypeFlags |= AssignTypeFlags.AllowIsinstanceSpecialForms; + } + + if (options?.isArgFirstPass) { + assignTypeFlags |= AssignTypeFlags.ArgAssignmentFirstPass; + } + + if (!assignType(argParam.paramType, argType, diag?.createAddendum(), constraints, assignTypeFlags)) { + if (!options?.skipReportError) { + // Mismatching parameter types are common in untyped code; don't bother spending time + // printing types if the diagnostic is disabled. + const fileInfo = AnalyzerNodeInfo.getFileInfo(argParam.errorNode); + if ( + fileInfo.diagnosticRuleSet.reportArgumentType !== 'none' && + !canSkipDiagnosticForNode(argParam.errorNode) && + !isTypeIncomplete + ) { + const argTypeText = printType(argType); + const paramTypeText = printType(argParam.paramType); + + let message: string; + if (argParam.paramName && !argParam.isParamNameSynthesized) { + if (functionName) { + message = LocMessage.argAssignmentParamFunction().format({ + argType: argTypeText, + paramType: paramTypeText, + functionName, + paramName: argParam.paramName, + }); + } else { + message = LocMessage.argAssignmentParam().format({ + argType: argTypeText, + paramType: paramTypeText, + paramName: argParam.paramName, + }); + } + } else { + if (functionName) { + message = LocMessage.argAssignmentFunction().format({ + argType: argTypeText, + paramType: paramTypeText, + functionName, + }); + } else { + message = LocMessage.argAssignment().format({ + argType: argTypeText, + paramType: paramTypeText, + }); + } + } + + // If we have an expected type diagnostic addendum, use that + // instead of the local diagnostic addendum because it will + // be more informative. + if (expectedTypeDiag) { + diag = expectedTypeDiag; + } + + addDiagnostic( + DiagnosticRule.reportArgumentType, + message + diag?.getString(), + argParam.errorNode, + diag?.getEffectiveTextRange() ?? argParam.errorNode + ); + } + } + + return { isCompatible: false, argType, isTypeIncomplete, skippedBareTypeVarExpectedType, condition }; + } + + if (!options.skipUnknownArgCheck) { + const simplifiedType = makeTopLevelTypeVarsConcrete(removeUnbound(argType)); + const fileInfo = AnalyzerNodeInfo.getFileInfo(argParam.errorNode); + + function getDiagAddendum() { + const diagAddendum = new DiagnosticAddendum(); + if (argParam.paramName) { + diagAddendum.addMessage( + (functionName + ? LocAddendum.argParamFunction().format({ + paramName: argParam.paramName, + functionName, + }) + : LocAddendum.argParam().format({ paramName: argParam.paramName })) + + diagAddendum.getString() + ); + } + return diagAddendum; + } + + // Do not check for unknown types if the expected type is "Any". + // Don't print types if reportUnknownArgumentType is disabled for performance. + if ( + fileInfo.diagnosticRuleSet.reportUnknownArgumentType !== 'none' && + !isAny(argParam.paramType) && + !isTypeIncomplete + ) { + if (isUnknown(simplifiedType)) { + const diagAddendum = getDiagAddendum(); + addDiagnostic( + DiagnosticRule.reportUnknownArgumentType, + LocMessage.argTypeUnknown() + diagAddendum.getString(), + argParam.errorNode + ); + } else if (isPartlyUnknown(simplifiedType)) { + // If the parameter type is also partially unknown, don't report + // the error because it's likely that the partially-unknown type + // arose due to bidirectional type matching. + if (!isPartlyUnknown(argParam.paramType)) { + const diagAddendum = getDiagAddendum(); + diagAddendum.addMessage( + LocAddendum.argumentType().format({ + type: printType(simplifiedType, { expandTypeAlias: true }), + }) + ); + addDiagnostic( + DiagnosticRule.reportUnknownArgumentType, + LocMessage.argTypePartiallyUnknown() + diagAddendum.getString(), + argParam.errorNode + ); + } + } + } + } + + return { isCompatible, argType, isTypeIncomplete, skippedBareTypeVarExpectedType, condition }; + } + + function createTypeVarType(errorNode: ExpressionNode, classType: ClassType, argList: Arg[]): Type | undefined { + let typeVarName = ''; + let firstConstraintArg: Arg | undefined; + let defaultValueNode: ExpressionNode | undefined; + + if (argList.length === 0) { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.typeVarFirstArg(), errorNode); + return undefined; + } + + const firstArg = argList[0]; + if (firstArg.valueExpression && firstArg.valueExpression.nodeType === ParseNodeType.StringList) { + typeVarName = firstArg.valueExpression.d.strings.map((s) => s.d.value).join(''); + } else { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarFirstArg(), + firstArg.valueExpression || errorNode + ); + } + + const typeVar = TypeBase.cloneAsSpecialForm( + TypeVarType.createInstantiable(typeVarName), + ClassType.cloneAsInstance(classType) + ); + + // Parse the remaining parameters. + const paramNameMap = new Map(); + for (let i = 1; i < argList.length; i++) { + const paramNameNode = argList[i].name; + const paramName = paramNameNode ? paramNameNode.d.value : undefined; + + if (paramName) { + if (paramNameMap.get(paramName)) { + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.duplicateParam().format({ name: paramName }), + argList[i].valueExpression || errorNode + ); + } + + if (paramName === 'bound') { + if (TypeVarType.hasConstraints(typeVar)) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarBoundAndConstrained(), + argList[i].valueExpression || errorNode + ); + } else { + const argType = + argList[i].typeResult?.type ?? + getTypeOfExpressionExpectingType(argList[i].valueExpression!, { + noNonTypeSpecialForms: true, + typeExpression: true, + parsesStringLiteral: true, + }).type; + if ( + requiresSpecialization(argType, { ignorePseudoGeneric: true, ignoreImplicitTypeArgs: true }) + ) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarBoundGeneric(), + argList[i].valueExpression || errorNode + ); + } + typeVar.shared.boundType = convertToInstance(argType); + } + } else if (paramName === 'covariant') { + if (argList[i].valueExpression && getBooleanValue(argList[i].valueExpression!)) { + if ( + typeVar.shared.declaredVariance === Variance.Contravariant || + typeVar.shared.declaredVariance === Variance.Auto + ) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarVariance(), + argList[i].valueExpression! + ); + } else { + typeVar.shared.declaredVariance = Variance.Covariant; + } + } + } else if (paramName === 'contravariant') { + if (argList[i].valueExpression && getBooleanValue(argList[i].valueExpression!)) { + if ( + typeVar.shared.declaredVariance === Variance.Covariant || + typeVar.shared.declaredVariance === Variance.Auto + ) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarVariance(), + argList[i].valueExpression! + ); + } else { + typeVar.shared.declaredVariance = Variance.Contravariant; + } + } + } else if (paramName === 'infer_variance') { + if (argList[i].valueExpression && getBooleanValue(argList[i].valueExpression!)) { + if ( + typeVar.shared.declaredVariance === Variance.Covariant || + typeVar.shared.declaredVariance === Variance.Contravariant + ) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarVariance(), + argList[i].valueExpression! + ); + } else { + typeVar.shared.declaredVariance = Variance.Auto; + } + } + } else if (paramName === 'default') { + defaultValueNode = argList[i].valueExpression; + const argType = + argList[i].typeResult?.type ?? + getTypeOfExpressionExpectingType(defaultValueNode!, { + allowTypeVarsWithoutScopeId: true, + typeExpression: true, + }).type; + typeVar.shared.defaultType = convertToInstance(argType); + typeVar.shared.isDefaultExplicit = true; + + const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode); + if ( + !fileInfo.isStubFile && + PythonVersion.isLessThan(fileInfo.executionEnvironment.pythonVersion, pythonVersion3_13) && + classType.shared.moduleName !== 'typing_extensions' + ) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarDefaultIllegal(), + defaultValueNode! + ); + } + } else { + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.typeVarUnknownParam().format({ name: paramName }), + argList[i].node?.d.name || argList[i].valueExpression || errorNode + ); + } + + paramNameMap.set(paramName, paramName); + } else { + if (TypeVarType.hasBound(typeVar)) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarBoundAndConstrained(), + argList[i].valueExpression || errorNode + ); + } else { + const argType = + argList[i].typeResult?.type ?? + getTypeOfExpressionExpectingType(argList[i].valueExpression!, { + typeExpression: true, + }).type; + + if (requiresSpecialization(argType, { ignorePseudoGeneric: true })) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarConstraintGeneric(), + argList[i].valueExpression || errorNode + ); + } + TypeVarType.addConstraint(typeVar, convertToInstance(argType)); + if (firstConstraintArg === undefined) { + firstConstraintArg = argList[i]; + } + } + } + } + + if (typeVar.shared.constraints.length === 1 && firstConstraintArg) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarSingleConstraint(), + firstConstraintArg.valueExpression || errorNode + ); + } + + // If a default is provided, make sure it is compatible with the bound + // or constraint. + if (typeVar.shared.isDefaultExplicit && defaultValueNode) { + verifyTypeVarDefaultIsCompatible(typeVar, defaultValueNode); + } + + return typeVar; + } + + function verifyTypeVarDefaultIsCompatible(typeVar: TypeVarType, defaultValueNode: ExpressionNode) { + assert(typeVar.shared.isDefaultExplicit); + + const constraints = new ConstraintTracker(); + const concreteDefaultType = makeTopLevelTypeVarsConcrete( + solveAndApplyConstraints(typeVar.shared.defaultType, constraints, { + replaceUnsolved: { + scopeIds: getTypeVarScopeIds(typeVar), + tupleClassType: getTupleClassType(), + }, + }) + ); + + if (typeVar.shared.boundType) { + if (!assignType(typeVar.shared.boundType, concreteDefaultType)) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarDefaultBoundMismatch(), + defaultValueNode + ); + } + } else if (TypeVarType.hasConstraints(typeVar)) { + let isConstraintCompatible = true; + + // If the default type is a constrained TypeVar, make sure all of its constraints + // are also constraints in typeVar. If the default type is not a constrained TypeVar, + // use its concrete type to compare against the constraints. + if (isTypeVar(typeVar.shared.defaultType) && TypeVarType.hasConstraints(typeVar.shared.defaultType)) { + for (const constraint of typeVar.shared.defaultType.shared.constraints) { + if (!typeVar.shared.constraints.some((c) => isTypeSame(c, constraint))) { + isConstraintCompatible = false; + } + } + } else if ( + !typeVar.shared.constraints.some((constraint) => + isTypeSame(constraint, concreteDefaultType, { ignoreConditions: true }) + ) + ) { + isConstraintCompatible = false; + } + + if (!isConstraintCompatible) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarDefaultConstraintMismatch(), + defaultValueNode + ); + } + } + } + + function createTypeVarTupleType(errorNode: ExpressionNode, classType: ClassType, argList: Arg[]): Type | undefined { + let typeVarName = ''; + + if (argList.length === 0) { + addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.typeVarFirstArg(), errorNode); + return undefined; + } + + const firstArg = argList[0]; + if (firstArg.valueExpression && firstArg.valueExpression.nodeType === ParseNodeType.StringList) { + typeVarName = firstArg.valueExpression.d.strings.map((s) => s.d.value).join(''); + } else { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarFirstArg(), + firstArg.valueExpression || errorNode + ); + } + + const typeVar = TypeBase.cloneAsSpecialForm( + TypeVarType.createInstantiable(typeVarName, TypeVarKind.TypeVarTuple), + ClassType.cloneAsInstance(classType) + ); + typeVar.shared.defaultType = makeTupleObject(evaluatorInterface, [ + { type: UnknownType.create(), isUnbounded: true }, + ]); + + // Parse the remaining parameters. + for (let i = 1; i < argList.length; i++) { + const paramNameNode = argList[i].name; + const paramName = paramNameNode ? paramNameNode.d.value : undefined; + + if (paramName) { + if (paramName === 'default') { + const expr = argList[i].valueExpression; + if (expr) { + const defaultType = getTypeVarTupleDefaultType(expr, /* isPep695Syntax */ false); + if (defaultType) { + typeVar.shared.defaultType = defaultType; + typeVar.shared.isDefaultExplicit = true; + } + } + + const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode); + if ( + !fileInfo.isStubFile && + PythonVersion.isLessThan(fileInfo.executionEnvironment.pythonVersion, pythonVersion3_13) && + classType.shared.moduleName !== 'typing_extensions' + ) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarDefaultIllegal(), + expr! + ); + } + } else { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarTupleUnknownParam().format({ name: argList[i].name?.d.value || '?' }), + argList[i].node?.d.name || argList[i].valueExpression || errorNode + ); + } + } else { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarTupleConstraints(), + argList[i].valueExpression || errorNode + ); + } + } + + return typeVar; + } + + function getTypeVarTupleDefaultType(node: ExpressionNode, isPep695Syntax: boolean): Type | undefined { + const argType = getTypeOfExpressionExpectingType(node, { + allowUnpackedTuple: true, + allowTypeVarsWithoutScopeId: true, + forwardRefs: isPep695Syntax, + typeExpression: true, + }).type; + const isUnpackedTuple = isClass(argType) && isTupleClass(argType) && argType.priv.isUnpacked; + const isUnpackedTypeVar = isUnpackedTypeVarTuple(argType); + + if (!isUnpackedTuple && !isUnpackedTypeVar) { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.typeVarTupleDefaultNotUnpacked(), node); + return undefined; + } + + return convertToInstance(argType); + } + + function createParamSpecType(errorNode: ExpressionNode, classType: ClassType, argList: Arg[]): Type | undefined { + if (argList.length === 0) { + addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.paramSpecFirstArg(), errorNode); + return undefined; + } + + const firstArg = argList[0]; + let paramSpecName = ''; + if (firstArg.valueExpression && firstArg.valueExpression.nodeType === ParseNodeType.StringList) { + paramSpecName = firstArg.valueExpression.d.strings.map((s) => s.d.value).join(''); + } else { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.paramSpecFirstArg(), + firstArg.valueExpression || errorNode + ); + } + + const paramSpec = TypeBase.cloneAsSpecialForm( + TypeVarType.createInstantiable(paramSpecName, TypeVarKind.ParamSpec), + ClassType.cloneAsInstance(classType) + ); + + paramSpec.shared.defaultType = ParamSpecType.getUnknown(); + + // Parse the remaining parameters. + for (let i = 1; i < argList.length; i++) { + const paramNameNode = argList[i].name; + const paramName = paramNameNode ? paramNameNode.d.value : undefined; + + if (paramName) { + if (paramName === 'default') { + const expr = argList[i].valueExpression; + if (expr) { + const defaultType = getParamSpecDefaultType(expr, /* isPep695Syntax */ false); + if (defaultType) { + paramSpec.shared.defaultType = defaultType; + paramSpec.shared.isDefaultExplicit = true; + } + } + + const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode); + if ( + !fileInfo.isStubFile && + PythonVersion.isLessThan(fileInfo.executionEnvironment.pythonVersion, pythonVersion3_13) && + classType.shared.moduleName !== 'typing_extensions' + ) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarDefaultIllegal(), + expr! + ); + } + } else { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.paramSpecUnknownParam().format({ name: paramName }), + paramNameNode || argList[i].valueExpression || errorNode + ); + } + } else { + addDiagnostic( + DiagnosticRule.reportCallIssue, + LocMessage.paramSpecUnknownArg(), + argList[i].valueExpression || errorNode + ); + break; + } + } + + return paramSpec; + } + + function getParamSpecDefaultType(node: ExpressionNode, isPep695Syntax: boolean): Type | undefined { + const functionType = FunctionType.createSynthesizedInstance('', FunctionTypeFlags.ParamSpecValue); + + if (node.nodeType === ParseNodeType.Ellipsis) { + FunctionType.addDefaultParams(functionType); + functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm; + return functionType; + } + + if (node.nodeType === ParseNodeType.List) { + node.d.items.forEach((paramExpr, index) => { + const typeResult = getTypeOfExpressionExpectingType(paramExpr, { + allowTypeVarsWithoutScopeId: true, + forwardRefs: isPep695Syntax, + typeExpression: true, + }); + + FunctionType.addParam( + functionType, + FunctionParam.create( + ParamCategory.Simple, + convertToInstance(typeResult.type), + FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared, + `__p${index}` + ) + ); + }); + + if (node.d.items.length > 0) { + FunctionType.addPositionOnlyParamSeparator(functionType); + } + + // Update the type cache so we don't attempt to re-evaluate this node. + // The type doesn't matter, so use Any. + writeTypeCache(node, { type: AnyType.create() }, /* flags */ undefined); + return functionType; + } else { + const typeResult = getTypeOfExpressionExpectingType(node, { + allowParamSpec: true, + allowTypeVarsWithoutScopeId: true, + allowEllipsis: true, + typeExpression: true, + }); + + if (typeResult.typeErrors) { + return undefined; + } + + if (isParamSpec(typeResult.type)) { + FunctionType.addParamSpecVariadics(functionType, typeResult.type); + return functionType; + } + + if ( + isClassInstance(typeResult.type) && + ClassType.isBuiltIn(typeResult.type, ['EllipsisType', 'ellipsis']) + ) { + FunctionType.addDefaultParams(functionType); + return functionType; + } + } + + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.paramSpecDefaultNotTuple(), node); + + return undefined; + } + + // Handles a call to TypeAliasType(). This special form allows a caller to programmatically + // create a type alias as defined in PEP 695. If one or more of the arguments is incorrect, + // it returns undefined so the normal constructor evaluation can be performed (and type errors + // emitted). + function createTypeAliasType(errorNode: ExpressionNode, argList: Arg[]): Type | undefined { + if (errorNode.nodeType !== ParseNodeType.Call || !errorNode.parent || argList.length < 2) { + return undefined; + } + + if ( + errorNode.parent.nodeType !== ParseNodeType.Assignment || + errorNode.parent.d.rightExpr !== errorNode || + errorNode.parent.d.leftExpr.nodeType !== ParseNodeType.Name + ) { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.typeAliasTypeMustBeAssigned(), errorNode); + return undefined; + } + + const scope = ScopeUtils.getScopeForNode(errorNode); + if (scope) { + if (scope.type !== ScopeType.Class && scope.type !== ScopeType.Module && scope.type !== ScopeType.Builtin) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeAliasTypeBadScope(), + errorNode.parent.d.leftExpr + ); + } + } + + const nameNode = errorNode.parent.d.leftExpr; + + const firstArg = argList[0]; + if (firstArg.valueExpression && firstArg.valueExpression.nodeType === ParseNodeType.StringList) { + const typeAliasName = firstArg.valueExpression.d.strings.map((s) => s.d.value).join(''); + if (typeAliasName !== nameNode.d.value) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeAliasTypeNameMismatch(), + firstArg.valueExpression + ); + } + } else { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeAliasTypeNameArg(), + firstArg.valueExpression || errorNode + ); + return undefined; + } + + let valueExpr: ExpressionNode | undefined; + let typeParamsExpr: ExpressionNode | undefined; + + // Parse the remaining parameters. + for (let i = 1; i < argList.length; i++) { + const paramNameNode = argList[i].name; + const paramName = paramNameNode ? paramNameNode.d.value : undefined; + + if (paramName) { + if (paramName === 'type_params' && !typeParamsExpr) { + typeParamsExpr = argList[i].valueExpression; + } else if (paramName === 'value' && !valueExpr) { + valueExpr = argList[i].valueExpression; + } else { + return undefined; + } + } else if (i === 1) { + valueExpr = argList[i].valueExpression; + } else { + return undefined; + } + } + + // The value expression is not optional, so bail if it's not present. + if (!valueExpr) { + return undefined; + } + + let typeParams: TypeVarType[] | undefined; + if (typeParamsExpr) { + if (typeParamsExpr.nodeType !== ParseNodeType.Tuple) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeAliasTypeParamInvalid(), + typeParamsExpr + ); + return undefined; + } + + typeParams = []; + let isTypeParamListValid = true; + typeParamsExpr.d.items.map((expr) => { + let entryType = getTypeOfExpression( + expr, + EvalFlags.InstantiableType | EvalFlags.AllowTypeVarWithoutScopeId + ).type; + + if (isTypeVar(entryType)) { + if (entryType.priv.scopeId || (isTypeVarTuple(entryType) && entryType.priv.isUnpacked)) { + isTypeParamListValid = false; + } else { + entryType = TypeVarType.cloneForScopeId( + entryType, + ParseTreeUtils.getScopeIdForNode(nameNode), + nameNode.d.value, + TypeVarScopeType.TypeAlias + ); + } + + typeParams!.push(entryType); + } else { + isTypeParamListValid = false; + } + }); + + if (!isTypeParamListValid) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeAliasTypeParamInvalid(), + typeParamsExpr + ); + return undefined; + } + } + + return getTypeOfTypeAliasCommon( + nameNode, + nameNode, + valueExpr, + /* isPep695Syntax */ false, + /* typeParamNodes */ undefined, + () => typeParams + ); + } + + function getBooleanValue(node: ExpressionNode): boolean { + if (node.nodeType === ParseNodeType.Constant) { + if (node.d.constType === KeywordType.False) { + return false; + } else if (node.d.constType === KeywordType.True) { + return true; + } + } + + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.expectedBoolLiteral(), node); + return false; + } + + function getFunctionFullName(functionNode: ParseNode, moduleName: string, functionName: string): string { + const nameParts: string[] = [functionName]; + + let curNode: ParseNode | undefined = functionNode; + + // Walk the parse tree looking for classes or functions. + while (curNode) { + curNode = ParseTreeUtils.getEnclosingClassOrFunction(curNode); + if (curNode) { + nameParts.push(curNode.d.name.d.value); + } + } + + nameParts.push(moduleName); + + return nameParts.reverse().join('.'); + } + + // Implements the semantics of the NewType call as documented + // in the Python specification: The static type checker will treat + // the new type as if it were a subclass of the original type. + function createNewType(errorNode: ExpressionNode, argList: Arg[]): ClassType | undefined { + const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode); + let className = ''; + + if (argList.length !== 2) { + addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.newTypeParamCount(), errorNode); + return undefined; + } + + const nameArg = argList[0]; + if ( + nameArg.argCategory === ArgCategory.Simple && + nameArg.valueExpression && + nameArg.valueExpression.nodeType === ParseNodeType.StringList + ) { + className = nameArg.valueExpression.d.strings.map((s) => s.d.value).join(''); + } + + if (!className) { + addDiagnostic(DiagnosticRule.reportArgumentType, LocMessage.newTypeBadName(), argList[0].node ?? errorNode); + return undefined; + } + + if ( + errorNode.parent?.nodeType === ParseNodeType.Assignment && + errorNode.parent.d.leftExpr.nodeType === ParseNodeType.Name && + errorNode.parent.d.leftExpr.d.value !== className + ) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.newTypeNameMismatch(), + errorNode.parent.d.leftExpr + ); + return undefined; + } + + let baseClass = getTypeOfArgExpectingType(argList[1]).type; + let isBaseClassAny = false; + + if (isAnyOrUnknown(baseClass)) { + baseClass = prefetched?.objectClass ?? UnknownType.create(); + + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.newTypeAnyOrUnknown(), + argList[1].node ?? errorNode + ); + + isBaseClassAny = true; + } + + // Specifically disallow Annotated. + if ( + baseClass.props?.specialForm && + isClassInstance(baseClass.props.specialForm) && + ClassType.isBuiltIn(baseClass.props.specialForm, 'Annotated') + ) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.newTypeNotAClass(), + argList[1].node || errorNode + ); + return undefined; + } + + if (!isInstantiableClass(baseClass)) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.newTypeNotAClass(), + argList[1].node || errorNode + ); + return undefined; + } + + if (ClassType.isProtocolClass(baseClass) || ClassType.isTypedDictClass(baseClass)) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.newTypeProtocolClass(), + argList[1].node || errorNode + ); + } else if (baseClass.priv.literalValue !== undefined) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.newTypeLiteral(), + argList[1].node || errorNode + ); + } + + const classType = ClassType.createInstantiable( + className, + ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className), + fileInfo.moduleName, + fileInfo.fileUri, + ClassTypeFlags.Final | ClassTypeFlags.NewTypeClass | ClassTypeFlags.ValidTypeAliasClass, + ParseTreeUtils.getTypeSourceId(errorNode), + /* declaredMetaclass */ undefined, + baseClass.shared.effectiveMetaclass + ); + classType.shared.baseClasses.push(isBaseClassAny ? AnyType.create() : baseClass); + computeMroLinearization(classType); + + if (!isBaseClassAny) { + // Synthesize an __init__ method that accepts only the specified type. + const initType = FunctionType.createSynthesizedInstance('__init__'); + FunctionType.addParam( + initType, + FunctionParam.create(ParamCategory.Simple, AnyType.create(), FunctionParamFlags.TypeDeclared, 'self') + ); + FunctionType.addParam( + initType, + FunctionParam.create( + ParamCategory.Simple, + ClassType.cloneAsInstance(baseClass), + FunctionParamFlags.TypeDeclared, + '_x' + ) + ); + initType.shared.declaredReturnType = getNoneType(); + ClassType.getSymbolTable(classType).set( + '__init__', + Symbol.createWithType(SymbolFlags.ClassMember, initType) + ); + + // Synthesize a trivial __new__ method. + const newType = FunctionType.createSynthesizedInstance('__new__', FunctionTypeFlags.ConstructorMethod); + FunctionType.addParam( + newType, + FunctionParam.create(ParamCategory.Simple, AnyType.create(), FunctionParamFlags.TypeDeclared, 'cls') + ); + FunctionType.addDefaultParams(newType); + newType.shared.declaredReturnType = ClassType.cloneAsInstance(classType); + newType.priv.constructorTypeVarScopeId = getTypeVarScopeId(classType); + ClassType.getSymbolTable(classType).set('__new__', Symbol.createWithType(SymbolFlags.ClassMember, newType)); + } + + return classType; + } + + // Implements the semantics of the multi-parameter variant of the "type" call. + function createClassFromMetaclass( + errorNode: ExpressionNode, + argList: Arg[], + metaclass: ClassType + ): ClassType | undefined { + const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode); + const arg0Type = getTypeOfArg(argList[0], /* inferenceContext */ undefined).type; + if (!isClassInstance(arg0Type) || !ClassType.isBuiltIn(arg0Type, 'str')) { + return undefined; + } + const className = (arg0Type.priv.literalValue as string) || '_'; + + const arg1Type = getTypeOfArg(argList[1], /* inferenceContext */ undefined).type; + + // TODO - properly handle case where tuple of base classes is provided. + if (!isClassInstance(arg1Type) || !isTupleClass(arg1Type) || arg1Type.priv.tupleTypeArgs === undefined) { + return undefined; + } + + const classType = ClassType.createInstantiable( + className, + ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className), + fileInfo.moduleName, + fileInfo.fileUri, + ClassTypeFlags.ValidTypeAliasClass, + ParseTreeUtils.getTypeSourceId(errorNode), + metaclass, + arg1Type.shared.effectiveMetaclass + ); + arg1Type.priv.tupleTypeArgs.forEach((typeArg) => { + const specializedType = makeTopLevelTypeVarsConcrete(typeArg.type); + + if (isEffectivelyInstantiable(specializedType)) { + classType.shared.baseClasses.push(specializedType); + } else { + classType.shared.baseClasses.push(UnknownType.create()); + } + }); + + if (!computeMroLinearization(classType)) { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.methodOrdering(), errorNode); + } + + return classType; + } + + function getTypeOfConstant(node: ConstantNode, flags: EvalFlags): TypeResult { + let type: Type | undefined; + + if (node.d.constType === KeywordType.None) { + if (prefetched?.noneTypeClass) { + type = + (flags & EvalFlags.InstantiableType) !== 0 + ? prefetched.noneTypeClass + : convertToInstance(prefetched.noneTypeClass); + + type = TypeBase.cloneWithTypeForm(type, convertToInstance(type)); + } + } else if ( + node.d.constType === KeywordType.True || + node.d.constType === KeywordType.False || + node.d.constType === KeywordType.Debug + ) { + type = getBuiltInObject(node, 'bool'); + + // For True and False, we can create truthy and falsy + // versions of 'bool'. + if (type && isClassInstance(type)) { + if (node.d.constType === KeywordType.True) { + type = ClassType.cloneWithLiteral(type, /* value */ true); + } else if (node.d.constType === KeywordType.False) { + type = ClassType.cloneWithLiteral(type, /* value */ false); + } + } + } + + return { type: type ?? UnknownType.create() }; + } + + function getTypeOfMagicMethodCall( + objType: Type, + methodName: string, + argList: TypeResult[], + errorNode: ExpressionNode, + inferenceContext?: InferenceContext, + diag?: DiagnosticAddendum + ): TypeResult | undefined { + let magicMethodSupported = true; + let isIncomplete = false; + let deprecationInfo: MagicMethodDeprecationInfo | undefined; + const overloadsUsedForCall: FunctionType[] = []; + + // Create a helper lambda for object subtypes. + const handleSubtype = (subtype: ClassType | TypeVarType) => { + let magicMethodType: Type | undefined; + const concreteSubtype = makeTopLevelTypeVarsConcrete(subtype); + + if (isClass(concreteSubtype)) { + magicMethodType = getBoundMagicMethod(concreteSubtype, methodName, subtype, errorNode, diag); + } + + if (magicMethodType) { + const functionArgs: Arg[] = argList.map((arg) => { + return { + argCategory: ArgCategory.Simple, + typeResult: arg, + }; + }); + + let callResult: CallResult | undefined; + + callResult = useSpeculativeMode(errorNode, () => { + assert(magicMethodType !== undefined); + return validateCallArgs( + errorNode, + functionArgs, + { type: magicMethodType }, + /* constraints */ undefined, + /* skipUnknownArgCheck */ true, + inferenceContext + ); + }); + + // If there were errors with the expected type, try + // to evaluate without the expected type. + if (callResult.argumentErrors && inferenceContext) { + callResult = useSpeculativeMode(errorNode, () => { + assert(magicMethodType !== undefined); + return validateCallArgs( + errorNode, + functionArgs, + { type: magicMethodType }, + /* constraints */ undefined, + /* skipUnknownArgCheck */ true, + /* inferenceContext */ undefined + ); + }); + } + + if (callResult.argumentErrors) { + magicMethodSupported = false; + } else if (callResult.overloadsUsedForCall) { + callResult.overloadsUsedForCall.forEach((overload) => { + overloadsUsedForCall.push(overload); + + // If one of the overloads is deprecated, note the message. + if (overload.shared.deprecatedMessage && isClass(concreteSubtype)) { + deprecationInfo = { + deprecatedMessage: overload.shared.deprecatedMessage, + className: concreteSubtype.shared.name, + methodName, + }; + } + }); + } + + if (callResult.isTypeIncomplete) { + isIncomplete = true; + } + + return callResult.returnType; + } + + magicMethodSupported = false; + return undefined; + }; + + const returnType = mapSubtypes(objType, (subtype) => { + if (isAnyOrUnknown(subtype)) { + return subtype; + } + + if (isClassInstance(subtype) || isInstantiableClass(subtype) || isTypeVar(subtype)) { + return handleSubtype(subtype); + } + + if (isNoneInstance(subtype)) { + if (prefetched?.objectClass && isInstantiableClass(prefetched.objectClass)) { + // Use 'object' for 'None'. + return handleSubtype(ClassType.cloneAsInstance(prefetched.objectClass)); + } + } + + if (isNoneTypeClass(subtype)) { + if (prefetched?.typeClass && isInstantiableClass(prefetched.typeClass)) { + // Use 'type' for 'type[None]'. + return handleSubtype(ClassType.cloneAsInstance(prefetched.typeClass)); + } + } + + magicMethodSupported = false; + return undefined; + }); + + if (!magicMethodSupported) { + return undefined; + } + + return { type: returnType, isIncomplete, magicMethodDeprecationInfo: deprecationInfo, overloadsUsedForCall }; + } + + function getTypeOfDictionary( + node: DictionaryNode, + flags: EvalFlags, + inferenceContext: InferenceContext | undefined + ): TypeResult { + if ((flags & EvalFlags.TypeExpression) !== 0 && node.parent?.nodeType !== ParseNodeType.Argument) { + const diag = new DiagnosticAddendum(); + diag.addMessage(LocAddendum.useDictInstead()); + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.dictInAnnotation() + diag.getString(), node); + } + + // If the expected type is a union, analyze for each of the subtypes + // to find one that matches. + let expectedType = inferenceContext?.expectedType; + + if (inferenceContext && isUnion(inferenceContext.expectedType)) { + let matchingSubtype: Type | undefined; + let matchingSubtypeResult: TypeResult | undefined; + + doForEachSubtype( + inferenceContext.expectedType, + (subtype) => { + // Use shortcut if we've already found a match. + if (matchingSubtypeResult && !matchingSubtypeResult.typeErrors) { + return; + } + + const subtypeResult = useSpeculativeMode(node, () => { + return getTypeOfDictionaryWithContext(node, flags, makeInferenceContext(subtype)); + }); + + if (subtypeResult && assignType(subtype, subtypeResult.type)) { + // If this is the first result we're seeing or it's the first result + // without errors, select it as the match. + if (!matchingSubtypeResult || (matchingSubtypeResult.typeErrors && !subtypeResult.typeErrors)) { + matchingSubtype = subtype; + matchingSubtypeResult = subtypeResult; + } + } + }, + /* sortSubtypes */ true + ); + + expectedType = matchingSubtype; + } + + let expectedTypeDiagAddendum = undefined; + if (expectedType) { + expectedTypeDiagAddendum = new DiagnosticAddendum(); + const result = getTypeOfDictionaryWithContext( + node, + flags, + makeInferenceContext(expectedType), + expectedTypeDiagAddendum + ); + if (result) { + return result; + } + } + + // Don't force strict inference when the expected type is a TypeVar that + // couldn't constrain the dict's key/value structure. A TypeVar like _T + // from sorted(Iterable[_T], key=...) doesn't provide useful type + // information for dict inference, and forcing strict inference would + // widen heterogeneous value types into a union that can cause false + // positives (e.g., dict[str, Path | str | dict[Any, Any]] when the + // user meant each key to have a specific type). + // Note: bounded/constrained TypeVars with dict-like bounds are handled + // by getTypeOfDictionaryWithContext above (via makeTopLevelTypeVarsConcrete), + // so they don't reach this fallback. + const hasUsefulExpectedType = !!inferenceContext?.expectedType && !isTypeVar(inferenceContext.expectedType); + const result = getTypeOfDictionaryInferred(node, flags, /* hasExpectedType */ hasUsefulExpectedType); + return { ...result, expectedTypeDiagAddendum }; + } + + function getTypeOfDictionaryWithContext( + node: DictionaryNode, + flags: EvalFlags, + inferenceContext: InferenceContext, + expectedDiagAddendum?: DiagnosticAddendum + ): TypeResult | undefined { + inferenceContext.expectedType = transformPossibleRecursiveTypeAlias(inferenceContext.expectedType); + let concreteExpectedType = makeTopLevelTypeVarsConcrete(inferenceContext.expectedType); + + if (!isClassInstance(concreteExpectedType)) { + return undefined; + } + + const keyTypes: TypeResultWithNode[] = []; + const valueTypes: TypeResultWithNode[] = []; + let isIncomplete = false; + let typeErrors = false; + + // Handle TypedDict's as a special case. + if (ClassType.isTypedDictClass(concreteExpectedType)) { + // Remove any conditions associated with the type so the resulting type isn't + // considered compatible with a bound TypeVar. + concreteExpectedType = TypeBase.cloneForCondition(concreteExpectedType, undefined); + + const expectedTypedDictEntries = getTypedDictMembersForClass(evaluatorInterface, concreteExpectedType); + + // Infer the key and value types if possible. + const keyValueTypeResult = getKeyAndValueTypesFromDictionary( + node, + flags, + keyTypes, + valueTypes, + /* forceStrictInference */ true, + /* isValueTypeInvariant */ true, + /* expectedKeyType */ undefined, + /* expectedValueType */ undefined, + expectedTypedDictEntries, + expectedDiagAddendum + ); + + if (keyValueTypeResult.isIncomplete) { + isIncomplete = true; + } + + if (keyValueTypeResult.typeErrors) { + typeErrors = true; + } + + const resultTypedDict = assignToTypedDict( + evaluatorInterface, + concreteExpectedType, + keyTypes, + valueTypes, + // Don't overwrite existing expectedDiagAddendum messages if they were + // already provided by getKeyValueTypesFromDictionary. + expectedDiagAddendum?.isEmpty() ? expectedDiagAddendum : undefined + ); + if (resultTypedDict) { + return { + type: resultTypedDict, + isIncomplete, + }; + } + + return undefined; + } + + let expectedKeyType: Type; + let expectedValueType: Type; + + if (isAnyOrUnknown(inferenceContext.expectedType)) { + expectedKeyType = inferenceContext.expectedType; + expectedValueType = inferenceContext.expectedType; + } else { + const builtInDict = getBuiltInObject(node, 'dict'); + if (!isClassInstance(builtInDict)) { + return undefined; + } + + const dictConstraints = new ConstraintTracker(); + if ( + !addConstraintsForExpectedType( + evaluatorInterface, + builtInDict, + inferenceContext.expectedType, + dictConstraints, + ParseTreeUtils.getTypeVarScopesForNode(node), + node.start + ) + ) { + return undefined; + } + + const specializedDict = solveAndApplyConstraints( + ClassType.cloneAsInstantiable(builtInDict), + dictConstraints + ) as ClassType; + if (!specializedDict.priv.typeArgs || specializedDict.priv.typeArgs.length !== 2) { + return undefined; + } + + expectedKeyType = specializedDict.priv.typeArgs[0]; + expectedValueType = specializedDict.priv.typeArgs[1]; + } + + // Dict and MutableMapping types have invariant value types, so they + // cannot be narrowed further. Other super-types like Mapping, Collection, + // and Iterable use covariant value types, so they can be narrowed. + let isValueTypeInvariant = false; + if (isClassInstance(inferenceContext.expectedType)) { + if (inferenceContext.expectedType.shared.typeParams.length >= 2) { + const valueTypeParam = inferenceContext.expectedType.shared.typeParams[1]; + if (TypeVarType.getVariance(valueTypeParam) === Variance.Invariant) { + isValueTypeInvariant = true; + } + } + } + + // Infer the key and value types if possible. + const keyValueResult = getKeyAndValueTypesFromDictionary( + node, + flags, + keyTypes, + valueTypes, + /* forceStrictInference */ true, + isValueTypeInvariant, + expectedKeyType, + expectedValueType, + undefined, + expectedDiagAddendum + ); + + if (keyValueResult.isIncomplete) { + isIncomplete = true; + } + + if (keyValueResult.typeErrors) { + typeErrors = true; + } + + const specializedKeyType = inferTypeArgFromExpectedEntryType( + makeInferenceContext(expectedKeyType), + keyTypes.map((result) => result.type), + /* isNarrowable */ false + ); + const specializedValueType = inferTypeArgFromExpectedEntryType( + makeInferenceContext(expectedValueType), + valueTypes.map((result) => result.type), + !isValueTypeInvariant + ); + if (!specializedKeyType || !specializedValueType) { + return undefined; + } + + const type = getBuiltInObject(node, 'dict', [specializedKeyType, specializedValueType]); + return { type, isIncomplete, typeErrors }; + } + + // Attempts to infer the type of a dictionary statement. If hasExpectedType + // is true, strict inference is used for the subexpressions. + function getTypeOfDictionaryInferred(node: DictionaryNode, flags: EvalFlags, hasExpectedType: boolean): TypeResult { + const fallbackType = hasExpectedType ? AnyType.create() : UnknownType.create(); + let keyType: Type = fallbackType; + let valueType: Type = fallbackType; + + const keyTypeResults: TypeResultWithNode[] = []; + const valueTypeResults: TypeResultWithNode[] = []; + + let isEmptyContainer = false; + let isIncomplete = false; + let typeErrors = false; + + // Infer the key and value types if possible. + const keyValueResult = getKeyAndValueTypesFromDictionary( + node, + flags, + keyTypeResults, + valueTypeResults, + /* forceStrictInference */ hasExpectedType, + /* isValueTypeInvariant */ false + ); + + if (keyValueResult.isIncomplete) { + isIncomplete = true; + } + + if (keyValueResult.typeErrors) { + typeErrors = true; + } + + // Strip any literal values and TypeForm types. + const keyTypes = keyTypeResults.map((t) => + stripTypeForm(convertSpecialFormToRuntimeValue(stripLiteralValue(t.type), flags, !hasExpectedType)) + ); + const valueTypes = valueTypeResults.map((t) => + stripTypeForm(convertSpecialFormToRuntimeValue(stripLiteralValue(t.type), flags, !hasExpectedType)) + ); + + if (keyTypes.length > 0) { + if (AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.strictDictionaryInference || hasExpectedType) { + keyType = combineTypes(keyTypes); + } else { + keyType = areTypesSame(keyTypes, { ignorePseudoGeneric: true }) ? keyTypes[0] : fallbackType; + } + } else { + keyType = fallbackType; + } + + // If the value type differs and we're not using "strict inference mode", + // we need to back off because we can't properly represent the mappings + // between different keys and associated value types. If all the values + // are the same type, we'll assume that all values in this dictionary should + // be the same. + if (valueTypes.length > 0) { + if (AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.strictDictionaryInference || hasExpectedType) { + valueType = combineTypes(valueTypes); + } else { + valueType = areTypesSame(valueTypes, { ignorePseudoGeneric: true }) ? valueTypes[0] : fallbackType; + } + } else { + valueType = fallbackType; + isEmptyContainer = true; + } + + const dictClass = getBuiltInType(node, 'dict'); + const type = isInstantiableClass(dictClass) + ? ClassType.cloneAsInstance( + ClassType.specialize( + dictClass, + [keyType, valueType], + /* isTypeArgExplicit */ true, + /* includeSubclasses */ undefined, + /* tupleTypeArgs */ undefined, + isEmptyContainer + ) + ) + : UnknownType.create(); + + if (isIncomplete) { + if (getContainerDepth(type) > maxInferredContainerDepth) { + return { type: UnknownType.create() }; + } + } + + return { type, isIncomplete, typeErrors }; + } + + function getKeyAndValueTypesFromDictionary( + node: DictionaryNode, + flags: EvalFlags, + keyTypes: TypeResultWithNode[], + valueTypes: TypeResultWithNode[], + forceStrictInference: boolean, + isValueTypeInvariant: boolean, + expectedKeyType?: Type, + expectedValueType?: Type, + expectedTypedDictEntries?: TypedDictEntries, + expectedDiagAddendum?: DiagnosticAddendum + ): TypeResult { + let isIncomplete = false; + let typeErrors = false; + + // Mask out some of the flags that are not applicable for a dictionary key + // even if it appears within an inlined TypedDict annotation. + const keyFlags = flags & ~(EvalFlags.TypeExpression | EvalFlags.StrLiteralAsType | EvalFlags.InstantiableType); + + // Infer the key and value types if possible. + node.d.items.forEach((entryNode, index) => { + let addUnknown = true; + + if (entryNode.nodeType === ParseNodeType.DictionaryKeyEntry) { + const keyTypeResult = getTypeOfExpression( + entryNode.d.keyExpr, + keyFlags | EvalFlags.StripTupleLiterals, + makeInferenceContext( + expectedKeyType ?? (forceStrictInference ? NeverType.createNever() : undefined) + ) + ); + + if (keyTypeResult.isIncomplete) { + isIncomplete = true; + } + + if (keyTypeResult.typeErrors) { + typeErrors = true; + } + + const keyType = keyTypeResult.type; + + if (!keyTypeResult.isIncomplete && !keyTypeResult.typeErrors) { + verifySetEntryOrDictKeyIsHashable(entryNode.d.keyExpr, keyType, /* isDictKey */ true); + } + + if (expectedDiagAddendum && keyTypeResult.expectedTypeDiagAddendum) { + expectedDiagAddendum.addAddendum(keyTypeResult.expectedTypeDiagAddendum); + } + + let valueTypeResult: TypeResult; + let entryInferenceContext: InferenceContext | undefined; + + if ( + expectedTypedDictEntries && + isClassInstance(keyType) && + ClassType.isBuiltIn(keyType, 'str') && + isLiteralType(keyType) && + (expectedTypedDictEntries.knownItems.has(keyType.priv.literalValue as string) || + expectedTypedDictEntries.extraItems) + ) { + let effectiveValueType = + expectedTypedDictEntries.knownItems.get(keyType.priv.literalValue as string)?.valueType ?? + expectedTypedDictEntries.extraItems?.valueType; + if (effectiveValueType) { + const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(node); + effectiveValueType = transformExpectedType(effectiveValueType, liveTypeVarScopes, node.start); + } + entryInferenceContext = makeInferenceContext(effectiveValueType); + valueTypeResult = getTypeOfExpression( + entryNode.d.valueExpr, + flags | EvalFlags.StripTupleLiterals, + entryInferenceContext + ); + } else { + let effectiveValueType = + expectedValueType ?? (forceStrictInference ? NeverType.createNever() : undefined); + if (effectiveValueType) { + const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(node); + effectiveValueType = transformExpectedType(effectiveValueType, liveTypeVarScopes, node.start); + } + entryInferenceContext = makeInferenceContext(effectiveValueType); + valueTypeResult = getTypeOfExpression( + entryNode.d.valueExpr, + flags | EvalFlags.StripTupleLiterals, + entryInferenceContext + ); + } + + if (entryInferenceContext && !valueTypeResult.typeErrors) { + const fromExpectedType = inferTypeArgFromExpectedEntryType( + entryInferenceContext, + [valueTypeResult.type], + !isValueTypeInvariant + ); + + if (fromExpectedType) { + valueTypeResult = { ...valueTypeResult, type: fromExpectedType }; + } + } + + if (expectedDiagAddendum && valueTypeResult.expectedTypeDiagAddendum) { + expectedDiagAddendum.addAddendum(valueTypeResult.expectedTypeDiagAddendum); + } + + const valueType = valueTypeResult.type; + if (valueTypeResult.isIncomplete) { + isIncomplete = true; + } + + if (valueTypeResult.typeErrors) { + typeErrors = true; + } + + if (forceStrictInference || index < maxEntriesToUseForInference) { + // If an existing key has the same literal type, delete the previous + // key since we're overwriting it here. + if (isClass(keyType) && isLiteralType(keyType)) { + const existingIndex = keyTypes.findIndex((kt) => isTypeSame(keyType, kt.type)); + if (existingIndex >= 0) { + keyTypes.splice(existingIndex, 1); + valueTypes.splice(existingIndex, 1); + } + } + + keyTypes.push({ node: entryNode.d.keyExpr, type: keyType }); + valueTypes.push({ node: entryNode.d.valueExpr, type: valueType }); + } + + addUnknown = false; + } else if (entryNode.nodeType === ParseNodeType.DictionaryExpandEntry) { + let expectedType: Type | undefined; + if (expectedKeyType && expectedValueType) { + if ( + prefetched?.supportsKeysAndGetItemClass && + isInstantiableClass(prefetched.supportsKeysAndGetItemClass) + ) { + expectedType = ClassType.cloneAsInstance( + ClassType.specialize(prefetched.supportsKeysAndGetItemClass, [ + expectedKeyType, + expectedValueType, + ]) + ); + } + } + + const entryInferenceContext = makeInferenceContext(expectedType); + let unexpandedTypeResult = getTypeOfExpression( + entryNode.d.expr, + flags | EvalFlags.StripTupleLiterals, + entryInferenceContext + ); + + if (entryInferenceContext && !unexpandedTypeResult.typeErrors) { + const fromExpectedType = inferTypeArgFromExpectedEntryType( + entryInferenceContext, + [unexpandedTypeResult.type], + !isValueTypeInvariant + ); + + if (fromExpectedType) { + unexpandedTypeResult = { ...unexpandedTypeResult, type: fromExpectedType }; + } + } + + if (unexpandedTypeResult.isIncomplete) { + isIncomplete = true; + } + + if (unexpandedTypeResult.typeErrors) { + typeErrors = true; + } + + const unexpandedType = unexpandedTypeResult.type; + + if (isAnyOrUnknown(unexpandedType)) { + if (forceStrictInference || index < maxEntriesToUseForInference) { + keyTypes.push({ node: entryNode, type: unexpandedType }); + valueTypes.push({ node: entryNode, type: unexpandedType }); + } + addUnknown = false; + } else if (isClassInstance(unexpandedType) && ClassType.isTypedDictClass(unexpandedType)) { + // Handle dictionary expansion for a TypedDict. + if (prefetched?.strClass && isInstantiableClass(prefetched.strClass)) { + const strObject = ClassType.cloneAsInstance(prefetched.strClass); + const tdEntries = getTypedDictMembersForClass( + evaluatorInterface, + unexpandedType, + /* allowNarrowed */ true + ); + + tdEntries.knownItems.forEach((entry, name) => { + if (entry.isRequired || entry.isProvided) { + keyTypes.push({ + node: entryNode, + type: ClassType.cloneWithLiteral(strObject, name), + }); + valueTypes.push({ node: entryNode, type: entry.valueType }); + } + }); + + if (!expectedTypedDictEntries) { + keyTypes.push({ node: entryNode, type: ClassType.cloneAsInstance(strObject) }); + valueTypes.push({ + node: entryNode, + type: tdEntries.extraItems?.valueType ?? getObjectType(), + }); + } + + addUnknown = false; + } + } else if ( + prefetched?.supportsKeysAndGetItemClass && + isInstantiableClass(prefetched.supportsKeysAndGetItemClass) + ) { + const mappingConstraints = new ConstraintTracker(); + + const supportsKeysAndGetItemClass = selfSpecializeClass(prefetched.supportsKeysAndGetItemClass); + + if ( + assignType( + ClassType.cloneAsInstance(supportsKeysAndGetItemClass), + unexpandedType, + /* diag */ undefined, + mappingConstraints, + AssignTypeFlags.RetainLiteralsForTypeVar + ) + ) { + const specializedMapping = solveAndApplyConstraints( + supportsKeysAndGetItemClass, + mappingConstraints + ) as ClassType; + const typeArgs = specializedMapping.priv.typeArgs; + if (typeArgs && typeArgs.length >= 2) { + if (forceStrictInference || index < maxEntriesToUseForInference) { + keyTypes.push({ node: entryNode, type: typeArgs[0] }); + valueTypes.push({ node: entryNode, type: typeArgs[1] }); + } + addUnknown = false; + } + } else { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.dictUnpackIsNotMapping(), + entryNode + ); + } + } + } else if (entryNode.nodeType === ParseNodeType.Comprehension) { + const dictEntryTypeResult = getElementTypeFromComprehension( + entryNode, + flags | EvalFlags.StripTupleLiterals, + expectedValueType, + expectedKeyType + ); + const dictEntryType = dictEntryTypeResult.type; + if (dictEntryTypeResult.isIncomplete) { + isIncomplete = true; + } + + if (dictEntryTypeResult.typeErrors) { + typeErrors = true; + } + + // The result should be a tuple. + if (isClassInstance(dictEntryType) && isTupleClass(dictEntryType)) { + const typeArgs = dictEntryType.priv.tupleTypeArgs?.map((t) => t.type); + if (typeArgs && typeArgs.length === 2) { + if (forceStrictInference || index < maxEntriesToUseForInference) { + keyTypes.push({ node: entryNode, type: typeArgs[0] }); + valueTypes.push({ node: entryNode, type: typeArgs[1] }); + } + addUnknown = false; + } + } + } + + if (addUnknown) { + if (forceStrictInference || index < maxEntriesToUseForInference) { + keyTypes.push({ node: entryNode, type: UnknownType.create() }); + valueTypes.push({ node: entryNode, type: UnknownType.create() }); + } + } + }); + + return { type: AnyType.create(), isIncomplete, typeErrors }; + } + + function getTypeOfListOrSet( + node: ListNode | SetNode, + flags: EvalFlags, + inferenceContext: InferenceContext | undefined + ): TypeResult { + if ( + (flags & EvalFlags.TypeExpression) !== 0 && + node.nodeType === ParseNodeType.List && + node.parent?.nodeType !== ParseNodeType.Argument + ) { + const diag = new DiagnosticAddendum(); + diag.addMessage(LocAddendum.useListInstead()); + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.listInAnnotation() + diag.getString(), node); + } + + flags &= ~(EvalFlags.TypeExpression | EvalFlags.StrLiteralAsType | EvalFlags.InstantiableType); + + // If the expected type is a union, recursively call for each of the subtypes + // to find one that matches. + let expectedType = inferenceContext?.expectedType; + + if (inferenceContext && isUnion(inferenceContext.expectedType)) { + let matchingSubtype: Type | undefined; + let matchingSubtypeResult: TypeResult | undefined; + + doForEachSubtype( + inferenceContext.expectedType, + (subtype) => { + // Use shortcut if we've already found a match. + if (matchingSubtypeResult && !matchingSubtypeResult.typeErrors) { + return; + } + + const subtypeResult = useSpeculativeMode(node, () => { + return getTypeOfListOrSetWithContext(node, flags, makeInferenceContext(subtype)); + }); + + if (subtypeResult && assignType(subtype, subtypeResult.type)) { + // If this is the first result we're seeing or it's the first result + // without errors, select it as the match. + if (!matchingSubtypeResult || (matchingSubtypeResult.typeErrors && !subtypeResult.typeErrors)) { + matchingSubtype = subtype; + matchingSubtypeResult = subtypeResult; + } + } + }, + /* sortSubtypes */ true + ); + + expectedType = matchingSubtype; + } + + let expectedTypeDiagAddendum: DiagnosticAddendum | undefined; + if (expectedType) { + const result = getTypeOfListOrSetWithContext(node, flags, makeInferenceContext(expectedType)); + if (result && !result.typeErrors) { + return result; + } + + expectedTypeDiagAddendum = result?.expectedTypeDiagAddendum; + } + + const typeResult = getTypeOfListOrSetInferred( + node, + flags, + /* hasExpectedType */ !!inferenceContext?.expectedType + ); + return { ...typeResult, expectedTypeDiagAddendum }; + } + + // Attempts to determine the type of a list or set statement based on an expected type. + // Returns undefined if that type cannot be honored. + function getTypeOfListOrSetWithContext( + node: ListNode | SetNode, + flags: EvalFlags, + inferenceContext: InferenceContext + ): TypeResult | undefined { + const builtInClassName = node.nodeType === ParseNodeType.List ? 'list' : 'set'; + inferenceContext.expectedType = transformPossibleRecursiveTypeAlias(inferenceContext.expectedType); + + let isIncomplete = false; + let typeErrors = false; + const verifyHashable = node.nodeType === ParseNodeType.Set; + + const expectedEntryType = getExpectedEntryTypeForIterable( + node, + getBuiltInType(node, builtInClassName), + inferenceContext + ); + if (!expectedEntryType) { + return undefined; + } + + const entryTypes: Type[] = []; + const expectedTypeDiagAddendum = new DiagnosticAddendum(); + node.d.items.forEach((entry) => { + let entryTypeResult: TypeResult; + + if (entry.nodeType === ParseNodeType.Comprehension) { + entryTypeResult = getElementTypeFromComprehension( + entry, + flags | EvalFlags.StripTupleLiterals, + expectedEntryType + ); + } else { + entryTypeResult = getTypeOfExpression( + entry, + flags | EvalFlags.StripTupleLiterals, + makeInferenceContext(expectedEntryType) + ); + } + + entryTypes.push(entryTypeResult.type); + + if (entryTypeResult.isIncomplete) { + isIncomplete = true; + } + + if (entryTypeResult.typeErrors) { + typeErrors = true; + } + + if (entryTypeResult.expectedTypeDiagAddendum) { + expectedTypeDiagAddendum.addAddendum(entryTypeResult.expectedTypeDiagAddendum); + } + + if (verifyHashable && !entryTypeResult.isIncomplete && !entryTypeResult.typeErrors) { + verifySetEntryOrDictKeyIsHashable(entry, entryTypeResult.type, /* isDictKey */ false); + } + }); + + let isTypeInvariant = false; + + if (isClassInstance(inferenceContext.expectedType)) { + inferVarianceForClass(inferenceContext.expectedType); + + if ( + inferenceContext.expectedType.shared.typeParams.some( + (t) => TypeVarType.getVariance(t) === Variance.Invariant + ) + ) { + isTypeInvariant = true; + } + } + + const specializedEntryType = inferTypeArgFromExpectedEntryType( + makeInferenceContext(expectedEntryType), + entryTypes, + !isTypeInvariant + ); + if (!specializedEntryType) { + return { type: UnknownType.create(), isIncomplete, typeErrors: true, expectedTypeDiagAddendum }; + } + + const type = getBuiltInObject(node, builtInClassName, [specializedEntryType]); + return { type, isIncomplete, typeErrors, expectedTypeDiagAddendum }; + } + + function getExpectedEntryTypeForIterable( + node: ListNode | SetNode | ComprehensionNode, + expectedClassType: Type | undefined, + inferenceContext?: InferenceContext + ): Type | undefined { + if (!inferenceContext) { + return undefined; + } + + if (!expectedClassType || !isInstantiableClass(expectedClassType)) { + return undefined; + } + + if (isAnyOrUnknown(inferenceContext.expectedType)) { + return inferenceContext.expectedType; + } + + if (!isClassInstance(inferenceContext.expectedType)) { + return undefined; + } + + const constraints = new ConstraintTracker(); + if ( + !addConstraintsForExpectedType( + evaluatorInterface, + ClassType.cloneAsInstance(expectedClassType), + inferenceContext.expectedType, + constraints, + ParseTreeUtils.getTypeVarScopesForNode(node), + node.start + ) + ) { + return undefined; + } + + const specializedListOrSet = solveAndApplyConstraints(expectedClassType, constraints) as ClassType; + if (!specializedListOrSet.priv.typeArgs) { + return undefined; + } + + return specializedListOrSet.priv.typeArgs[0]; + } + + // Attempts to infer the type of a list or set statement with no "expected type". + function getTypeOfListOrSetInferred( + node: ListNode | SetNode, + flags: EvalFlags, + hasExpectedType: boolean + ): TypeResult { + const builtInClassName = node.nodeType === ParseNodeType.List ? 'list' : 'set'; + const verifyHashable = node.nodeType === ParseNodeType.Set; + let isEmptyContainer = false; + let isIncomplete = false; + let typeErrors = false; + + let entryTypes: Type[] = []; + node.d.items.forEach((entry, index) => { + let entryTypeResult: TypeResult; + + if (entry.nodeType === ParseNodeType.Comprehension && !entry.d.isGenerator) { + entryTypeResult = getElementTypeFromComprehension(entry, flags | EvalFlags.StripTupleLiterals); + } else { + entryTypeResult = getTypeOfExpression(entry, flags | EvalFlags.StripTupleLiterals); + } + + entryTypeResult.type = stripTypeForm( + convertSpecialFormToRuntimeValue(entryTypeResult.type, flags, !hasExpectedType) + ); + + if (entryTypeResult.isIncomplete) { + isIncomplete = true; + } + + if (entryTypeResult.typeErrors) { + typeErrors = true; + } + + if (hasExpectedType || index < maxEntriesToUseForInference) { + entryTypes.push(entryTypeResult.type); + } + + if (verifyHashable && !entryTypeResult.isIncomplete && !entryTypeResult.typeErrors) { + verifySetEntryOrDictKeyIsHashable(entry, entryTypeResult.type, /* isDictKey */ false); + } + }); + + entryTypes = entryTypes.map((t) => stripLiteralValue(t)); + + let inferredEntryType: Type = hasExpectedType ? AnyType.create() : UnknownType.create(); + if (entryTypes.length > 0) { + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + // If there was an expected type or we're using strict list inference, + // combine the types into a union. + if ( + (builtInClassName === 'list' && fileInfo.diagnosticRuleSet.strictListInference) || + (builtInClassName === 'set' && fileInfo.diagnosticRuleSet.strictSetInference) || + hasExpectedType + ) { + inferredEntryType = combineTypes(entryTypes, { maxSubtypeCount: maxSubtypesForInferredType }); + } else { + // Is the list or set homogeneous? If so, use stricter rules. Otherwise relax the rules. + inferredEntryType = areTypesSame(entryTypes, { ignorePseudoGeneric: true }) + ? entryTypes[0] + : inferredEntryType; + } + } else { + isEmptyContainer = true; + } + + const listOrSetClass = getBuiltInType(node, builtInClassName); + const type = isInstantiableClass(listOrSetClass) + ? ClassType.cloneAsInstance( + ClassType.specialize( + listOrSetClass, + [inferredEntryType], + /* isTypeArgExplicit */ true, + /* includeSubclasses */ undefined, + /* tupleTypeArgs */ undefined, + isEmptyContainer + ) + ) + : UnknownType.create(); + + if (isIncomplete) { + if (getContainerDepth(type) > maxInferredContainerDepth) { + return { type: UnknownType.create() }; + } + } + + return { type, isIncomplete, typeErrors }; + } + + function verifySetEntryOrDictKeyIsHashable(entry: ExpressionNode, type: Type, isDictKey: boolean) { + // Verify that the type is hashable. + if (!isTypeHashable(type)) { + const diag = new DiagnosticAddendum(); + diag.addMessage(LocAddendum.unhashableType().format({ type: printType(type) })); + + const message = isDictKey ? LocMessage.unhashableDictKey() : LocMessage.unhashableSetEntry(); + + addDiagnostic(DiagnosticRule.reportUnhashable, message + diag.getString(), entry); + } + } + + function inferTypeArgFromExpectedEntryType( + inferenceContext: InferenceContext, + entryTypes: Type[], + isNarrowable: boolean + ): Type | undefined { + // If the expected type is Any, the resulting type becomes Any. + if (isAny(inferenceContext.expectedType)) { + return inferenceContext.expectedType; + } + + const constraints = new ConstraintTracker(); + const expectedType = inferenceContext.expectedType; + let isCompatible = true; + + entryTypes.forEach((entryType) => { + if (isCompatible && !assignType(expectedType, entryType, /* diag */ undefined, constraints)) { + isCompatible = false; + } + }); + + if (!isCompatible) { + return undefined; + } + + if (isNarrowable && entryTypes.length > 0) { + const combinedTypes = combineTypes(entryTypes); + return containsLiteralType(inferenceContext.expectedType) + ? combinedTypes + : stripLiteralValue(combinedTypes); + } + + return mapSubtypes( + solveAndApplyConstraints(inferenceContext.expectedType, constraints, { + replaceUnsolved: { + scopeIds: [], + tupleClassType: getTupleClassType(), + }, + }), + (subtype) => { + if (entryTypes.length !== 1) { + return subtype; + } + const entryType = entryTypes[0]; + + // If the entry type is a TypedDict instance, clone it with additional information. + if ( + isTypeSame(subtype, entryType, { ignoreTypedDictNarrowEntries: true }) && + isClass(subtype) && + isClass(entryType) && + ClassType.isTypedDictClass(entryType) + ) { + return ClassType.cloneForNarrowedTypedDictEntries(subtype, entryType.priv.typedDictNarrowedEntries); + } + + return subtype; + } + ); + } + + function getTypeOfYield(node: YieldNode): TypeResult { + let expectedYieldType: Type | undefined; + let sentType: Type | undefined; + let isIncomplete = false; + + const enclosingFunction = ParseTreeUtils.getEnclosingFunction(node); + if (enclosingFunction) { + const functionTypeInfo = getTypeOfFunction(enclosingFunction); + if (functionTypeInfo) { + let returnType = FunctionType.getEffectiveReturnType(functionTypeInfo.functionType); + if (returnType) { + const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(node); + returnType = makeTypeVarsBound(returnType, liveScopeIds); + + expectedYieldType = getGeneratorYieldType(returnType, !!enclosingFunction.d.isAsync); + + const generatorTypeArgs = getGeneratorTypeArgs(returnType); + if (generatorTypeArgs && generatorTypeArgs.length >= 2) { + sentType = makeTypeVarsBound(generatorTypeArgs[1], liveScopeIds); + } + } + } + } + + if (node.d.expr) { + const exprResult = getTypeOfExpression( + node.d.expr, + /* flags */ undefined, + makeInferenceContext(expectedYieldType) + ); + if (exprResult.isIncomplete) { + isIncomplete = true; + } + } + + return { type: sentType || UnknownType.create(), isIncomplete }; + } + + function getTypeOfYieldFrom(node: YieldFromNode): TypeResult { + const yieldFromTypeResult = getTypeOfExpression(node.d.expr); + const yieldFromType = yieldFromTypeResult.type; + + const returnedType = mapSubtypes(yieldFromType, (yieldFromSubtype) => { + // Is the expression a Generator type? + let generatorTypeArgs = getGeneratorTypeArgs(yieldFromSubtype); + if (generatorTypeArgs) { + return generatorTypeArgs.length >= 2 ? generatorTypeArgs[2] : UnknownType.create(); + } + + // Handle old-style (pre-await) Coroutines as a special case. + if ( + isClassInstance(yieldFromSubtype) && + ClassType.isBuiltIn(yieldFromSubtype, ['Coroutine', 'CoroutineType']) + ) { + return UnknownType.create(); + } + + // Handle simple iterables. + const iterableType = + getTypeOfIterable(yieldFromTypeResult, /* isAsync */ false, node)?.type ?? UnknownType.create(); + + // Does the iterable return a Generator? + generatorTypeArgs = getGeneratorTypeArgs(iterableType); + return generatorTypeArgs && generatorTypeArgs.length >= 2 ? generatorTypeArgs[2] : UnknownType.create(); + }); + + return { type: returnedType }; + } + + function getTypeOfLambda(node: LambdaNode, inferenceContext: InferenceContext | undefined): TypeResult { + let expectedFunctionTypes: FunctionType[] = []; + if (inferenceContext) { + mapSubtypes(inferenceContext.expectedType, (subtype) => { + if (isFunction(subtype)) { + expectedFunctionTypes.push(subtype); + } + + if (isClassInstance(subtype)) { + const boundMethod = getBoundMagicMethod(subtype, '__call__'); + if (boundMethod && isFunction(boundMethod)) { + expectedFunctionTypes.push(boundMethod as FunctionType); + } + } + + return undefined; + }); + } + + let expectedSubtype: FunctionType | undefined; + + // If there's more than one type, try each in turn until we find one that works. + if (expectedFunctionTypes.length > 1) { + // Sort the expected types for deterministic results. + expectedFunctionTypes = sortTypes(expectedFunctionTypes) as FunctionType[]; + + for (const subtype of expectedFunctionTypes) { + const result = getTypeOfLambdaWithExpectedType( + node, + subtype, + inferenceContext, + /* forceSpeculative */ true + ); + + if (!result.typeErrors) { + expectedSubtype = subtype; + break; + } + } + } + + if (!expectedSubtype && expectedFunctionTypes.length > 0) { + expectedSubtype = expectedFunctionTypes[0]; + } + + return getTypeOfLambdaWithExpectedType(node, expectedSubtype, inferenceContext, /* forceSpeculative */ false); + } + + function getTypeOfLambdaWithExpectedType( + node: LambdaNode, + expectedType: FunctionType | undefined, + inferenceContext: InferenceContext | undefined, + forceSpeculative: boolean + ): TypeResult { + let isIncomplete = !!inferenceContext?.isTypeIncomplete; + let paramsArePositionOnly = true; + + let expectedReturnType: Type | undefined; + let expectedParamDetails: ParamListDetails | undefined; + + if (expectedType) { + const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(node); + expectedType = transformExpectedType(expectedType, liveTypeVarScopes, node.start) as FunctionType; + + expectedParamDetails = getParamListDetails(expectedType); + expectedReturnType = getEffectiveReturnType(expectedType); + } + + let functionType = FunctionType.createInstance('', '', '', FunctionTypeFlags.PartiallyEvaluated); + functionType.shared.typeVarScopeId = ParseTreeUtils.getScopeIdForNode(node); + + return invalidateTypeCacheIfCanceled(() => { + // Pre-cache the incomplete function type in case the evaluation of the + // lambda depends on itself. + writeTypeCache(node, { type: functionType, isIncomplete: true }, EvalFlags.None); + + // We assume for simplicity that the parameter signature of the lambda is + // the same as the expected type. If this isn't the case, we'll use + // object for any lambda parameters that don't match. We could make this + // more sophisticated in the future, but it becomes very complex to handle + // all of the permutations. + let sawParamMismatch = false; + + node.d.params.forEach((param, index) => { + let paramType: Type | undefined; + + if (expectedParamDetails && !sawParamMismatch) { + if (index < expectedParamDetails.params.length) { + const expectedParam = expectedParamDetails.params[index]; + + // If the parameter category matches and both of the parameters are + // either separators (/ or *) or not separators, copy the type + // from the expected parameter. + if ( + expectedParam.param.category === param.d.category && + !param.d.name === !expectedParam.param.name + ) { + paramType = expectedParam.type; + } else { + sawParamMismatch = true; + } + } else if (param.d.defaultValue) { + // If the lambda param has a default value but there is no associated + // parameter in the expected type, assume that the default value is + // being used to explicitly capture a value from an outer scope. Infer + // its type from the default value expression. + paramType = getTypeOfExpression(param.d.defaultValue, undefined, inferenceContext).type; + } + } else if (param.d.defaultValue) { + // If there is no inference context but we have a default value, + // use the default value to infer the parameter's type. + paramType = inferParamTypeFromDefaultValue(param.d.defaultValue); + } + + if (param.d.name) { + writeTypeCache( + param.d.name, + { + type: transformVariadicParamType(node, param.d.category, paramType ?? UnknownType.create()), + }, + EvalFlags.None + ); + } + + if (param.d.defaultValue) { + // Evaluate the default value if it's present. + getTypeOfExpression(param.d.defaultValue, EvalFlags.ConvertEllipsisToAny); + } + + // Determine whether we need to insert an implied position-only parameter. + // This is needed when a function's parameters are named using the old-style + // way of specifying position-only parameters. + if (index >= 0) { + let isImplicitPositionOnlyParam = false; + + if (param.d.category === ParamCategory.Simple && param.d.name) { + if (isPrivateName(param.d.name.d.value)) { + isImplicitPositionOnlyParam = true; + } + } else { + paramsArePositionOnly = false; + } + + if ( + paramsArePositionOnly && + !isImplicitPositionOnlyParam && + functionType.shared.parameters.length > 0 + ) { + FunctionType.addPositionOnlyParamSeparator(functionType); + } + + if (!isImplicitPositionOnlyParam) { + paramsArePositionOnly = false; + } + } + + const functionParam = FunctionParam.create( + param.d.category, + paramType ?? UnknownType.create(), + FunctionParamFlags.TypeDeclared, + param.d.name ? param.d.name.d.value : undefined, + param.d.defaultValue ? AnyType.create(/* isEllipsis */ true) : undefined, + param.d.defaultValue + ); + + FunctionType.addParam(functionType, functionParam); + }); + + if (paramsArePositionOnly && functionType.shared.parameters.length > 0) { + FunctionType.addPositionOnlyParamSeparator(functionType); + } + + let typeErrors = false; + + // If we're speculatively evaluating the lambda, create another speculative + // evaluation scope for the return expression and do not allow retention + // of the cached types. + // We need to set allowCacheRetention to false because we don't want to + // cache the type of the lambda return expression because it depends on + // the parameter types that we set above, and the speculative type cache + // doesn't know about that context. + useSpeculativeMode( + forceSpeculative || isSpeculativeModeInUse(node) || inferenceContext?.isTypeIncomplete + ? node.d.expr + : undefined, + () => { + const returnTypeResult = getTypeOfExpression( + node.d.expr, + /* flags */ undefined, + makeInferenceContext(expectedReturnType) + ); + + functionType.shared.inferredReturnType = { + type: returnTypeResult.type, + }; + if (returnTypeResult.isIncomplete) { + isIncomplete = true; + } + + if (returnTypeResult.typeErrors) { + typeErrors = true; + } else if (expectedReturnType) { + // If the expectedReturnType is generic, see if the actual return type + // provides types for some or all type variables. + if (requiresSpecialization(expectedReturnType)) { + const constraints = new ConstraintTracker(); + if ( + assignType(expectedReturnType, returnTypeResult.type, /* diag */ undefined, constraints) + ) { + functionType = solveAndApplyConstraints(functionType, constraints, { + replaceUnsolved: { + scopeIds: [], + tupleClassType: getTupleClassType(), + }, + }) as FunctionType; + } + } + } + }, + { + dependentType: inferenceContext?.expectedType, + allowDiagnostics: + !forceSpeculative && !canSkipDiagnosticForNode(node) && !inferenceContext?.isTypeIncomplete, + } + ); + + // Mark the function type as no longer being evaluated. + functionType.shared.flags &= ~FunctionTypeFlags.PartiallyEvaluated; + + // Is the resulting function compatible with the expected type? + if (expectedType && !assignType(expectedType, functionType)) { + typeErrors = true; + } + + return { type: functionType, isIncomplete, typeErrors }; + }); + } + + function getTypeOfComprehension( + node: ComprehensionNode, + flags: EvalFlags, + inferenceContext?: InferenceContext + ): TypeResult { + let isIncomplete = false; + let typeErrors = false; + + // If any of the "for" clauses are marked async or any of the "if" clauses + // or any clause other than the leftmost "for" contain an "await" operator, + // it is treated as an async generator. + let isAsync = node.d.forIfNodes.some((comp, index) => { + if (comp.nodeType === ParseNodeType.ComprehensionFor && comp.d.isAsync) { + return true; + } + return index > 0 && ParseTreeUtils.containsAwaitNode(comp); + }); + let type: Type = UnknownType.create(); + + if (ParseTreeUtils.containsAwaitNode(node.d.expr)) { + isAsync = true; + } + + const builtInIteratorType = getTypingType(node, isAsync ? 'AsyncGenerator' : 'Generator'); + + const expectedEntryType = getExpectedEntryTypeForIterable(node, builtInIteratorType, inferenceContext); + const elementTypeResult = getElementTypeFromComprehension( + node, + flags | EvalFlags.StripTupleLiterals, + expectedEntryType + ); + + if (elementTypeResult.isIncomplete) { + isIncomplete = true; + } + + if (elementTypeResult.typeErrors) { + typeErrors = true; + } + + let elementType = elementTypeResult.type; + if (!expectedEntryType || !containsLiteralType(expectedEntryType)) { + elementType = stripLiteralValue(elementType); + } + + if (builtInIteratorType && isInstantiableClass(builtInIteratorType)) { + type = ClassType.cloneAsInstance( + ClassType.specialize( + builtInIteratorType, + isAsync ? [elementType, getNoneType()] : [elementType, getNoneType(), getNoneType()] + ) + ); + } + + return { type, isIncomplete, typeErrors }; + } + + function reportPossibleUnknownAssignment( + diagLevel: DiagnosticLevel, + rule: DiagnosticRule, + target: NameNode, + type: Type, + errorNode: ExpressionNode, + ignoreEmptyContainers: boolean + ) { + // Don't bother if the feature is disabled. + if (diagLevel === 'none') { + return; + } + + const nameValue = target.d.value; + + // Sometimes variables contain an "unbound" type if they're + // assigned only within conditional statements. Remove this + // to avoid confusion. + const simplifiedType = removeUnbound(type); + + if (isUnknown(simplifiedType)) { + addDiagnostic(rule, LocMessage.typeUnknown().format({ name: nameValue }), errorNode); + } else if (isPartlyUnknown(simplifiedType)) { + // If ignoreEmptyContainers is true, don't report the problem for + // empty containers (lists or dictionaries). We'll report the problem + // only if the assigned value is used later. + if (!ignoreEmptyContainers || !isClassInstance(type) || !type.priv.isEmptyContainer) { + const diagAddendum = new DiagnosticAddendum(); + diagAddendum.addMessage( + LocAddendum.typeOfSymbol().format({ + name: nameValue, + type: printType(simplifiedType, { expandTypeAlias: true }), + }) + ); + addDiagnostic( + rule, + LocMessage.typePartiallyUnknown().format({ name: nameValue }) + diagAddendum.getString(), + errorNode + ); + } + } + } + + function evaluateComprehensionForIf(node: ComprehensionForIfNode) { + let isIncomplete = false; + + if (node.nodeType === ParseNodeType.ComprehensionFor) { + const iterableTypeResult = getTypeOfExpression(node.d.iterableExpr); + if (iterableTypeResult.isIncomplete) { + isIncomplete = true; + } + const iterableType = stripLiteralValue(iterableTypeResult.type); + const itemTypeResult = getTypeOfIterator( + { type: iterableType, isIncomplete: iterableTypeResult.isIncomplete }, + !!node.d.isAsync, + node.d.iterableExpr + ) ?? { type: UnknownType.create(), isIncomplete: iterableTypeResult.isIncomplete }; + + const targetExpr = node.d.targetExpr; + assignTypeToExpression(targetExpr, itemTypeResult, node.d.iterableExpr); + } else { + assert(node.nodeType === ParseNodeType.ComprehensionIf); + + // Evaluate the test expression to validate it and mark symbols + // as referenced. This doesn't affect the type of the evaluated + // comprehension, but it is important for evaluating intermediate + // expressions such as assignment expressions that can affect other + // subexpressions. + getTypeOfExpression(node.d.testExpr); + } + + return isIncomplete; + } + + // Returns the type of one entry returned by the comprehension. + function getElementTypeFromComprehension( + node: ComprehensionNode, + flags: EvalFlags, + expectedValueOrElementType?: Type, + expectedKeyType?: Type + ): TypeResult { + let isIncomplete = false; + let typeErrors = false; + + // "Execute" the list comprehensions from start to finish. + for (const forIfNode of node.d.forIfNodes) { + if (evaluateComprehensionForIf(forIfNode)) { + isIncomplete = true; + } + } + + let type: Type = UnknownType.create(); + if (node.d.expr.nodeType === ParseNodeType.DictionaryKeyEntry) { + // Create a tuple with the key/value types. + const keyTypeResult = getTypeOfExpression( + node.d.expr.d.keyExpr, + flags, + makeInferenceContext(expectedKeyType) + ); + if (keyTypeResult.isIncomplete) { + isIncomplete = true; + } + if (keyTypeResult.typeErrors) { + typeErrors = true; + } + let keyType = keyTypeResult.type; + if (!expectedKeyType || !containsLiteralType(expectedKeyType)) { + keyType = stripLiteralValue(keyType); + } + + const valueTypeResult = getTypeOfExpression( + node.d.expr.d.valueExpr, + flags, + makeInferenceContext(expectedValueOrElementType) + ); + if (valueTypeResult.isIncomplete) { + isIncomplete = true; + } + if (valueTypeResult.typeErrors) { + typeErrors = true; + } + let valueType = valueTypeResult.type; + if (!expectedValueOrElementType || !containsLiteralType(expectedValueOrElementType)) { + valueType = stripLiteralValue(valueType); + } + + type = makeTupleObject(evaluatorInterface, [ + { type: keyType, isUnbounded: false }, + { type: valueType, isUnbounded: false }, + ]); + } else if (node.d.expr.nodeType === ParseNodeType.DictionaryExpandEntry) { + // The parser should have reported an error in this case because it's not allowed. + getTypeOfExpression(node.d.expr.d.expr, flags, makeInferenceContext(expectedValueOrElementType)); + } else if (isExpressionNode(node)) { + const exprTypeResult = getTypeOfExpression( + node.d.expr as ExpressionNode, + flags, + makeInferenceContext(expectedValueOrElementType) + ); + if (exprTypeResult.isIncomplete) { + isIncomplete = true; + } + if (exprTypeResult.typeErrors) { + typeErrors = true; + } + type = exprTypeResult.type; + } + + return { type, isIncomplete, typeErrors }; + } + + function getTypeOfSlice(node: SliceNode): TypeResult { + const noneType = getNoneType(); + let startType = noneType; + let endType = noneType; + let stepType = noneType; + let isIncomplete = false; + + // Evaluate the expressions to report errors and record symbol + // references. + if (node.d.startValue) { + const startTypeResult = getTypeOfExpression(node.d.startValue); + startType = startTypeResult.type; + if (startTypeResult.isIncomplete) { + isIncomplete = true; + } + } + + if (node.d.endValue) { + const endTypeResult = getTypeOfExpression(node.d.endValue); + endType = endTypeResult.type; + if (endTypeResult.isIncomplete) { + isIncomplete = true; + } + } + + if (node.d.stepValue) { + const stepTypeResult = getTypeOfExpression(node.d.stepValue); + stepType = stepTypeResult.type; + if (stepTypeResult.isIncomplete) { + isIncomplete = true; + } + } + + const sliceType = getBuiltInObject(node, 'slice'); + + if (!isClassInstance(sliceType)) { + return { type: sliceType }; + } + + return { type: ClassType.specialize(sliceType, [startType, endType, stepType]), isIncomplete }; + } + + // Verifies that a type argument's type is not disallowed. + function validateTypeArg(argResult: TypeResultWithNode, options?: ValidateTypeArgsOptions): boolean { + if (argResult.typeList) { + if (!options?.allowTypeArgList) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeArgListNotAllowed(), argResult.node); + return false; + } else { + argResult.typeList.forEach((typeArg) => { + validateTypeArg(typeArg); + }); + } + } + + if (isEllipsisType(argResult.type)) { + if (!options?.allowTypeArgList) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.ellipsisContext(), argResult.node); + return false; + } + } + + if (isModule(argResult.type)) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.moduleAsType(), argResult.node); + return false; + } + + if (isParamSpec(argResult.type)) { + if (!options?.allowParamSpec) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.paramSpecContext(), argResult.node); + return false; + } + } + + if (isTypeVarTuple(argResult.type) && !argResult.type.priv.isInUnion) { + if (!options?.allowTypeVarTuple) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeVarTupleContext(), argResult.node); + return false; + } else { + validateTypeVarTupleIsUnpacked(argResult.type, argResult.node); + } + } + + if (!options?.allowEmptyTuple && argResult.isEmptyTupleShorthand) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.zeroLengthTupleNotAllowed(), argResult.node); + return false; + } + + if (isUnpackedClass(argResult.type)) { + if (!options?.allowUnpackedTuples) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.unpackedArgInTypeArgument(), + argResult.node + ); + return false; + } + } + + return true; + } + + // Evaluates the type arguments for a Callable type. It should have zero + // to two arguments.The first argument, if present, should be an ellipsis, + // a ParamSpec, a Concatenate, or a list of positional parameter types. + // The second argument, if present, should specify the return type. + function createCallableType( + classType: ClassType, + typeArgs: TypeResultWithNode[] | undefined, + errorNode: ParseNode + ): FunctionType { + let functionType = FunctionType.createInstantiable(FunctionTypeFlags.None); + let paramSpec: ParamSpecType | undefined; + let isValidTypeForm = true; + + TypeBase.setSpecialForm(functionType, ClassType.cloneAsInstance(classType)); + functionType.shared.declaredReturnType = UnknownType.create(); + functionType.shared.typeVarScopeId = ParseTreeUtils.getScopeIdForNode(errorNode); + + if (typeArgs && typeArgs.length > 0) { + functionType.priv.isCallableWithTypeArgs = true; + + if (typeArgs[0].typeList) { + const typeList = typeArgs[0].typeList; + let sawUnpacked = false; + let reportedUnpackedError = false; + const noteSawUnpacked = (entry: TypeResultWithNode) => { + // Make sure we have at most one unpacked TypeVarTuple. + if (sawUnpacked) { + if (!reportedUnpackedError) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.variadicTypeArgsTooMany(), + entry.node + ); + reportedUnpackedError = true; + isValidTypeForm = false; + } + } + sawUnpacked = true; + }; + + typeList.forEach((entry, index) => { + let entryType = entry.type; + let paramCategory: ParamCategory = ParamCategory.Simple; + const paramName = `__p${index.toString()}`; + + if (isTypeVarTuple(entryType)) { + validateTypeVarTupleIsUnpacked(entryType, entry.node); + paramCategory = ParamCategory.ArgsList; + noteSawUnpacked(entry); + } else if (validateTypeArg(entry, { allowUnpackedTuples: true })) { + if (isUnpackedClass(entryType)) { + paramCategory = ParamCategory.ArgsList; + + if ( + entryType.priv.tupleTypeArgs?.some( + (typeArg) => isTypeVarTuple(typeArg.type) || typeArg.isUnbounded + ) + ) { + noteSawUnpacked(entry); + } + } + } else { + entryType = UnknownType.create(); + } + + FunctionType.addParam( + functionType, + FunctionParam.create( + paramCategory, + convertToInstance(entryType), + FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared, + paramName + ) + ); + }); + + if (typeList.length > 0) { + FunctionType.addPositionOnlyParamSeparator(functionType); + } + } else if (isEllipsisType(typeArgs[0].type)) { + FunctionType.addDefaultParams(functionType); + functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm; + } else if (isParamSpec(typeArgs[0].type)) { + paramSpec = typeArgs[0].type; + } else { + if (isInstantiableClass(typeArgs[0].type) && ClassType.isBuiltIn(typeArgs[0].type, 'Concatenate')) { + const concatTypeArgs = typeArgs[0].type.priv.typeArgs; + if (concatTypeArgs && concatTypeArgs.length > 0) { + concatTypeArgs.forEach((typeArg, index) => { + if (index === concatTypeArgs.length - 1) { + FunctionType.addPositionOnlyParamSeparator(functionType); + + if (isParamSpec(typeArg)) { + paramSpec = typeArg; + } else if (isEllipsisType(typeArg)) { + FunctionType.addDefaultParams(functionType); + functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm; + } + } else { + FunctionType.addParam( + functionType, + FunctionParam.create( + ParamCategory.Simple, + typeArg, + FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared, + `__p${index}` + ) + ); + } + }); + } + } else { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.callableFirstArg(), + typeArgs[0].node + ); + isValidTypeForm = false; + } + } + + if (typeArgs.length > 1) { + let typeArg1Type = typeArgs[1].type; + if (!validateTypeArg(typeArgs[1])) { + typeArg1Type = UnknownType.create(); + } + functionType.shared.declaredReturnType = convertToInstance(typeArg1Type); + } else { + addDiagnostic(DiagnosticRule.reportMissingTypeArgument, LocMessage.callableSecondArg(), errorNode); + + functionType.shared.declaredReturnType = UnknownType.create(); + isValidTypeForm = false; + } + + if (typeArgs.length > 2) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.callableExtraArgs(), typeArgs[2].node); + isValidTypeForm = false; + } + } else { + FunctionType.addDefaultParams(functionType, /* useUnknown */ true); + functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm; + + if (typeArgs && typeArgs.length === 0) { + isValidTypeForm = false; + } + } + + if (paramSpec) { + FunctionType.addParamSpecVariadics(functionType, convertToInstance(paramSpec)); + } + + if (isValidTypeForm) { + functionType = TypeBase.cloneWithTypeForm(functionType, convertToInstance(functionType)); + } + + return functionType; + } + + // Creates an Optional[X] type. + function createOptionalType( + classType: ClassType, + errorNode: ParseNode, + typeArgs: TypeResultWithNode[] | undefined, + flags: EvalFlags + ): Type { + if (!typeArgs) { + // If no type arguments are provided, the resulting type + // depends on whether we're evaluating a type annotation or + // we're in some other context. + if ((flags & EvalFlags.TypeExpression) !== 0) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.optionalExtraArgs(), errorNode); + return UnknownType.create(); + } + + return classType; + } + + if (typeArgs.length !== 1) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.optionalExtraArgs(), errorNode); + return UnknownType.create(); + } + + let typeArg0Type = typeArgs[0].type; + if (!validateTypeArg(typeArgs[0])) { + typeArg0Type = UnknownType.create(); + } + + let optionalType = combineTypes([typeArg0Type, prefetched?.noneTypeClass ?? UnknownType.create()]); + if (prefetched?.unionTypeClass && isInstantiableClass(prefetched.unionTypeClass)) { + optionalType = TypeBase.cloneAsSpecialForm( + optionalType, + ClassType.cloneAsInstance(prefetched.unionTypeClass) + ); + } + + if (typeArg0Type.props?.typeForm) { + const typeFormType = combineTypes([ + typeArg0Type.props.typeForm, + convertToInstance(prefetched?.noneTypeClass ?? UnknownType.create()), + ]); + optionalType = TypeBase.cloneWithTypeForm(optionalType, typeFormType); + } + + return optionalType; + } + + function cloneBuiltinObjectWithLiteral(node: ParseNode, builtInName: string, value: LiteralValue): Type { + const type = getBuiltInObject(node, builtInName); + if (isClassInstance(type)) { + return ClassType.cloneWithLiteral(ClassType.cloneRemoveTypePromotions(type), value); + } + + return UnknownType.create(); + } + + function cloneBuiltinClassWithLiteral( + node: ParseNode, + literalClassType: ClassType, + builtInName: string, + value: LiteralValue + ): Type { + const type = getBuiltInType(node, builtInName); + if (isInstantiableClass(type)) { + const literalType = ClassType.cloneWithLiteral(type, value); + TypeBase.setSpecialForm(literalType, literalClassType); + return literalType; + } + + return UnknownType.create(); + } + + // Creates a type that represents a Literal. + function createLiteralType(classType: ClassType, node: IndexNode, flags: EvalFlags): Type { + if (node.d.items.length === 0) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.literalEmptyArgs(), node.d.leftExpr); + return UnknownType.create(); + } + + // As per the specification, we support None, int, bool, str, bytes literals + // plus enum values. + const literalTypes: Type[] = []; + let isValidTypeForm = true; + + for (const item of node.d.items) { + let type: Type | undefined; + const itemExpr = item.d.valueExpr; + + if (item.d.argCategory !== ArgCategory.Simple) { + if ((flags & EvalFlags.TypeExpression) !== 0) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.unpackedArgInTypeArgument(), + itemExpr + ); + type = UnknownType.create(); + isValidTypeForm = false; + } + } else if (item.d.name) { + if ((flags & EvalFlags.TypeExpression) !== 0) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.keywordArgInTypeArgument(), + itemExpr + ); + type = UnknownType.create(); + isValidTypeForm = false; + } + } else if (itemExpr.nodeType === ParseNodeType.StringList) { + const isBytes = (itemExpr.d.strings[0].d.token.flags & StringTokenFlags.Bytes) !== 0; + const value = itemExpr.d.strings.map((s) => s.d.value).join(''); + if (isBytes) { + type = cloneBuiltinClassWithLiteral(node, classType, 'bytes', value); + } else { + type = cloneBuiltinClassWithLiteral(node, classType, 'str', value); + } + + if ((flags & EvalFlags.TypeExpression) !== 0) { + itemExpr.d.strings.forEach((stringNode) => { + if ((stringNode.d.token.flags & StringTokenFlags.NamedUnicodeEscape) !== 0) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.literalNamedUnicodeEscape(), + stringNode + ); + isValidTypeForm = false; + } + }); + } + } else if (itemExpr.nodeType === ParseNodeType.Number) { + if (!itemExpr.d.isImaginary && itemExpr.d.isInteger) { + type = cloneBuiltinClassWithLiteral(node, classType, 'int', itemExpr.d.value); + } + } else if (itemExpr.nodeType === ParseNodeType.Constant) { + if (itemExpr.d.constType === KeywordType.True) { + type = cloneBuiltinClassWithLiteral(node, classType, 'bool', true); + } else if (itemExpr.d.constType === KeywordType.False) { + type = cloneBuiltinClassWithLiteral(node, classType, 'bool', false); + } else if (itemExpr.d.constType === KeywordType.None) { + type = prefetched?.noneTypeClass ?? UnknownType.create(); + } + } else if (itemExpr.nodeType === ParseNodeType.UnaryOperation) { + if (itemExpr.d.operator === OperatorType.Subtract || itemExpr.d.operator === OperatorType.Add) { + if (itemExpr.d.expr.nodeType === ParseNodeType.Number) { + if (!itemExpr.d.expr.d.isImaginary && itemExpr.d.expr.d.isInteger) { + type = cloneBuiltinClassWithLiteral( + node, + classType, + 'int', + itemExpr.d.operator === OperatorType.Subtract + ? -itemExpr.d.expr.d.value + : itemExpr.d.expr.d.value + ); + } + } + } + } + + if (!type) { + const exprType = getTypeOfExpression( + itemExpr, + (flags & (EvalFlags.ForwardRefs | EvalFlags.TypeExpression)) | EvalFlags.NoConvertSpecialForm + ); + + // Is this an enum type? + if ( + isClassInstance(exprType.type) && + ClassType.isEnumClass(exprType.type) && + exprType.type.priv.literalValue !== undefined + ) { + type = ClassType.cloneAsInstantiable(exprType.type); + } else { + // Is this a type alias to an existing literal type? + let isLiteralType = true; + + doForEachSubtype(exprType.type, (subtype) => { + if (!isInstantiableClass(subtype) || subtype.priv.literalValue === undefined) { + if (!isNoneTypeClass(subtype)) { + isLiteralType = false; + } + } + }); + + if (isLiteralType) { + type = exprType.type; + } + } + } + + if (!type) { + if ((flags & EvalFlags.TypeExpression) !== 0) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.literalUnsupportedType(), item); + type = UnknownType.create(); + isValidTypeForm = false; + } else { + return ClassType.cloneAsInstance(classType); + } + } + + literalTypes.push(type); + } + + let result = combineTypes(literalTypes, { skipElideRedundantLiterals: true }); + + if (isUnion(result) && prefetched?.unionTypeClass && isInstantiableClass(prefetched.unionTypeClass)) { + result = TypeBase.cloneAsSpecialForm(result, ClassType.cloneAsInstance(prefetched.unionTypeClass)); + } + + if (isValidTypeForm) { + result = TypeBase.cloneWithTypeForm(result, convertToInstance(result)); + } + + return result; + } + + // Creates a ClassVar type. + function createClassVarType( + classType: ClassType, + errorNode: ParseNode, + typeArgs: TypeResultWithNode[] | undefined, + flags: EvalFlags + ): Type { + if (flags & EvalFlags.NoClassVar) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.classVarNotAllowed(), errorNode); + return AnyType.create(); + } + + if (!typeArgs) { + return classType; + } else if (typeArgs.length === 0) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.classVarFirstArgMissing(), errorNode); + return UnknownType.create(); + } else if (typeArgs.length > 1) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.classVarTooManyArgs(), typeArgs[1].node); + return UnknownType.create(); + } + + const type = typeArgs[0].type; + + // A ClassVar should not allow TypeVars or generic types parameterized + // by TypeVars. + if (requiresSpecialization(type, { ignorePseudoGeneric: true, ignoreSelf: true })) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.classVarWithTypeVar(), + typeArgs[0].node ?? errorNode + ); + } + + return type; + } + + function createTypeFormType( + classType: ClassType, + errorNode: ExpressionNode, + typeArgs: TypeResultWithNode[] | undefined + ): Type { + if (!typeArgs || typeArgs.length === 0) { + return ClassType.specialize(classType, [UnknownType.create()]); + } + + if (typeArgs.length > 1) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.typeArgsTooMany().format({ + name: classType.priv.aliasName || classType.shared.name, + expected: 1, + received: typeArgs.length, + }), + typeArgs[1].node + ); + return UnknownType.create(); + } + + const convertedTypeArgs = typeArgs.map((typeArg) => { + return convertToInstance(validateTypeArg(typeArg) ? typeArg.type : UnknownType.create()); + }); + let resultType = ClassType.specialize(classType, convertedTypeArgs); + + resultType = TypeBase.cloneWithTypeForm(resultType, convertToInstance(resultType)); + + return resultType; + } + + // Creates a "TypeGuard" and "TypeIs" type. + function createTypeGuardType( + classType: ClassType, + errorNode: ParseNode, + typeArgs: TypeResultWithNode[] | undefined, + flags: EvalFlags + ): Type { + // If no type arguments are provided, the resulting type + // depends on whether we're evaluating a type annotation or + // we're in some other context. + if (!typeArgs) { + if ((flags & EvalFlags.TypeExpression) !== 0) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeGuardArgCount(), errorNode); + } + + return classType; + } else if (typeArgs.length !== 1) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeGuardArgCount(), errorNode); + return UnknownType.create(); + } + + const convertedTypeArgs = typeArgs.map((typeArg) => { + return convertToInstance(validateTypeArg(typeArg) ? typeArg.type : UnknownType.create()); + }); + + let resultType = ClassType.specialize(classType, convertedTypeArgs); + + resultType = TypeBase.cloneWithTypeForm(resultType, convertToInstance(resultType)); + + return resultType; + } + + function createSelfType( + classType: ClassType, + errorNode: ParseNode, + typeArgs: TypeResultWithNode[] | undefined, + flags: EvalFlags + ) { + // Self doesn't support any type arguments. + if (typeArgs && typeArgs.length > 0) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeArguments, + LocMessage.typeArgsExpectingNone().format({ + name: classType.shared.name, + }), + typeArgs[0].node ?? errorNode + ); + } + + let enclosingClass = ParseTreeUtils.getEnclosingClass(errorNode); + + // If `Self` appears anywhere outside of the class body (e.g. a decorator, + // base class list, metaclass argument, type parameter list), it is + // considered illegal. + if (enclosingClass && !ParseTreeUtils.isNodeContainedWithin(errorNode, enclosingClass.d.suite)) { + enclosingClass = undefined; + } + + const enclosingClassTypeResult = enclosingClass ? getTypeOfClass(enclosingClass) : undefined; + if (!enclosingClassTypeResult) { + if ((flags & (EvalFlags.TypeExpression | EvalFlags.InstantiableType)) !== 0) { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.selfTypeContext(), errorNode); + } + + return UnknownType.create(); + } else if (isInstantiableMetaclass(enclosingClassTypeResult.classType)) { + // If `Self` appears within a metaclass, it is considered illegal. + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.selfTypeMetaclass(), errorNode); + + return UnknownType.create(); + } + + const enclosingFunction = ParseTreeUtils.getEnclosingFunction(errorNode); + if (enclosingFunction) { + const functionInfo = getFunctionInfoFromDecorators( + evaluatorInterface, + enclosingFunction, + /* isInClass */ true + ); + + const isInnerFunction = !!ParseTreeUtils.getEnclosingFunction(enclosingFunction); + if (!isInnerFunction) { + // Check for static methods. + if (functionInfo.flags & FunctionTypeFlags.StaticMethod) { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.selfTypeContext(), errorNode); + + return UnknownType.create(); + } + + if (enclosingFunction.d.params.length > 0) { + const firstParamTypeAnnotation = ParseTreeUtils.getTypeAnnotationForParam(enclosingFunction, 0); + if ( + firstParamTypeAnnotation && + !ParseTreeUtils.isNodeContainedWithin(errorNode, firstParamTypeAnnotation) + ) { + const annotationType = getTypeOfAnnotation(firstParamTypeAnnotation, { + typeVarGetsCurScope: true, + }); + if (!isTypeVar(annotationType) || !TypeVarType.isSelf(annotationType)) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.selfTypeWithTypedSelfOrCls(), + errorNode + ); + } + } + } + } + } + + let result = synthesizeTypeVarForSelfCls(enclosingClassTypeResult.classType, /* isClsParam */ true); + + if (enclosingClass) { + // If "Self" is used as a type expression within a function suite, it needs + // to be marked as bound. + const enclosingSuite = ParseTreeUtils.getEnclosingClassOrFunctionSuite(errorNode); + + if (enclosingSuite && ParseTreeUtils.isNodeContainedWithin(enclosingSuite, enclosingClass)) { + if (enclosingClass.d.suite !== enclosingSuite) { + result = TypeVarType.cloneAsBound(result); + } + } + } + + return result; + } + + function createRequiredOrReadOnlyType( + classType: ClassType, + errorNode: ParseNode, + typeArgs: TypeResultWithNode[] | undefined, + flags: EvalFlags + ): TypeResult { + // If no type arguments are provided, the resulting type + // depends on whether we're evaluating a type annotation or + // we're in some other context. + if (!typeArgs && (flags & EvalFlags.TypeExpression) === 0) { + return { type: classType }; + } + + if (!typeArgs || typeArgs.length !== 1) { + if ((flags & EvalFlags.TypeExpression) !== 0) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + classType.shared.name === 'ReadOnly' + ? LocMessage.readOnlyArgCount() + : classType.shared.name === 'Required' + ? LocMessage.requiredArgCount() + : LocMessage.notRequiredArgCount(), + errorNode + ); + } + + return { type: classType }; + } + + const typeArgType = typeArgs[0].type; + + // Make sure this is used only in a dataclass. + const containingClassNode = ParseTreeUtils.getEnclosingClass(errorNode, /* stopAtFunction */ true); + const classTypeInfo = containingClassNode ? getTypeOfClass(containingClassNode) : undefined; + + let isUsageLegal = false; + + if ( + classTypeInfo && + isInstantiableClass(classTypeInfo.classType) && + ClassType.isTypedDictClass(classTypeInfo.classType) + ) { + // The only legal usage is when used in a type annotation statement. + if (ParseTreeUtils.isNodeContainedWithinNodeType(errorNode, ParseNodeType.TypeAnnotation)) { + isUsageLegal = true; + } + } + + let isReadOnly = typeArgs[0].isReadOnly; + let isRequired = typeArgs[0].isRequired; + let isNotRequired = typeArgs[0].isNotRequired; + + if (classType.shared.name === 'ReadOnly') { + if ((flags & EvalFlags.AllowReadOnly) !== 0) { + isUsageLegal = true; + } + + // Nested ReadOnly are not allowed. + if (typeArgs[0].isReadOnly) { + isUsageLegal = false; + } + + isReadOnly = true; + } else { + if ((flags & EvalFlags.AllowRequired) !== 0) { + isUsageLegal = true; + } + + // Nested Required/NotRequired are not allowed. + if (typeArgs[0].isRequired || typeArgs[0].isNotRequired) { + isUsageLegal = false; + } + + isRequired = classType.shared.name === 'Required'; + isNotRequired = classType.shared.name === 'NotRequired'; + } + + if (!isUsageLegal) { + if ((flags & EvalFlags.TypeExpression) !== 0) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + classType.shared.name === 'ReadOnly' + ? LocMessage.readOnlyNotInTypedDict() + : classType.shared.name === 'Required' + ? LocMessage.requiredNotInTypedDict() + : LocMessage.notRequiredNotInTypedDict(), + errorNode + ); + } + + return { type: classType }; + } + + return { type: typeArgType, isReadOnly, isRequired, isNotRequired }; + } + + function createUnpackType( + classType: ClassType, + errorNode: ParseNode, + typeArgs: TypeResultWithNode[] | undefined, + flags: EvalFlags + ): Type { + if (!typeArgs || typeArgs.length !== 1) { + if ((flags & EvalFlags.TypeExpression) !== 0) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.unpackArgCount(), errorNode); + } + return classType; + } + + const typeArgType = typeArgs[0].type; + + if ((flags & EvalFlags.AllowUnpackedTuple) !== 0) { + const unpackedType = applyUnpackToTupleLike(typeArgType); + if (unpackedType) { + return unpackedType; + } + + if ((flags & EvalFlags.TypeExpression) === 0) { + return classType; + } + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.unpackExpectedTypeVarTuple(), errorNode); + return UnknownType.create(); + } + + if ((flags & EvalFlags.AllowUnpackedTypedDict) !== 0) { + if (isInstantiableClass(typeArgType) && ClassType.isTypedDictClass(typeArgType)) { + return ClassType.cloneForUnpacked(typeArgType); + } + + if ((flags & EvalFlags.TypeExpression) === 0) { + return classType; + } + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.unpackExpectedTypedDict(), errorNode); + return UnknownType.create(); + } + + if ((flags & EvalFlags.TypeExpression) === 0) { + return classType; + } + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.unpackNotAllowed(), errorNode); + return UnknownType.create(); + } + + // Creates a "Final" type. + function createFinalType( + classType: ClassType, + errorNode: ParseNode, + typeArgs: TypeResultWithNode[] | undefined, + flags: EvalFlags + ): Type { + if (flags & EvalFlags.NoFinal) { + if ((flags & EvalFlags.TypeExpression) !== 0) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.finalContext(), errorNode); + } + return classType; + } + + if ((flags & EvalFlags.TypeExpression) === 0 || !typeArgs || typeArgs.length === 0) { + return classType; + } + + if (typeArgs.length > 1) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.finalTooManyArgs(), errorNode); + } + + return TypeBase.cloneAsSpecialForm(typeArgs[0].type, classType); + } + + function createConcatenateType( + classType: ClassType, + errorNode: ParseNode, + typeArgs: TypeResultWithNode[] | undefined, + flags: EvalFlags + ): Type { + if ((flags & EvalFlags.AllowConcatenate) === 0) { + if ((flags & EvalFlags.TypeExpression) !== 0) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.concatenateContext(), errorNode); + } + return classType; + } + + if (!typeArgs || typeArgs.length === 0) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.concatenateTypeArgsMissing(), errorNode); + } else { + typeArgs.forEach((typeArg, index) => { + if (index === typeArgs.length - 1) { + if (!isParamSpec(typeArg.type) && !isEllipsisType(typeArg.type)) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.concatenateParamSpecMissing(), + typeArg.node + ); + } + } else { + if (isParamSpec(typeArg.type)) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.paramSpecContext(), + typeArg.node + ); + } else if (isUnpackedTypeVarTuple(typeArg.type)) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.typeVarTupleContext(), + typeArg.node + ); + } else if (isUnpackedClass(typeArg.type)) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.unpackedArgInTypeArgument(), + typeArg.node + ); + } + } + }); + } + + return createSpecialType(classType, typeArgs, /* paramLimit */ undefined, /* allowParamSpec */ true); + } + + function createAnnotatedType( + classType: ClassType, + errorNode: ExpressionNode, + typeArgs: TypeResultWithNode[] | undefined, + flags: EvalFlags + ): TypeResult { + let type: Type | undefined; + + const typeExprFlags = EvalFlags.TypeExpression | EvalFlags.NoConvertSpecialForm; + if ((flags & typeExprFlags) === 0) { + type = ClassType.cloneAsInstance(classType); + + if (typeArgs && typeArgs.length >= 1 && typeArgs[0].type.props?.typeForm) { + type = TypeBase.cloneWithTypeForm(type, typeArgs[0].type.props.typeForm); + } + + return { type }; + } + + if (typeArgs && typeArgs.length > 0) { + type = typeArgs[0].type; + + if (typeArgs.length < 2) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.annotatedTypeArgMissing(), errorNode); + } else { + type = validateAnnotatedMetadata(errorNode, typeArgs[0].type, typeArgs.slice(1)); + } + } + + if (!type || !typeArgs || typeArgs.length === 0) { + return { type: AnyType.create() }; + } + + if (typeArgs[0].typeList) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeArgListNotAllowed(), typeArgs[0].node); + } + + return { + type: TypeBase.cloneAsSpecialForm(type, ClassType.cloneAsInstance(classType)), + isReadOnly: typeArgs[0].isReadOnly, + isRequired: typeArgs[0].isRequired, + isNotRequired: typeArgs[0].isNotRequired, + }; + } + + // Enforces metadata consistency as specified in PEP 746. + function validateAnnotatedMetadata( + errorNode: ExpressionNode, + baseType: Type, + metaArgs: TypeResultWithNode[] + ): Type { + for (const metaArg of metaArgs) { + validateTypeMetadata(errorNode, baseType, metaArg); + } + + return baseType; + } + + // Determines whether the metadata object is compatible with the base type. + function validateTypeMetadata(errorNode: ExpressionNode, baseType: Type, metaArg: TypeResultWithNode): boolean { + // This function was added for draft PEP 746, but the functionality + // has been removed for now while the PEP is being revised. + return true; + } + + // Creates one of several "special" types that are defined in typing.pyi + // but not declared in their entirety. This includes the likes of "Tuple", + // "Dict", etc. + function createSpecialType( + classType: ClassType, + typeArgs: TypeResultWithNode[] | undefined, + paramLimit?: number, + allowParamSpec = false, + isSpecialForm = true + ): Type { + const isTupleTypeParam = ClassType.isTupleClass(classType); + + if (typeArgs) { + if (isTupleTypeParam && typeArgs.length === 1 && typeArgs[0].isEmptyTupleShorthand) { + typeArgs = []; + } else { + let sawUnpacked = false; + const noteSawUnpacked = (typeArg: TypeResultWithNode) => { + if (sawUnpacked) { + if (!reportedUnpackedError) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.variadicTypeArgsTooMany(), + typeArg.node + ); + reportedUnpackedError = true; + } + } + sawUnpacked = true; + }; + let reportedUnpackedError = false; + + // Verify that we didn't receive any inappropriate types. + typeArgs.forEach((typeArg, index) => { + assert(typeArgs !== undefined); + if (isEllipsisType(typeArg.type)) { + if (!isTupleTypeParam) { + if (!allowParamSpec) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.ellipsisContext(), + typeArg.node + ); + } + } else if (typeArgs.length !== 2 || index !== 1) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.ellipsisSecondArg(), + typeArg.node + ); + } else { + if (isTypeVarTuple(typeArgs[0].type) && !typeArgs[0].type.priv.isInUnion) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.typeVarTupleContext(), + typeArgs[0].node + ); + } else if (isUnpackedClass(typeArgs[0].type)) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.ellipsisAfterUnpacked(), + typeArg.node + ); + } + } + } else if (isParamSpec(typeArg.type) && allowParamSpec) { + // Nothing to do - this is allowed. + } else if (paramLimit === undefined && isTypeVarTuple(typeArg.type)) { + if (!typeArg.type.priv.isInUnion) { + noteSawUnpacked(typeArg); + } + validateTypeVarTupleIsUnpacked(typeArg.type, typeArg.node); + } else if (paramLimit === undefined && isUnpackedClass(typeArg.type)) { + if (isUnboundedTupleClass(typeArg.type)) { + noteSawUnpacked(typeArg); + } + validateTypeArg(typeArg, { allowUnpackedTuples: true }); + } else { + validateTypeArg(typeArg); + } + }); + } + } + + let typeArgTypes = typeArgs ? typeArgs.map((t) => convertToInstance(t.type)) : []; + + // Make sure the argument list count is correct. + if (paramLimit !== undefined) { + if (typeArgs && typeArgTypes.length > paramLimit) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.typeArgsTooMany().format({ + name: classType.priv.aliasName || classType.shared.name, + expected: paramLimit, + received: typeArgTypes.length, + }), + typeArgs[paramLimit].node + ); + typeArgTypes = typeArgTypes.slice(0, paramLimit); + } else if (typeArgTypes.length < paramLimit) { + // Fill up the remainder of the slots with unknown types. + while (typeArgTypes.length < paramLimit) { + typeArgTypes.push(UnknownType.create()); + } + } + } + + // Handle tuple type params as a special case. + let returnType: Type; + if (isTupleTypeParam) { + const tupleTypeArgTypes: TupleTypeArg[] = []; + + // If no type args are provided and it's a tuple, default to [Unknown, ...]. + if (!typeArgs) { + tupleTypeArgTypes.push({ type: UnknownType.create(), isUnbounded: true }); + } else { + typeArgs.forEach((typeArg, index) => { + if (index === 1 && isEllipsisType(typeArgTypes[index])) { + if (tupleTypeArgTypes.length === 1 && !tupleTypeArgTypes[0].isUnbounded) { + tupleTypeArgTypes[0] = { type: tupleTypeArgTypes[0].type, isUnbounded: true }; + } + } else if (isUnpackedClass(typeArg.type) && typeArg.type.priv.tupleTypeArgs) { + appendArray(tupleTypeArgTypes, typeArg.type.priv.tupleTypeArgs); + } else { + tupleTypeArgTypes.push({ type: typeArgTypes[index], isUnbounded: false }); + } + }); + } + + returnType = specializeTupleClass(classType, tupleTypeArgTypes, typeArgs !== undefined); + } else { + returnType = ClassType.specialize(classType, typeArgTypes, typeArgs !== undefined); + } + + if (isSpecialForm) { + returnType = TypeBase.cloneAsSpecialForm(returnType, classType); + } + + return returnType; + } + + // Unpacks the index expression for a "Union[X, Y, Z]" type annotation. + function createUnionType( + classType: ClassType, + errorNode: ParseNode, + typeArgs: TypeResultWithNode[] | undefined, + flags: EvalFlags + ): Type { + const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode); + const types: Type[] = []; + let allowSingleTypeArg = false; + let isValidTypeForm = true; + + if (!typeArgs) { + // If no type arguments are provided, the resulting type + // depends on whether we're evaluating a type annotation or + // we're in some other context. + if ((flags & EvalFlags.TypeExpression) !== 0) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.unionTypeArgCount(), errorNode); + return NeverType.createNever(); + } + + return classType; + } + + for (const typeArg of typeArgs) { + let typeArgType = typeArg.type; + + // This is an experimental feature because Unions of unpacked TypeVarTuples are not officially supported. + if ( + !validateTypeArg(typeArg, { + allowTypeVarTuple: fileInfo.diagnosticRuleSet.enableExperimentalFeatures, + }) + ) { + typeArgType = UnknownType.create(); + } + + if (isTypeVar(typeArgType) && isUnpackedTypeVarTuple(typeArgType)) { + // This is an experimental feature because Unions of unpacked TypeVarTuples are not officially supported. + if (fileInfo.diagnosticRuleSet.enableExperimentalFeatures) { + // If this is an unpacked TypeVar, note that it is in a union so we can + // differentiate between Unpack[Vs] and Union[Unpack[Vs]]. + typeArgType = TypeVarType.cloneForUnpacked(typeArgType, /* isInUnion */ true); + allowSingleTypeArg = true; + } else { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.unionUnpackedTypeVarTuple(), + errorNode + ); + + typeArgType = UnknownType.create(); + isValidTypeForm = false; + } + } + + types.push(typeArgType); + } + + // Validate that we received at least two type arguments. One type argument + // is allowed if it's an unpacked TypeVarTuple or tuple. None is also allowed + // since it is used to define NoReturn in typeshed stubs). + if (types.length === 1 && !allowSingleTypeArg && !isNoneInstance(types[0])) { + if ((flags & EvalFlags.TypeExpression) !== 0) { + addDiagnostic(DiagnosticRule.reportInvalidTypeArguments, LocMessage.unionTypeArgCount(), errorNode); + } + isValidTypeForm = false; + } + + let unionType = combineTypes(types, { skipElideRedundantLiterals: true }); + if (prefetched?.unionTypeClass && isInstantiableClass(prefetched.unionTypeClass)) { + unionType = TypeBase.cloneAsSpecialForm(unionType, ClassType.cloneAsInstance(prefetched.unionTypeClass)); + } + + if (!isValidTypeForm || types.some((t) => !t.props?.typeForm)) { + if (unionType.props?.typeForm) { + unionType = TypeBase.cloneWithTypeForm(unionType, undefined); + } + } else { + const typeFormType = combineTypes(types.map((t) => t.props!.typeForm!)); + unionType = TypeBase.cloneWithTypeForm(unionType, typeFormType); + } + + return unionType; + } + + // Creates a type that represents "Generic[T1, T2, ...]", used in the + // definition of a generic class. + function createGenericType( + classType: ClassType, + errorNode: ParseNode, + typeArgs: TypeResultWithNode[] | undefined, + flags: EvalFlags + ): Type { + if (!typeArgs) { + // If no type arguments are provided, the resulting type + // depends on whether we're evaluating a type annotation or + // we're in some other context. + if ((flags & (EvalFlags.TypeExpression | EvalFlags.NoNakedGeneric)) !== 0) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.genericTypeArgMissing(), errorNode); + } + + return classType; + } + + const uniqueTypeVars: TypeVarType[] = []; + if (typeArgs) { + // Make sure there's at least one type arg. + if (typeArgs.length === 0) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.genericTypeArgMissing(), errorNode); + } + + // Make sure that all of the type args are typeVars and are unique. + typeArgs.forEach((typeArg) => { + if (!isTypeVar(typeArg.type)) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.genericTypeArgTypeVar(), + typeArg.node + ); + } else { + if (uniqueTypeVars.some((t) => isTypeSame(t, typeArg.type))) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.genericTypeArgUnique(), + typeArg.node + ); + } + + uniqueTypeVars.push(typeArg.type); + } + }); + } + + return createSpecialType(classType, typeArgs, /* paramLimit */ undefined, /* allowParamSpec */ true); + } + + function transformTypeForTypeAlias( + type: Type, + errorNode: ExpressionNode, + typeAliasPlaceholder: TypeVarType, + isPep695TypeVarType: boolean, + typeParamNodes?: TypeParameterNode[] + ): Type { + // If this is a recursive type alias that hasn't yet been fully resolved + // (i.e. there is no boundType associated with it), don't apply the transform. + if (isTypeAliasPlaceholder(type)) { + return type; + } + + const sharedInfo = typeAliasPlaceholder.shared.recursiveAlias; + assert(sharedInfo !== undefined); + + let typeParams: TypeVarType[] | undefined = sharedInfo.typeParams; + if (!typeParams) { + // Determine if there are any generic type parameters associated + // with this type alias. + typeParams = []; + + addTypeVarsToListIfUnique(typeParams, getTypeVarArgsRecursive(type)); + + // Don't include any synthesized type variables. + typeParams = typeParams.filter((typeVar) => !typeVar.shared.isSynthesized); + } + + // Convert all type variables to instances. + typeParams = typeParams.map((typeVar) => { + if (TypeBase.isInstance(typeVar)) { + return typeVar; + } + return convertToInstance(typeVar); + }); + + // See if the type alias includes a TypeVarTuple followed by a TypeVar + // with a default value. This isn't allowed. + const firstTypeVarTupleIndex = typeParams.findIndex((typeVar) => isTypeVarTuple(typeVar)); + if (firstTypeVarTupleIndex >= 0) { + const typeVarWithDefaultIndex = typeParams.findIndex( + (typeVar, index) => + index > firstTypeVarTupleIndex && !isParamSpec(typeVar) && typeVar.shared.isDefaultExplicit + ); + + if (typeVarWithDefaultIndex >= 0) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarWithDefaultFollowsVariadic().format({ + typeVarName: typeParams[typeVarWithDefaultIndex].shared.name, + variadicName: typeParams[firstTypeVarTupleIndex].shared.name, + }), + typeParamNodes ? typeParamNodes[typeVarWithDefaultIndex].d.name : errorNode + ); + } + } + + // Validate the default types for all type parameters. + typeParams.forEach((typeParam, index) => { + assert(typeParams !== undefined); + let bestErrorNode = errorNode; + if (typeParamNodes && index < typeParamNodes.length) { + bestErrorNode = typeParamNodes[index].d.defaultExpr ?? typeParamNodes[index].d.name; + } + validateTypeParamDefault(bestErrorNode, typeParam, typeParams.slice(0, index), sharedInfo.typeVarScopeId); + }); + + // Verify that we have at most one TypeVarTuple. + const variadics = typeParams.filter((param) => isTypeVarTuple(param)); + if (variadics.length > 1) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.variadicTypeParamTooManyAlias().format({ + names: variadics.map((v) => `"${v.shared.name}"`).join(', '), + }), + errorNode + ); + } + + if (!sharedInfo.isTypeAliasType && !isPep695TypeVarType) { + const boundTypeVars = typeParams.filter( + (typeVar) => + typeVar.priv.scopeId !== sharedInfo.typeVarScopeId && + typeVar.priv.scopeType === TypeVarScopeType.Class + ); + + if (boundTypeVars.length > 0) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.genericTypeAliasBoundTypeVar().format({ + names: boundTypeVars.map((t) => `${t.shared.name}`).join(', '), + }), + errorNode + ); + } + } + + if (!TypeBase.isInstantiable(type)) { + return type; + } + + sharedInfo.typeParams = typeParams.length > 0 ? typeParams : undefined; + + let typeAlias = TypeBase.cloneForTypeAlias(type, { + shared: sharedInfo, + typeArgs: undefined, + }); + + // All PEP 695 type aliases are special forms because they are + // TypeAliasType objects at runtime. + if (sharedInfo.isTypeAliasType || isPep695TypeVarType) { + const typeAliasTypeClass = getTypingType(errorNode, 'TypeAliasType'); + if (typeAliasTypeClass && isInstantiableClass(typeAliasTypeClass)) { + typeAlias = TypeBase.cloneAsSpecialForm(typeAlias, ClassType.cloneAsInstance(typeAliasTypeClass)); + } + } + + // Delete the TypeForm info. The type alias serves as its own TypeForm info. + if (typeAlias.props?.typeForm) { + typeAlias = TypeBase.cloneWithTypeForm(typeAlias, undefined); + } + + return typeAlias; + } + + function createSpecialBuiltInClass(node: ParseNode, assignedName: string, aliasMapEntry: AliasMapEntry): ClassType { + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + let specialClassType = ClassType.createInstantiable( + assignedName, + ParseTreeUtils.getClassFullName(node, fileInfo.moduleName, assignedName), + fileInfo.moduleName, + fileInfo.fileUri, + ClassTypeFlags.BuiltIn | ClassTypeFlags.SpecialBuiltIn, + /* typeSourceId */ 0, + /* declaredMetaclass */ undefined, + /* effectiveMetaclass */ undefined + ); + + if (aliasMapEntry.isSpecialForm) { + specialClassType.shared.flags |= ClassTypeFlags.SpecialFormClass; + } + + if (aliasMapEntry.isIllegalInIsinstance) { + specialClassType.shared.flags |= ClassTypeFlags.IllegalIsinstanceClass; + } + + // Synthesize a single type parameter with the specified variance if + // specified in the alias map entry. + if (aliasMapEntry.typeParamVariance !== undefined) { + let typeParam = TypeVarType.createInstance('T'); + typeParam = TypeVarType.cloneForScopeId( + typeParam, + ParseTreeUtils.getScopeIdForNode(node), + assignedName, + TypeVarScopeType.Class + ); + typeParam.shared.declaredVariance = aliasMapEntry.typeParamVariance; + specialClassType.shared.typeParams.push(typeParam); + } + + const specialBuiltInClassDeclaration = (AnalyzerNodeInfo.getDeclaration(node) ?? + (node.parent ? AnalyzerNodeInfo.getDeclaration(node.parent) : undefined)) as + | SpecialBuiltInClassDeclaration + | undefined; + + specialClassType.shared.declaration = specialBuiltInClassDeclaration; + + if (fileInfo.isTypingExtensionsStubFile) { + specialClassType.shared.flags |= ClassTypeFlags.TypingExtensionClass; + } + + const baseClassName = aliasMapEntry.implicitBaseClass || aliasMapEntry.alias || 'object'; + + let baseClass: Type | undefined; + if (aliasMapEntry.module === 'builtins') { + baseClass = getBuiltInType(node, baseClassName); + } else if (aliasMapEntry.module === 'collections') { + // The typing.pyi file imports collections. + baseClass = getTypeOfModule(node, baseClassName, ['collections']); + } else if (aliasMapEntry.module === 'internals') { + // Handle TypedDict specially. + assert(baseClassName === 'TypedDictFallback'); + baseClass = prefetched?.typedDictPrivateClass; + if (baseClass) { + // The TypedDictFallback class is marked as abstract, but the + // methods that are abstract are overridden and shouldn't + // cause the TypedDict to be marked as abstract. + if ( + isInstantiableClass(baseClass) && + ClassType.isBuiltIn(baseClass, ['_TypedDict', 'TypedDictFallback']) + ) { + baseClass = ClassType.cloneWithNewFlags( + baseClass, + baseClass.shared.flags & + ~(ClassTypeFlags.SupportsAbstractMethods | ClassTypeFlags.TypeCheckOnly) + ); + } + } + } + + if (baseClass && isInstantiableClass(baseClass)) { + if (aliasMapEntry.alias) { + specialClassType = ClassType.cloneForTypingAlias(baseClass, assignedName); + } else { + specialClassType.shared.baseClasses.push(baseClass); + specialClassType.shared.effectiveMetaclass = baseClass.shared.effectiveMetaclass; + computeMroLinearization(specialClassType); + } + } else { + specialClassType.shared.baseClasses.push(UnknownType.create()); + specialClassType.shared.effectiveMetaclass = UnknownType.create(); + computeMroLinearization(specialClassType); + } + + return specialClassType; + } + + // Handles some special-case type annotations that are found + // within the typings.pyi file. + function handleTypingStubTypeAnnotation(node: ExpressionNode): Type | undefined { + if (!node.parent || node.parent.nodeType !== ParseNodeType.TypeAnnotation) { + return undefined; + } + + if (node.parent.d.valueExpr.nodeType !== ParseNodeType.Name) { + return undefined; + } + + const nameNode = node.parent.d.valueExpr; + const assignedName = nameNode.d.value; + + const specialTypes: Map = new Map([ + ['Tuple', { alias: 'tuple', module: 'builtins' }], + ['Generic', { alias: '', module: 'builtins', isSpecialForm: true }], + ['Protocol', { alias: '', module: 'builtins', isSpecialForm: true }], + ['Callable', { alias: '', module: 'builtins', isSpecialForm: true }], + ['Type', { alias: 'type', module: 'builtins' }], + ['ClassVar', { alias: '', module: 'builtins', isSpecialForm: true }], + ['Final', { alias: '', module: 'builtins', isSpecialForm: true }], + ['Literal', { alias: '', module: 'builtins', isSpecialForm: true }], + ['TypedDict', { alias: 'TypedDictFallback', module: 'internals' }], + ['Union', { alias: '', module: 'builtins', isSpecialForm: true }], + ['Optional', { alias: '', module: 'builtins', isSpecialForm: true }], + ['Annotated', { alias: '', module: 'builtins', isSpecialForm: true, isIllegalInIsinstance: true }], + ['TypeAlias', { alias: '', module: 'builtins', isSpecialForm: true }], + ['Concatenate', { alias: '', module: 'builtins', isSpecialForm: true }], + [ + 'TypeGuard', + { + alias: '', + module: 'builtins', + implicitBaseClass: 'bool', + isSpecialForm: true, + typeParamVariance: Variance.Covariant, + }, + ], + ['Unpack', { alias: '', module: 'builtins', isSpecialForm: true }], + ['Required', { alias: '', module: 'builtins', isSpecialForm: true }], + ['NotRequired', { alias: '', module: 'builtins', isSpecialForm: true }], + ['Self', { alias: '', module: 'builtins', isSpecialForm: true }], + ['NoReturn', { alias: '', module: 'builtins', isSpecialForm: true }], + ['Never', { alias: '', module: 'builtins', isSpecialForm: true }], + ['LiteralString', { alias: '', module: 'builtins', isSpecialForm: true }], + ['ReadOnly', { alias: '', module: 'builtins', isSpecialForm: true }], + [ + 'TypeIs', + { + alias: '', + module: 'builtins', + implicitBaseClass: 'bool', + isSpecialForm: true, + typeParamVariance: Variance.Invariant, + }, + ], + [ + 'TypeForm', + { + alias: '', + module: 'builtins', + isSpecialForm: true, + typeParamVariance: Variance.Covariant, + isIllegalInIsinstance: true, + }, + ], + ]); + + const aliasMapEntry = specialTypes.get(assignedName); + + if (aliasMapEntry) { + const cachedType = readTypeCache(node, EvalFlags.None); + if (cachedType) { + return cachedType; + } + + let specialType: Type = createSpecialBuiltInClass(node, assignedName, aliasMapEntry); + + // Handle 'LiteralString' specially because we want it to act as + // though it derives from 'str'. + if (assignedName === 'LiteralString') { + specialType.shared.baseClasses.push(prefetched?.strClass ?? AnyType.create()); + computeMroLinearization(specialType); + + specialType = TypeBase.cloneWithTypeForm(specialType, convertToInstance(specialType)); + } + + // Handle 'Never' and 'NoReturn' specially. + if (assignedName === 'Never' || assignedName === 'NoReturn') { + specialType = TypeBase.cloneAsSpecialForm( + assignedName === 'Never' ? NeverType.createNever() : NeverType.createNoReturn(), + specialType + ); + + specialType = TypeBase.cloneWithTypeForm(specialType, convertToInstance(specialType)); + } + + writeTypeCache(node, { type: specialType }, EvalFlags.None); + return specialType; + } + + return undefined; + } + + // Handles some special-case assignment statements that are found + // within the typings.pyi file. + function handleTypingStubAssignment(node: AssignmentNode): Type | undefined { + if (node.d.leftExpr.nodeType !== ParseNodeType.Name) { + return undefined; + } + + const nameNode = node.d.leftExpr; + const assignedName = nameNode.d.value; + + if (assignedName === 'Any') { + return AnyType.createSpecialForm(); + } + + const specialTypes: Map = new Map([ + ['overload', { alias: '', module: 'builtins' }], + ['TypeVar', { alias: '', module: 'builtins' }], + ['_promote', { alias: '', module: 'builtins' }], + ['no_type_check', { alias: '', module: 'builtins' }], + ['NoReturn', { alias: '', module: 'builtins' }], + ['Never', { alias: '', module: 'builtins' }], + ['Counter', { alias: 'Counter', module: 'collections' }], + ['List', { alias: 'list', module: 'builtins' }], + ['Dict', { alias: 'dict', module: 'builtins' }], + ['DefaultDict', { alias: 'defaultdict', module: 'collections' }], + ['Set', { alias: 'set', module: 'builtins' }], + ['FrozenSet', { alias: 'frozenset', module: 'builtins' }], + ['Deque', { alias: 'deque', module: 'collections' }], + ['ChainMap', { alias: 'ChainMap', module: 'collections' }], + ['OrderedDict', { alias: 'OrderedDict', module: 'collections' }], + ]); + + const aliasMapEntry = specialTypes.get(assignedName); + if (aliasMapEntry) { + // Evaluate the expression so symbols are marked as accessed. + getTypeOfExpression(node.d.rightExpr); + return createSpecialBuiltInClass(node, assignedName, aliasMapEntry); + } + + return undefined; + } + + function evaluateTypesForAssignmentStatement(node: AssignmentNode): void { + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + + // If the entire statement has already been evaluated, don't + // re-evaluate it. + if (isTypeCached(node)) { + return; + } + + let flags: EvalFlags = EvalFlags.None; + if (fileInfo.isStubFile) { + // An assignment of ellipsis means "Any" within a type stub file. + flags |= EvalFlags.ConvertEllipsisToAny; + } + + if ( + node.d.rightExpr.nodeType === ParseNodeType.Name || + node.d.rightExpr.nodeType === ParseNodeType.MemberAccess + ) { + // Don't specialize a generic class on assignment (e.g. "x = list" + // or "x = collections.OrderedDict") because we may want to later + // specialize it (e.g. "x[int]"). + flags |= EvalFlags.NoSpecialize; + } + + // Is this type already cached? + let rightHandType = readTypeCache(node.d.rightExpr, /* flags */ undefined); + let isIncomplete = false; + let expectedTypeDiagAddendum: DiagnosticAddendum | undefined; + + if (!rightHandType) { + // Special-case the typing.pyi file, which contains some special + // types that the type analyzer needs to interpret differently. + if (fileInfo.isTypingStubFile || fileInfo.isTypingExtensionsStubFile) { + rightHandType = handleTypingStubAssignment(node); + if (rightHandType) { + writeTypeCache(node.d.rightExpr, { type: rightHandType }, EvalFlags.None); + } + } + } + + if (!rightHandType) { + let typeAliasNameNode: NameNode | undefined; + let typeAliasPlaceholder: TypeVarType | undefined; + let isSpeculativeTypeAlias = false; + + if (isDeclaredTypeAlias(node.d.leftExpr)) { + flags = + EvalFlags.InstantiableType | + EvalFlags.TypeExpression | + EvalFlags.StrLiteralAsType | + EvalFlags.NoParamSpec | + EvalFlags.NoTypeVarTuple | + EvalFlags.NoClassVar; + + typeAliasNameNode = (node.d.leftExpr as TypeAnnotationNode).d.valueExpr as NameNode; + + if (!isLegalTypeAliasExpressionForm(node.d.rightExpr, /* allowStrLiteral */ true)) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.typeAliasIllegalExpressionForm(), + node.d.rightExpr + ); + } + } else if (node.d.leftExpr.nodeType === ParseNodeType.Name) { + const symbolWithScope = lookUpSymbolRecursive( + node.d.leftExpr, + node.d.leftExpr.d.value, + /* honorCodeFlow */ false + ); + + if (symbolWithScope) { + const decls = symbolWithScope.symbol.getDeclarations(); + + if (decls.length === 1) { + if (isPossibleTypeAliasDeclaration(decls[0])) { + typeAliasNameNode = node.d.leftExpr; + isSpeculativeTypeAlias = true; + flags |= EvalFlags.NoConvertSpecialForm; + } else if (isPossibleTypeDictFactoryCall(decls[0])) { + // Handle calls to TypedDict factory functions like type + // aliases to support recursive field type definitions. + typeAliasNameNode = node.d.leftExpr; + } + } + } + } + + if (typeAliasNameNode) { + typeAliasPlaceholder = synthesizeTypeAliasPlaceholder(typeAliasNameNode); + + writeTypeCache(node, { type: typeAliasPlaceholder }, /* flags */ undefined); + writeTypeCache(node.d.leftExpr, { type: typeAliasPlaceholder }, /* flags */ undefined); + + if (node.d.leftExpr.nodeType === ParseNodeType.TypeAnnotation) { + writeTypeCache(node.d.leftExpr.d.valueExpr, { type: typeAliasPlaceholder }, /* flags */ undefined); + } + } + + let declaredType = getDeclaredTypeForExpression(node.d.leftExpr, { method: 'set' }); + + if (declaredType) { + const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(node); + declaredType = makeTypeVarsBound(declaredType, liveTypeVarScopes); + } + + const srcTypeResult = getTypeOfExpression(node.d.rightExpr, flags, makeInferenceContext(declaredType)); + + rightHandType = srcTypeResult.type; + expectedTypeDiagAddendum = srcTypeResult.expectedTypeDiagAddendum; + if (srcTypeResult.isIncomplete) { + isIncomplete = true; + } + + // If this was a speculative type alias, it becomes a real type alias + // only if the evaluated type is an instantiable type. + if (isSpeculativeTypeAlias && !isLegalImplicitTypeAliasType(rightHandType)) { + typeAliasNameNode = undefined; + } + + if (typeAliasNameNode) { + assert(typeAliasPlaceholder !== undefined); + + // If this is a type alias, record its name based on the assignment target. + rightHandType = transformTypeForTypeAlias( + rightHandType, + typeAliasNameNode, + typeAliasPlaceholder, + /* isPep695TypeVarType */ false + ); + + if (isTypeAliasRecursive(typeAliasPlaceholder, rightHandType)) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeAliasIsRecursiveDirect().format({ + name: typeAliasNameNode.d.value, + }), + node.d.rightExpr + ); + + rightHandType = UnknownType.create(); + } + + // Set the resulting type to the boundType of the original type alias + // to support recursive type aliases. + typeAliasPlaceholder.shared.boundType = rightHandType; + + // Record the type parameters within the recursive type alias so it + // can be specialized. + typeAliasPlaceholder.shared.recursiveAlias!.typeParams = + rightHandType.props?.typeAliasInfo?.shared.typeParams; + } else { + // If the RHS is a constant boolean expression, assign it a literal type. + const constExprValue = evaluateStaticBoolExpression( + node.d.rightExpr, + fileInfo.executionEnvironment, + fileInfo.definedConstants + ); + + if (constExprValue !== undefined) { + const boolType = getBuiltInObject(node, 'bool'); + if (isClassInstance(boolType)) { + rightHandType = ClassType.cloneWithLiteral(boolType, constExprValue); + } + } + } + } + + // Viper 扩展:TypeSet 语义——带类型注解的赋值保持声明类型。 + // Viper 是 AOT 静态语言,注解位置的 `|` 表示类型组合(如 CConst | str + // 表示 const char*),而非 Python 的 TypeUnion(任选其一)。变量类型 + // 在声明时确定,不应被右侧推断类型收窄(如 `a: CConst | str = "Hello"` + // 应保持 `CConst | str`,而非 `Literal['Hello']`)。 + // 必须在 assignTypeToExpression 之前覆盖 rightHandType,因为 + // assignTypeToExpression 会将类型写入 NameNode 缓存(hover 读取的就是这个缓存)。 + // 这与 getTypeOfName(第 5028 行)的保持声明类型逻辑对应。 + // + // 例外:t.CTypedef 注解的变量(如 `UINT32PTR: t.CTypedef = t.CUInt32T | t.CPtr`) + // 属于类型别名声明,其右侧表达式才是真实类型组合。此时不应覆盖为声明类型 + // (t.CTypedef 本质是 TypeAlias 类实例,会被解析为基础类型 int),而应保持 + // 右侧推断出的类型组合(t.CUInt32T | t.CPtr)。 + if (IsViperDialect(fileInfo.executionEnvironment.dialect) && node.d.leftExpr.nodeType === ParseNodeType.TypeAnnotation) { + const typeAnnotNode = node.d.leftExpr as TypeAnnotationNode; + const annotationNode = typeAnnotNode.d.annotation; + const isCTypedefDecl = + annotationNode.nodeType === ParseNodeType.MemberAccess && + (annotationNode as MemberAccessNode).d.member.d.value === 'CTypedef'; + + if (!isCTypedefDecl) { + const viperDeclaredType = getDeclaredTypeForExpression(node.d.leftExpr, { method: 'set' }); + if (viperDeclaredType) { + rightHandType = viperDeclaredType; + } + } + } + + assignTypeToExpression( + node.d.leftExpr, + { type: rightHandType, isIncomplete }, + node.d.rightExpr, + /* ignoreEmptyContainers */ true, + /* allowAssignmentToFinalVar */ true, + expectedTypeDiagAddendum + ); + + writeTypeCache(node, { type: rightHandType, isIncomplete }, EvalFlags.None); + } + + // Synthesize a TypeVar that acts as a placeholder for a type alias. This allows + // the type alias definition to refer to itself. + function synthesizeTypeAliasPlaceholder(nameNode: NameNode, isTypeAliasType: boolean = false): TypeVarType { + const placeholder = TypeVarType.createInstantiable(`__type_alias_${nameNode.d.value}`); + placeholder.shared.isSynthesized = true; + const typeVarScopeId = ParseTreeUtils.getScopeIdForNode(nameNode); + const fileInfo = AnalyzerNodeInfo.getFileInfo(nameNode); + + placeholder.shared.recursiveAlias = { + name: nameNode.d.value, + fullName: ParseTreeUtils.getClassFullName(nameNode, fileInfo.moduleName, nameNode.d.value), + moduleName: fileInfo.moduleName, + fileUri: fileInfo.fileUri, + typeVarScopeId, + isTypeAliasType, + typeParams: undefined, + computedVariance: undefined, + }; + placeholder.priv.scopeId = typeVarScopeId; + + return placeholder; + } + + // Evaluates the type of a type alias (i.e. "type") statement. This code + // path does not handle traditional type aliases, which are treated as + // variables since they use normal variable assignment syntax. + function getTypeOfTypeAlias(node: TypeAliasNode): Type { + return getTypeOfTypeAliasCommon( + node, + node.d.name, + node.d.expr, + /* isPep695Syntax */ true, + node.d.typeParams?.d.params, + () => { + if (node.d.typeParams) { + return evaluateTypeParamList(node.d.typeParams); + } + return undefined; + } + ); + } + + // This function is common to the handling of "type" statements and explicit + // calls to the TypeAliasType constructor. + function getTypeOfTypeAliasCommon( + declNode: ParseNode, + nameNode: NameNode, + valueNode: ExpressionNode, + isPep695Syntax: boolean, + typeParamNodes: TypeParameterNode[] | undefined, + getTypeParamCallback: () => TypeVarType[] | undefined + ) { + const cachedType = readTypeCache(nameNode, EvalFlags.None); + if (cachedType) { + return cachedType; + } + + // Synthesize a type variable that represents the type alias while we're + // evaluating it. This allows us to handle recursive definitions. + const typeAliasTypeVar = synthesizeTypeAliasPlaceholder(nameNode, /* isTypeAliasType */ true); + + // Write the type to the type cache to support recursive type alias definitions. + writeTypeCache(nameNode, { type: typeAliasTypeVar }, /* flags */ undefined); + + // Set a partial type to handle recursive (self-referential) type aliases. + const scope = ScopeUtils.getScopeForNode(declNode); + const typeAliasSymbol = scope?.lookUpSymbolRecursive(nameNode.d.value); + const typeAliasDecl = AnalyzerNodeInfo.getDeclaration(declNode); + if (typeAliasDecl && typeAliasSymbol) { + setSymbolResolutionPartialType(typeAliasSymbol.symbol, typeAliasDecl, typeAliasTypeVar); + } + + const typeParams = getTypeParamCallback(); + if (typeAliasTypeVar.shared.recursiveAlias) { + typeAliasTypeVar.shared.recursiveAlias.typeParams = typeParams ?? []; + } + + let aliasTypeResult: TypeResult; + if (isPep695Syntax) { + aliasTypeResult = getTypeOfExpressionExpectingType(valueNode, { + forwardRefs: true, + typeExpression: true, + }); + } else { + const flags = + EvalFlags.InstantiableType | + EvalFlags.TypeExpression | + EvalFlags.StrLiteralAsType | + EvalFlags.NoParamSpec | + EvalFlags.NoTypeVarTuple | + EvalFlags.NoClassVar; + aliasTypeResult = getTypeOfExpression(valueNode, flags); + } + + let isIncomplete = false; + let aliasType = aliasTypeResult.type; + if (aliasTypeResult.isIncomplete) { + isIncomplete = true; + } + + aliasType = transformTypeForTypeAlias( + aliasType, + nameNode, + typeAliasTypeVar, + /* isPep695TypeVarType */ true, + typeParamNodes + ); + + // See if the type alias relies on itself in a way that cannot be resolved. + if (isTypeAliasRecursive(typeAliasTypeVar, aliasType)) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeAliasIsRecursiveDirect().format({ + name: nameNode.d.value, + }), + valueNode + ); + + aliasType = UnknownType.create(); + } + + // Set the resulting type to the boundType of the original type alias + // to support recursive type aliases. + typeAliasTypeVar.shared.boundType = aliasType; + + writeTypeCache(nameNode, { type: aliasType, isIncomplete }, EvalFlags.None); + + return aliasType; + } + + function evaluateTypesForAugmentedAssignment(node: AugmentedAssignmentNode): void { + if (isTypeCached(node)) { + return; + } + + const destTypeResult = getTypeOfAugmentedAssignment(evaluatorInterface, node, /* inferenceContext */ undefined); + + writeTypeCache(node, destTypeResult, EvalFlags.None); + } + + function getPseudoGenericTypeVarName(paramName: string) { + return `__type_of_${paramName}`; + } + + // Creates a new class type that is a subclass of two other specified classes. + function createSubclass(errorNode: ExpressionNode, type1: ClassType, type2: ClassType): ClassType { + assert(isInstantiableClass(type1) && isInstantiableClass(type2)); + + // If both classes are class objects (type[A] and type[B]), create a new + // class object (type[A & B]) rather than "type[A] & type[B]". + let createClassObject = false; + if (TypeBase.getInstantiableDepth(type1) > 0 && TypeBase.getInstantiableDepth(type2) > 0) { + type1 = ClassType.cloneAsInstance(type1); + type2 = ClassType.cloneAsInstance(type2); + createClassObject = true; + } + + const className = ``; + const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode); + + // The effective metaclass of the intersection is the narrower of the two metaclasses. + let effectiveMetaclass = type1.shared.effectiveMetaclass; + if (type2.shared.effectiveMetaclass) { + if (!effectiveMetaclass || assignType(effectiveMetaclass, type2.shared.effectiveMetaclass)) { + effectiveMetaclass = type2.shared.effectiveMetaclass; + } + } + + let newClassType = ClassType.createInstantiable( + className, + ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className), + fileInfo.moduleName, + fileInfo.fileUri, + ClassTypeFlags.None, + ParseTreeUtils.getTypeSourceId(errorNode), + /* declaredMetaclass */ undefined, + effectiveMetaclass, + type1.shared.docString + ); + + newClassType.shared.baseClasses = [type1, type2]; + computeMroLinearization(newClassType); + + newClassType = addConditionToType(newClassType, type1.props?.condition); + newClassType = addConditionToType(newClassType, type2.props?.condition); + + if (createClassObject) { + newClassType = ClassType.cloneAsInstantiable(newClassType); + } + + return newClassType; + } + + function getTypeOfClass(node: ClassNode): ClassTypeResult | undefined { + initializePrefetchedTypes(node); + + // Is this type already cached? + const cachedClassType = readTypeCache(node.d.name, EvalFlags.None); + + if (cachedClassType) { + if (!isInstantiableClass(cachedClassType)) { + // This can happen in rare circumstances where the class declaration + // is located in an unreachable code block. + return undefined; + } + return { + classType: cachedClassType, + decoratedType: readTypeCache(node, EvalFlags.None) || UnknownType.create(), + }; + } + + // The type wasn't cached, so we need to create a new one. + const scope = ScopeUtils.getScopeForNode(node); + + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + let classFlags = ClassTypeFlags.None; + if ( + scope?.type === ScopeType.Builtin || + fileInfo.isTypingStubFile || + fileInfo.isTypingExtensionsStubFile || + fileInfo.isBuiltInStubFile || + fileInfo.isTypeshedStubFile + ) { + classFlags |= ClassTypeFlags.BuiltIn; + + if (fileInfo.isTypingExtensionsStubFile) { + classFlags |= ClassTypeFlags.TypingExtensionClass; + } + + if (node.d.name.d.value === 'property') { + classFlags |= ClassTypeFlags.PropertyClass; + } + + if (node.d.name.d.value === 'tuple') { + classFlags |= ClassTypeFlags.TupleClass; + } + } + + if (fileInfo.isStubFile) { + classFlags |= ClassTypeFlags.DefinedInStub; + } + + const classType = ClassType.createInstantiable( + node.d.name.d.value, + ParseTreeUtils.getClassFullName(node, fileInfo.moduleName, node.d.name.d.value), + fileInfo.moduleName, + fileInfo.fileUri, + classFlags, + ParseTreeUtils.getTypeSourceId(node), + /* declaredMetaclass */ undefined, + /* effectiveMetaclass */ undefined, + ParseTreeUtils.getDocString(node.d.suite.d.statements) + ); + + classType.shared.typeVarScopeId = ParseTreeUtils.getScopeIdForNode(node); + + // Is this a special type that supports type promotions according to PEP 484? + if (typePromotions.has(classType.shared.fullName)) { + classType.priv.includePromotions = true; + } + + // Some classes refer to themselves within type arguments used within + // base classes. We'll register the partially-constructed class type + // to allow these to be resolved. + const classSymbol = scope?.lookUpSymbol(node.d.name.d.value); + let classDecl: ClassDeclaration | undefined; + const decl = AnalyzerNodeInfo.getDeclaration(node); + if (decl) { + classDecl = decl as ClassDeclaration; + } + if (classDecl && classSymbol) { + setSymbolResolutionPartialType(classSymbol, classDecl, classType); + } + classType.shared.flags |= ClassTypeFlags.PartiallyEvaluated; + classType.shared.declaration = classDecl; + + return invalidateTypeCacheIfCanceled(() => { + writeTypeCache(node, { type: classType }, /* flags */ undefined); + writeTypeCache(node.d.name, { type: classType }, /* flags */ undefined); + + // Keep a list of unique type parameters that are used in the + // base class arguments. + let typeParams: TypeVarType[] = []; + + if (node.d.typeParams) { + typeParams = evaluateTypeParamList(node.d.typeParams).map((t) => TypeVarType.cloneAsInstance(t)); + } + + // If the class derives from "Generic" directly, it will provide + // all of the type parameters in the specified order. + let genericTypeParams: TypeVarType[] | undefined; + let protocolTypeParams: TypeVarType[] | undefined; + let isNamedTupleSubclass = false; + + const initSubclassArgs: Arg[] = []; + let metaclassNode: ExpressionNode | undefined; + let exprFlags = + EvalFlags.InstantiableType | + EvalFlags.AllowGeneric | + EvalFlags.NoNakedGeneric | + EvalFlags.NoTypeVarWithScopeId | + EvalFlags.TypeVarGetsCurScope | + EvalFlags.EnforceVarianceConsistency; + if (fileInfo.isStubFile) { + exprFlags |= EvalFlags.ForwardRefs; + } + let sawClosedOrExtraItems = false; + + node.d.arguments.forEach((arg) => { + // Ignore unpacked arguments. + if (arg.d.argCategory === ArgCategory.UnpackedDictionary) { + // Evaluate the expression's type so symbols are marked accessed + // and errors are reported. + getTypeOfExpression(arg.d.valueExpr); + return; + } + + if (!arg.d.name) { + let argType: Type; + + if (arg.d.argCategory === ArgCategory.UnpackedList) { + getTypeOfExpression(arg.d.valueExpr); + argType = UnknownType.create(); + } else { + argType = getTypeOfExpression(arg.d.valueExpr, exprFlags).type; + + if ( + isTypeVar(argType) && + argType.props?.specialForm && + TypeBase.isInstance(argType.props.specialForm) + ) { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.baseClassInvalid(), arg); + argType = UnknownType.create(); + } + + argType = makeTopLevelTypeVarsConcrete(argType); + } + + // In some stub files, classes are conditionally defined (e.g. based + // on platform type). We'll assume that the conditional logic is correct + // and strip off the "unbound" union. + if (isUnion(argType)) { + argType = removeUnbound(argType); + } + + // Any is allowed as a base class. Remove its "special form" flag to avoid + // false positive errors. + if (isAny(argType) && argType.props?.specialForm) { + argType = AnyType.create(); + } + + argType = stripTypeFormRecursive(argType); + + if (!isAnyOrUnknown(argType) && !isUnbound(argType)) { + // If the specified base class is type(T), use the metaclass + // of T if it's known. + if ( + isClass(argType) && + TypeBase.getInstantiableDepth(argType) > 0 && + argType.shared.effectiveMetaclass && + isClass(argType.shared.effectiveMetaclass) + ) { + argType = argType.shared.effectiveMetaclass; + } + + if (isMetaclassInstance(argType)) { + assert(isClassInstance(argType)); + argType = + argType.priv.typeArgs && argType.priv.typeArgs.length > 0 + ? argType.priv.typeArgs[0] + : UnknownType.create(); + } else if (!isInstantiableClass(argType)) { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.baseClassInvalid(), arg); + argType = UnknownType.create(); + } else { + if ( + ClassType.isPartiallyEvaluated(argType) || + argType.shared.mro.some((t) => isClass(t) && ClassType.isPartiallyEvaluated(t)) + ) { + // If the base class is partially evaluated, install a callback + // so we can fix up this class (e.g. compute the MRO) when the + // dependent class is completed. + registerDeferredClassCompletion(node, argType); + } + + if (ClassType.isBuiltIn(argType, 'Protocol')) { + if ( + !fileInfo.isStubFile && + !ClassType.isTypingExtensionClass(argType) && + PythonVersion.isLessThan( + fileInfo.executionEnvironment.pythonVersion, + pythonVersion3_7 + ) + ) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.protocolIllegal(), + arg.d.valueExpr + ); + } + classType.shared.flags |= ClassTypeFlags.ProtocolClass; + } + + if (ClassType.isBuiltIn(argType, 'property')) { + classType.shared.flags |= ClassTypeFlags.PropertyClass; + } + + // If the class directly derives from NamedTuple (in Python 3.6 or + // newer), it's considered a (read-only) dataclass. + if ( + PythonVersion.isGreaterOrEqualTo( + fileInfo.executionEnvironment.pythonVersion, + pythonVersion3_6 + ) + ) { + if (ClassType.isBuiltIn(argType, 'NamedTuple')) { + isNamedTupleSubclass = true; + } + } + + // If the class directly derives from TypedDict or from a class that is + // a TypedDict, it is considered a TypedDict. + if (ClassType.isBuiltIn(argType, 'TypedDict') || ClassType.isTypedDictClass(argType)) { + classType.shared.flags |= ClassTypeFlags.TypedDictClass; + + // Propagate the "effectively closed" flag from base classes. + if (ClassType.isTypedDictEffectivelyClosed(argType)) { + classType.shared.flags |= ClassTypeFlags.TypedDictEffectivelyClosed; + } + } + + // Validate that the class isn't deriving from itself, creating a + // circular dependency. + if (derivesFromClassRecursive(argType, classType, /* ignoreUnknown */ true)) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.baseClassCircular(), + arg + ); + argType = UnknownType.create(); + } + + // If the class is attempting to derive from a TypeAliasType, + // generate an error. + if ( + argType.props?.specialForm && + ClassType.isBuiltIn(argType.props.specialForm, 'TypeAliasType') + ) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeAliasTypeBaseClass(), + arg + ); + argType = UnknownType.create(); + } + } + } + + if (isUnknown(argType)) { + addDiagnostic(DiagnosticRule.reportUntypedBaseClass, LocMessage.baseClassUnknown(), arg); + } + + // Check for a duplicate class. + if ( + classType.shared.baseClasses.some((prevBaseClass) => { + return ( + isInstantiableClass(prevBaseClass) && + isInstantiableClass(argType) && + ClassType.isSameGenericClass(argType, prevBaseClass) + ); + }) + ) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.duplicateBaseClass(), + arg.d.name || arg + ); + } + + classType.shared.baseClasses.push(argType); + if (isInstantiableClass(argType)) { + if (ClassType.isEnumClass(argType)) { + classType.shared.flags |= ClassTypeFlags.EnumClass; + } + + // Viper 扩展:CEnum 子类也标记为 EnumClass。 + // Viper 的 CEnum 不是 Python 的 enum.Enum,但语义上等价: + // 枚举成员访问(如 DRESULT.RES_PARERR)应返回 DRESULT 类型, + // 而不是成员的声明类型。这样可使枚举成员赋值给返回类型为 + // DRESULT 的变量/函数时不报类型错误。 + // 注意:不能用 ClassType.isBuiltIn 判断,因为 t.CEnum 来自 + // vipershed-fallback,没有 BuiltIn 标志。 + if ( + IsViperDialect(fileInfo.executionEnvironment.dialect) && + isCEnumType(argType) + ) { + classType.shared.flags |= ClassTypeFlags.EnumClass; + } + + // Determine if the class is abstract. Protocol classes support abstract methods + // because they are constructed by the _ProtocolMeta metaclass, which derives + // from ABCMeta. + if (ClassType.supportsAbstractMethods(argType) || ClassType.isProtocolClass(argType)) { + classType.shared.flags |= ClassTypeFlags.SupportsAbstractMethods; + } + + if (ClassType.isPropertyClass(argType)) { + classType.shared.flags |= ClassTypeFlags.PropertyClass; + } + + if (ClassType.isFinal(argType)) { + const className = printObjectTypeForClass(argType); + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.baseClassFinal().format({ type: className }), + arg.d.valueExpr + ); + } + } + + addTypeVarsToListIfUnique(typeParams, getTypeVarArgsRecursive(argType)); + if (isInstantiableClass(argType)) { + if (ClassType.isBuiltIn(argType, 'Generic')) { + // 'Generic' is implicitly added if type parameter syntax is used. + if (node.d.typeParams) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.genericBaseClassNotAllowed(), + arg.d.valueExpr + ); + } else { + if (!genericTypeParams) { + if (protocolTypeParams) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.duplicateGenericAndProtocolBase(), + arg.d.valueExpr + ); + } + genericTypeParams = buildTypeParamsFromTypeArgs(argType); + } + } + } else if ( + ClassType.isBuiltIn(argType, 'Protocol') && + argType.priv.typeArgs && + argType.priv.typeArgs.length > 0 + ) { + if (!protocolTypeParams) { + if (genericTypeParams) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.duplicateGenericAndProtocolBase(), + arg.d.valueExpr + ); + } + protocolTypeParams = buildTypeParamsFromTypeArgs(argType); + + if (node.d.typeParams && protocolTypeParams.length > 0) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.protocolBaseClassWithTypeArgs(), + arg.d.valueExpr + ); + protocolTypeParams = []; + } + } + } + } + } else if (ClassType.isTypedDictClass(classType)) { + if (arg.d.name.d.value === 'total' || arg.d.name.d.value === 'closed') { + // The "total" and "readonly" parameters apply only for TypedDict classes. + // PEP 589 specifies that the parameter must be either True or False. + const constArgValue = evaluateStaticBoolExpression( + arg.d.valueExpr, + fileInfo.executionEnvironment, + fileInfo.definedConstants + ); + + if (constArgValue === undefined) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typedDictBoolParam().format({ name: arg.d.name.d.value }), + arg.d.valueExpr + ); + } else if (arg.d.name.d.value === 'total' && !constArgValue) { + classType.shared.flags |= ClassTypeFlags.CanOmitDictValues; + } else if (arg.d.name.d.value === 'closed') { + if (constArgValue) { + classType.shared.flags |= + ClassTypeFlags.TypedDictMarkedClosed | ClassTypeFlags.TypedDictEffectivelyClosed; + + if (classType.shared.typedDictExtraItemsExpr) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typedDictExtraItemsClosed(), + classType.shared.typedDictExtraItemsExpr + ); + } + } else { + // PEP 728: A class that subclasses from a non-open TypedDict + // cannot specify closed=False. + const nonOpenBase = classType.shared.baseClasses.find( + (base) => + isInstantiableClass(base) && + ClassType.isTypedDictClass(base) && + ClassType.isTypedDictEffectivelyClosed(base) + ); + if (nonOpenBase) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typedDictClosedFalseNonOpenBase().format({ + name: (nonOpenBase as ClassType).shared.name, + }), + arg.d.valueExpr + ); + } + } + + if (sawClosedOrExtraItems) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typedDictExtraItemsClosed(), + arg.d.valueExpr + ); + } + + sawClosedOrExtraItems = true; + } + } else if (arg.d.name.d.value === 'extra_items') { + // Record a reference to the expression but don't evaluate it yet. + // It may refer to the class itself. + classType.shared.typedDictExtraItemsExpr = arg.d.valueExpr; + classType.shared.flags |= ClassTypeFlags.TypedDictEffectivelyClosed; + + if (ClassType.isTypedDictMarkedClosed(classType)) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typedDictExtraItemsClosed(), + classType.shared.typedDictExtraItemsExpr + ); + } + + if (sawClosedOrExtraItems) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typedDictExtraItemsClosed(), + arg.d.valueExpr + ); + } + + sawClosedOrExtraItems = true; + } else { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typedDictInitsubclassParameter().format({ name: arg.d.name.d.value }), + arg + ); + } + } else if (arg.d.name.d.value === 'metaclass') { + if (metaclassNode) { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.metaclassDuplicate(), arg); + } else { + metaclassNode = arg.d.valueExpr; + } + } else { + // Collect arguments that will be passed to the `__init_subclass__` + // method described in PEP 487. + initSubclassArgs.push({ + argCategory: ArgCategory.Simple, + node: arg, + name: arg.d.name, + valueExpression: arg.d.valueExpr, + }); + } + }); + + // Check for NamedTuple multiple inheritance. + if (classType.shared.baseClasses.length > 1) { + let derivesFromNamedTuple = false; + let foundIllegalBaseClass = false; + + classType.shared.baseClasses.forEach((baseClass) => { + if (isInstantiableClass(baseClass)) { + if (ClassType.isBuiltIn(baseClass, 'NamedTuple')) { + derivesFromNamedTuple = true; + } else if (!ClassType.isBuiltIn(baseClass, 'Generic')) { + foundIllegalBaseClass = true; + } + } + }); + + if (derivesFromNamedTuple && foundIllegalBaseClass) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.namedTupleMultipleInheritance(), + node.d.name + ); + } + } + + // Make sure we don't have 'object' derive from itself. Infinite + // recursion will result. + if ( + !ClassType.isBuiltIn(classType, 'object') && + classType.shared.baseClasses.filter((baseClass) => isClass(baseClass)).length === 0 + ) { + // If there are no other (known) base classes, the class implicitly derives from object. + classType.shared.baseClasses.push(getBuiltInType(node, 'object')); + } + + // If genericTypeParams or protocolTypeParams are provided, + // make sure that typeParams is a proper subset. + genericTypeParams = genericTypeParams ?? protocolTypeParams; + if (genericTypeParams && !node.d.typeParams) { + verifyGenericTypeParams(node.d.name, typeParams, genericTypeParams); + } + classType.shared.typeParams = genericTypeParams ?? typeParams; + + // Determine if one or more type parameters is autovariance. + if ( + classType.shared.typeParams.some( + (param) => + param.shared.declaredVariance === Variance.Auto && param.priv.computedVariance === undefined + ) + ) { + classType.shared.requiresVarianceInference = true; + } + + // Make sure there's at most one TypeVarTuple. + const variadics = typeParams.filter((param) => isTypeVarTuple(param)); + if (variadics.length > 1) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.variadicTypeParamTooManyClass().format({ + names: variadics.map((v) => `"${v.shared.name}"`).join(', '), + }), + node.d.name, + TextRange.combine(node.d.arguments) || node.d.name + ); + } else if (variadics.length > 0) { + // Make sure a TypeVar with a default doesn't come after a TypeVarTuple. + const firstVariadicIndex = typeParams.findIndex((param) => isTypeVarTuple(param)); + const typeVarWithDefaultIndex = typeParams.findIndex( + (param, index) => + index > firstVariadicIndex && !isParamSpec(param) && param.shared.isDefaultExplicit + ); + + if (typeVarWithDefaultIndex >= 0) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarWithDefaultFollowsVariadic().format({ + typeVarName: typeParams[typeVarWithDefaultIndex].shared.name, + variadicName: typeParams[firstVariadicIndex].shared.name, + }), + node.d.typeParams ? node.d.typeParams.d.params[typeVarWithDefaultIndex].d.name : node.d.name + ); + } + } + + // Validate the default types for all type parameters. + classType.shared.typeParams.forEach((typeParam, index) => { + let bestErrorNode: ExpressionNode = node.d.name; + if (node.d.typeParams && index < node.d.typeParams.d.params.length) { + const typeParamNode = node.d.typeParams.d.params[index]; + bestErrorNode = typeParamNode.d.defaultExpr ?? typeParamNode.d.name; + } + validateTypeParamDefault( + bestErrorNode, + typeParam, + classType.shared.typeParams.slice(0, index), + classType.shared.typeVarScopeId! + ); + }); + + if (!computeMroLinearization(classType)) { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.methodOrdering(), node.d.name); + } + + // The scope for this class becomes the "fields" for the corresponding type. + const innerScope = ScopeUtils.getScopeForNode(node.d.suite); + classType.shared.fields = innerScope?.symbolTable + ? new Map(innerScope.symbolTable) + : new Map(); + + // Determine whether the class should inherit __hash__. If a class defines + // __eq__ but doesn't define __hash__ then __hash__ is set to None. + if (classType.shared.fields.has('__eq__') && !classType.shared.fields.has('__hash__')) { + classType.shared.fields.set( + '__hash__', + Symbol.createWithType( + SymbolFlags.ClassMember | + SymbolFlags.ClassVar | + SymbolFlags.IgnoredForProtocolMatch | + SymbolFlags.IgnoredForOverrideChecks, + getNoneType() + ) + ); + } + + // Determine whether the class's instance variables are constrained + // to those defined by __slots__. We need to do this prior to dataclass + // processing because dataclasses can implicitly add to the slots + // list. + const slotsNames = innerScope?.getSlotsNames(); + if (slotsNames) { + classType.shared.localSlotsNames = slotsNames; + } + + // Determine if the class should be a "pseudo-generic" class, characterized + // by having an __init__ method with parameters that lack type annotations. + // For such classes, we'll treat them as generic, with the type arguments provided + // by the callers of the constructor. + if (!fileInfo.isStubFile && classType.shared.typeParams.length === 0) { + const initMethod = classType.shared.fields.get('__init__'); + if (initMethod) { + const initDecls = initMethod.getTypedDeclarations(); + if (initDecls.length === 1 && initDecls[0].type === DeclarationType.Function) { + const initDeclNode = initDecls[0].node; + const initParams = initDeclNode.d.params; + + if ( + initParams.length > 1 && + !initParams.some( + (param, index) => !!ParseTreeUtils.getTypeAnnotationForParam(initDeclNode, index) + ) + ) { + const genericParams = initParams.filter( + (param, index) => + index > 0 && + param.d.name && + param.d.category === ParamCategory.Simple && + !param.d.defaultValue + ); + + if (genericParams.length > 0) { + classType.shared.flags |= ClassTypeFlags.PseudoGenericClass; + + // Create a type parameter for each simple, named parameter + // in the __init__ method. + classType.shared.typeParams = genericParams.map((param) => { + const typeVar = TypeVarType.createInstance( + getPseudoGenericTypeVarName(param.d.name!.d.value) + ); + typeVar.shared.isSynthesized = true; + typeVar.priv.scopeId = ParseTreeUtils.getScopeIdForNode(initDeclNode); + typeVar.shared.boundType = UnknownType.create(); + return TypeVarType.cloneForScopeId( + typeVar, + ParseTreeUtils.getScopeIdForNode(node), + node.d.name.d.value, + TypeVarScopeType.Class + ); + }); + } + } + } + } + } + + // Determine if the class has a custom __class_getitem__ method. This applies + // only to classes that have no type parameters, since those with type parameters + // are assumed to follow normal subscripting semantics for generic classes. + if (classType.shared.typeParams.length === 0 && !ClassType.isBuiltIn(classType, 'type')) { + if ( + classType.shared.baseClasses.some( + (baseClass) => isInstantiableClass(baseClass) && ClassType.hasCustomClassGetItem(baseClass) + ) || + classType.shared.fields.has('__class_getitem__') + ) { + classType.shared.flags |= ClassTypeFlags.HasCustomClassGetItem; + } + } + + // Determine the effective metaclass. + if (metaclassNode) { + let metaclassType = getTypeOfExpression(metaclassNode, exprFlags).type; + if (isInstantiableClass(metaclassType) || isUnknown(metaclassType)) { + if (requiresSpecialization(metaclassType, { ignorePseudoGeneric: true })) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.metaclassIsGeneric(), + metaclassNode + ); + } + + // If the specified metaclass is type(T), use the metaclass + // of T if it's known. + if ( + TypeBase.getInstantiableDepth(metaclassType) > 0 && + isClass(metaclassType) && + metaclassType.shared.effectiveMetaclass && + isClass(metaclassType.shared.effectiveMetaclass) + ) { + metaclassType = metaclassType.shared.effectiveMetaclass; + } + + classType.shared.declaredMetaclass = metaclassType; + if (isInstantiableClass(metaclassType)) { + if (isEnumMetaclass(metaclassType)) { + classType.shared.flags |= ClassTypeFlags.EnumClass; + } + + if (derivesFromStdlibClass(metaclassType, 'ABCMeta')) { + classType.shared.flags |= ClassTypeFlags.SupportsAbstractMethods; + } + } + } + } + + const effectiveMetaclass = computeEffectiveMetaclass(classType, node.d.name); + + // Clear the "partially constructed" flag. + classType.shared.flags &= ~ClassTypeFlags.PartiallyEvaluated; + + // Now determine the decorated type of the class. + let decoratedType: Type = classType; + let foundUnknown = false; + + for (let i = node.d.decorators.length - 1; i >= 0; i--) { + const decorator = node.d.decorators[i]; + + const newDecoratedType = useSignatureTracker(node.parent ?? node, () => + applyClassDecorator(evaluatorInterface, decoratedType, classType, decorator) + ); + const unknownOrAny = containsAnyOrUnknown(newDecoratedType, /* recurse */ false); + + if (unknownOrAny && isUnknown(unknownOrAny)) { + // Report this error only on the first unknown type. + if (!foundUnknown) { + addDiagnostic( + DiagnosticRule.reportUntypedClassDecorator, + LocMessage.classDecoratorTypeUnknown(), + node.d.decorators[i].d.expr + ); + + foundUnknown = true; + } + } else { + // Apply the decorator only if the type is known. + decoratedType = newDecoratedType; + } + } + + // Determine whether this class derives from (or has a metaclass) that imbues + // it with dataclass-like behaviors. If so, we'll apply those here. + let dataClassBehaviors: DataClassBehaviors | undefined; + if (isInstantiableClass(effectiveMetaclass) && effectiveMetaclass.shared.classDataClassTransform) { + dataClassBehaviors = effectiveMetaclass.shared.classDataClassTransform; + } else { + const baseClassDataTransform = classType.shared.mro.find((mroClass) => { + return ( + isClass(mroClass) && + mroClass.shared.classDataClassTransform !== undefined && + !ClassType.isSameGenericClass(mroClass, classType) + ); + }); + + if (baseClassDataTransform) { + dataClassBehaviors = (baseClassDataTransform as ClassType).shared.classDataClassTransform; + } + } + + if (dataClassBehaviors) { + applyDataClassClassBehaviorOverrides( + evaluatorInterface, + node.d.name, + classType, + initSubclassArgs, + dataClassBehaviors + ); + } + + // Run any deferred class completions that depend on this class. + runDeferredClassCompletions(classType); + + // If there are any outstanding deferred class completions registered that + // were not removed by the call to runDeferredClassCompletions, assume that + // the current class may depend on them and register for deferred completion. + registerDeferredClassCompletion(node, /* dependsUpon */ undefined); + + // Synthesize TypedDict methods. + if (ClassType.isTypedDictClass(classType)) { + // TypedDict classes must derive only from other TypedDict classes. + let foundInvalidBaseClass = false; + const diag = new DiagnosticAddendum(); + + classType.shared.baseClasses.forEach((baseClass) => { + if ( + isClass(baseClass) && + !ClassType.isTypedDictClass(baseClass) && + !ClassType.isBuiltIn(baseClass, ['_TypedDict', 'TypedDictFallback', 'Generic']) + ) { + foundInvalidBaseClass = true; + diag.addMessage(LocAddendum.typedDictBaseClass().format({ type: baseClass.shared.name })); + } + }); + + if (foundInvalidBaseClass) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typedDictBaseClass() + diag.getString(), + node.d.name + ); + } + + synthesizeTypedDictClassMethods(evaluatorInterface, node, classType); + } + + // Synthesize dataclass methods. + if (ClassType.isDataClass(classType) || isNamedTupleSubclass) { + const skipSynthesizedInit = ClassType.isDataClassSkipGenerateInit(classType); + let hasExistingInitMethod = skipSynthesizedInit; + + // See if there's already a non-synthesized __init__ method. + // We shouldn't override it. + if (!skipSynthesizedInit) { + const initSymbol = classType.shared.fields.get('__init__'); + if (initSymbol && initSymbol.isClassMember()) { + hasExistingInitMethod = true; + } + } + + let skipSynthesizeHash = false; + const hashSymbol = classType.shared.fields.get('__hash__'); + + // If there is a hash symbol defined in the class (i.e. one that we didn't + // synthesize above), then we shouldn't synthesize a new one for the dataclass. + if (hashSymbol && hashSymbol.isClassMember() && !hashSymbol.getSynthesizedType()) { + skipSynthesizeHash = true; + } + + const synthesizeMethods = () => + synthesizeDataClassMethods( + evaluatorInterface, + node, + classType, + isNamedTupleSubclass, + skipSynthesizedInit, + hasExistingInitMethod, + skipSynthesizeHash + ); + + // If this is a NamedTuple subclass, immediately synthesize dataclass methods + // because we also need to update the MRO classes in this case. For regular + // dataclasses, we'll defer the method synthesis to avoid circular dependencies. + if (isNamedTupleSubclass) { + synthesizeMethods(); + } else { + classType.shared.synthesizeMethodsDeferred = () => { + delete classType.shared.synthesizeMethodsDeferred; + synthesizeMethods(); + }; + } + } + + // Build a complete list of all slots names defined by the class hierarchy. + // This needs to be done after dataclass processing. + classType.shared.calculateInheritedSlotsNamesDeferred = () => { + delete classType.shared.calculateInheritedSlotsNamesDeferred; + + if (classType.shared.localSlotsNames) { + let isLimitedToSlots = true; + const extendedSlotsNames = Array.from(classType.shared.localSlotsNames); + + classType.shared.baseClasses.forEach((baseClass) => { + if (isInstantiableClass(baseClass)) { + if ( + !ClassType.isBuiltIn(baseClass, 'object') && + !ClassType.isBuiltIn(baseClass, 'type') && + !ClassType.isBuiltIn(baseClass, 'Generic') + ) { + const inheritedSlotsNames = ClassType.getInheritedSlotsNames(baseClass); + if (inheritedSlotsNames) { + appendArray(extendedSlotsNames, inheritedSlotsNames); + } else { + isLimitedToSlots = false; + } + } + } else { + isLimitedToSlots = false; + } + }); + + if (isLimitedToSlots) { + classType.shared.inheritedSlotsNamesCached = extendedSlotsNames; + } + } + }; + + // If Any is defined using a class statement, treat it as a special form. + if (node.d.name.d.value === 'Any' && fileInfo.isTypingStubFile) { + decoratedType = AnyType.createSpecialForm(); + } + + // Update the undecorated class type. + writeTypeCache(node.d.name, { type: classType }, EvalFlags.None); + + // Update the decorated class type. + writeTypeCache(node, { type: decoratedType }, EvalFlags.None); + + return { classType, decoratedType }; + }); + } + + function buildTypeParamsFromTypeArgs(classType: ClassType): TypeVarType[] { + const typeParams: TypeVarType[] = []; + const typeArgs = classType.priv.typeArgs ?? []; + + typeArgs.forEach((typeArg, index) => { + if (isTypeVar(typeArg)) { + typeParams.push(typeArg); + return; + } + + // Synthesize a dummy type parameter. + const typeVar = TypeVarType.createInstance(`__P${index}`); + typeVar.shared.isSynthesized = true; + typeParams.push(typeVar); + }); + + return typeParams; + } + + // Determines whether the type parameters has a default that refers to another + // type parameter. If so, validates that it is in the list of "live" type + // parameters and updates the scope of the type parameter referred to in the + // default type expression. + function validateTypeParamDefault( + errorNode: ExpressionNode, + typeParam: TypeVarType, + otherLiveTypeParams: TypeVarType[], + scopeId: TypeVarScopeId + ) { + if (!typeParam.shared.isDefaultExplicit && !typeParam.shared.isSynthesized && !TypeVarType.isSelf(typeParam)) { + const typeVarWithDefault = otherLiveTypeParams.find( + (param) => param.shared.isDefaultExplicit && param.priv.scopeId === scopeId + ); + + if (typeVarWithDefault) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarWithoutDefault().format({ + name: typeParam.shared.name, + other: typeVarWithDefault.shared.name, + }), + errorNode + ); + } + return; + } + + const invalidTypeVars = new Set(); + validateTypeVarDefault(typeParam, otherLiveTypeParams, invalidTypeVars); + + // If we found one or more unapplied type variable, report an error. + if (invalidTypeVars.size > 0) { + const diag = new DiagnosticAddendum(); + invalidTypeVars.forEach((name) => { + diag.addMessage(LocAddendum.typeVarDefaultOutOfScope().format({ name })); + }); + + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarDefaultInvalidTypeVar().format({ + name: typeParam.shared.name, + }) + diag.getString(), + errorNode + ); + } + } + + function inferVarianceForClass(classType: ClassType): void { + if (!classType.shared.requiresVarianceInference) { + return; + } + + // Presumptively mark the variance inference as complete. This + // prevents potential recursion. + classType.shared.requiresVarianceInference = false; + + // Presumptively mark the computed variance to "unknown". We'll + // replace this below once the variance has been inferred. + classType.shared.typeParams.forEach((param) => { + if (param.shared.declaredVariance === Variance.Auto) { + param.priv.computedVariance = Variance.Unknown; + } + }); + + const dummyTypeObject = ClassType.createInstantiable( + '__varianceDummy', + '', + '', + Uri.empty(), + 0, + 0, + undefined, + undefined + ); + + classType.shared.typeParams.forEach((param, paramIndex) => { + // Skip TypeVarTuples and ParamSpecs. + if (isTypeVarTuple(param) || isParamSpec(param)) { + return; + } + + // Skip type variables without auto-variance. + if (param.shared.declaredVariance !== Variance.Auto) { + return; + } + + // Replace all type arguments with a dummy type except for the + // TypeVar of interest, which is replaced with an object instance. + const srcTypeArgs = classType.shared.typeParams.map((p, i) => { + if (isTypeVarTuple(p)) { + return p; + } + return i === paramIndex ? getObjectType() : dummyTypeObject; + }); + + // Replace all type arguments with a dummy type except for the + // TypeVar of interest, which is replaced with itself. + const destTypeArgs = classType.shared.typeParams.map((p, i) => { + return i === paramIndex || isTypeVarTuple(p) ? p : dummyTypeObject; + }); + + const srcType = ClassType.specialize(classType, srcTypeArgs); + const destType = ClassType.specialize(classType, destTypeArgs); + + const isDestSubtypeOfSrc = assignClassToSelf( + srcType, + destType, + Variance.Covariant, + /* ignoreBaseClassVariance */ false + ); + + let inferredVariance: Variance; + if (isDestSubtypeOfSrc) { + inferredVariance = Variance.Covariant; + } else { + const isSrcSubtypeOfDest = assignClassToSelf( + destType, + srcType, + Variance.Contravariant, + /* ignoreBaseClassVariance */ false + ); + if (isSrcSubtypeOfDest) { + inferredVariance = Variance.Contravariant; + } else { + inferredVariance = Variance.Invariant; + } + } + + // We assume here that we don't need to clone the type var object + // because it was already cloned when it was associated with this + // class scope. + classType.shared.typeParams[paramIndex].priv.computedVariance = inferredVariance; + }); + } + + function evaluateTypeParamList(node: TypeParameterListNode): TypeVarType[] { + const paramTypes: TypeVarType[] = []; + const typeParamScope = AnalyzerNodeInfo.getScope(node); + + node.d.params.forEach((param) => { + const paramSymbol = typeParamScope?.symbolTable.get(param.d.name.d.value); + if (!paramSymbol) { + // This can happen if the code is unreachable. + return; + } + + const typeOfParam = getDeclaredTypeOfSymbol(paramSymbol, param.d.name)?.type; + if (!typeOfParam || !isTypeVar(typeOfParam)) { + return; + } + + writeTypeCache(param.d.name, { type: typeOfParam }, EvalFlags.None); + paramTypes.push(typeOfParam); + }); + + return paramTypes; + } + + function computeEffectiveMetaclass(classType: ClassType, errorNode: ParseNode) { + let effectiveMetaclass = classType.shared.declaredMetaclass; + let reportedMetaclassConflict = false; + + if (!effectiveMetaclass || isInstantiableClass(effectiveMetaclass)) { + for (const baseClass of classType.shared.baseClasses) { + if (isInstantiableClass(baseClass)) { + const baseClassMeta = baseClass.shared.effectiveMetaclass ?? prefetched?.typeClass; + if (baseClassMeta && isInstantiableClass(baseClassMeta)) { + // Make sure there is no metaclass conflict. + if (!effectiveMetaclass) { + effectiveMetaclass = baseClassMeta; + } else if ( + derivesFromClassRecursive(baseClassMeta, effectiveMetaclass, /* ignoreUnknown */ false) + ) { + effectiveMetaclass = baseClassMeta; + } else if ( + !derivesFromClassRecursive(effectiveMetaclass, baseClassMeta, /* ignoreUnknown */ false) + ) { + if (!reportedMetaclassConflict) { + const diag = new DiagnosticAddendum(); + + diag.addMessage( + LocAddendum.metaclassConflict().format({ + metaclass1: printType(convertToInstance(effectiveMetaclass)), + metaclass2: printType(convertToInstance(baseClassMeta)), + }) + ); + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.metaclassConflict() + diag.getString(), + errorNode + ); + + // Don't report more than once. + reportedMetaclassConflict = true; + } + } + } else { + effectiveMetaclass = baseClassMeta ? UnknownType.create() : undefined; + break; + } + } else { + // If one of the base classes is unknown, then the effective + // metaclass is also unknowable. + effectiveMetaclass = UnknownType.create(); + break; + } + } + } + + // If we haven't found an effective metaclass, assume "type", which + // is the metaclass for "object". + if (!effectiveMetaclass) { + const typeMetaclass = getBuiltInType(errorNode, 'type'); + effectiveMetaclass = + typeMetaclass && isInstantiableClass(typeMetaclass) ? typeMetaclass : UnknownType.create(); + } + + classType.shared.effectiveMetaclass = effectiveMetaclass; + + return effectiveMetaclass; + } + + // Verifies that the type variables provided outside of "Generic" + // or "Protocol" are also provided within the "Generic". For example: + // class Foo(Mapping[K, V], Generic[V]) + // is illegal because K is not included in Generic. + function verifyGenericTypeParams( + errorNode: ExpressionNode, + typeVars: TypeVarType[], + genericTypeVars: TypeVarType[] + ) { + const missingFromGeneric = typeVars.filter((typeVar) => { + return !genericTypeVars.some((genericTypeVar) => genericTypeVar.shared.name === typeVar.shared.name); + }); + + if (missingFromGeneric.length > 0) { + const diag = new DiagnosticAddendum(); + diag.addMessage( + LocAddendum.typeVarsMissing().format({ + names: missingFromGeneric.map((typeVar) => `"${typeVar.shared.name}"`).join(', '), + }) + ); + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarsNotInGenericOrProtocol() + diag.getString(), + errorNode + ); + } + } + + // Records the fact that the specified class requires "deferred completion" because + // one of its base classes has not yet been fully evaluated. If the caller passes + // undefined for "dependsUpon", then the class is added to all outstanding deferred + // completions. + function registerDeferredClassCompletion(classToComplete: ClassNode, dependsUpon: ClassType | undefined) { + if (dependsUpon) { + // See if there is an existing entry for this dependency. + const entry = deferredClassCompletions.find((e) => + ClassType.isSameGenericClass(e.dependsUpon, dependsUpon) + ); + if (entry) { + entry.classesToComplete.push(classToComplete); + } else { + deferredClassCompletions.push({ dependsUpon, classesToComplete: [classToComplete] }); + } + } else { + deferredClassCompletions.forEach((e) => { + e.classesToComplete.push(classToComplete); + }); + } + } + + // Runs any registered "deferred class completions" that depend on the specified + // class type. This allows us to complete any work that requires dependent classes + // to be completed. + function runDeferredClassCompletions(type: ClassType) { + deferredClassCompletions.forEach((e) => { + if (ClassType.isSameGenericClass(e.dependsUpon, type)) { + e.classesToComplete.forEach((classNode) => { + const classType = readTypeCache(classNode.d.name, EvalFlags.None); + if (classType) { + completeClassTypeDeferred(classType as ClassType, classNode.d.name); + } + }); + } + }); + + // Remove any completions that depend on this type. + deferredClassCompletions = deferredClassCompletions.filter( + (e) => !ClassType.isSameGenericClass(e.dependsUpon, type) + ); + } + + // Recomputes the MRO and effective metaclass for the class after dependent + // classes have been fully constructed. + function completeClassTypeDeferred(type: ClassType, errorNode: ParseNode) { + // Recompute the MRO linearization. + if (!computeMroLinearization(type)) { + addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.methodOrdering(), errorNode); + } + + // Recompute the effective metaclass. + computeEffectiveMetaclass(type, errorNode); + } + + function validateInitSubclassArgs(node: ClassNode, classType: ClassType) { + // Collect arguments that will be passed to the `__init_subclass__` + // method described in PEP 487 and validate it. + const argList: Arg[] = []; + + node.d.arguments.forEach((arg) => { + if (arg.d.name && arg.d.name.d.value !== 'metaclass') { + argList.push({ + argCategory: ArgCategory.Simple, + node: arg, + name: arg.d.name, + valueExpression: arg.d.valueExpr, + }); + } + }); + + let newMethodMember: ClassMember | undefined; + + // See if the class has a metaclass that overrides `__new__`. If so, we + // will validate the signature of the `__new__` method. + if (classType.shared.effectiveMetaclass && isClass(classType.shared.effectiveMetaclass)) { + // If the metaclass is 'type' or 'ABCMeta', we'll assume it will call through to + // __init_subclass__, so we'll skip the `__new__` method check. We need to exclude + // TypedDict classes here because _TypedDict uses ABCMeta as its metaclass, but its + // typeshed definition doesn't override __init_subclass__. + const metaclassCallsInitSubclass = + ClassType.isBuiltIn(classType.shared.effectiveMetaclass, ['ABCMeta', 'type']) && + !ClassType.isTypedDictClass(classType); + + if (!metaclassCallsInitSubclass) { + // See if the metaclass has a `__new__` method that accepts keyword parameters. + newMethodMember = lookUpClassMember( + classType.shared.effectiveMetaclass, + '__new__', + MemberAccessFlags.SkipTypeBaseClass + ); + } + } + + if (newMethodMember) { + const newMethodType = getTypeOfMember(newMethodMember); + if (isFunction(newMethodType)) { + const paramListDetails = getParamListDetails(newMethodType); + + if (paramListDetails.firstKeywordOnlyIndex !== undefined) { + // Build a map of the keyword-only parameters. + const paramMap = new Map(); + for (let i = paramListDetails.firstKeywordOnlyIndex; i < paramListDetails.params.length; i++) { + const paramInfo = paramListDetails.params[i]; + if ( + paramInfo.param.category === ParamCategory.Simple && + paramInfo.param.name && + paramInfo.kind !== ParamKind.Positional + ) { + paramMap.set(paramInfo.param.name, i); + } + } + + argList.forEach((arg) => { + if (arg.argCategory === ArgCategory.Simple && arg.name) { + const paramIndex = paramMap.get(arg.name.d.value) ?? paramListDetails.kwargsIndex; + + if (paramIndex !== undefined) { + const paramInfo = paramListDetails.params[paramIndex]; + const argParam: ValidateArgTypeParams = { + paramCategory: paramInfo.param.category, + paramType: paramInfo.type, + requiresTypeVarMatching: false, + argument: arg, + errorNode: arg.valueExpression ?? node.d.name, + }; + + validateArgType( + argParam, + new ConstraintTracker(), + { type: newMethodType }, + { skipUnknownArgCheck: true } + ); + paramMap.delete(arg.name.d.value); + } else { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.paramNameMissing().format({ name: arg.name.d.value }), + arg.name ?? node.d.name + ); + } + } + }); + + // See if we have any remaining unmatched parameters without + // default values. + const unassignedParams: string[] = []; + paramMap.forEach((index, paramName) => { + const paramInfo = paramListDetails.params[index]; + if (!paramInfo.defaultType) { + unassignedParams.push(paramName); + } + }); + + if (unassignedParams.length > 0) { + const missingParamNames = unassignedParams.map((p) => `"${p}"`).join(', '); + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + unassignedParams.length === 1 + ? LocMessage.argMissingForParam().format({ name: missingParamNames }) + : LocMessage.argMissingForParams().format({ names: missingParamNames }), + node.d.name + ); + } + } + } + } else { + // If there was no custom metaclass __new__ method, see if there is an __init_subclass__ + // method present somewhere in the class hierarchy. + const initSubclassMethodInfo = getTypeOfBoundMember( + node.d.name, + classType, + '__init_subclass__', + /* usage */ undefined, + /* diag */ undefined, + MemberAccessFlags.SkipClassMembers | + MemberAccessFlags.SkipOriginalClass | + MemberAccessFlags.SkipAttributeAccessOverride + ); + + if (initSubclassMethodInfo) { + const initSubclassMethodType = initSubclassMethodInfo.type; + + if (initSubclassMethodType && initSubclassMethodInfo.classType) { + const callResult = validateCallArgs( + node.d.name, + argList, + { type: initSubclassMethodType }, + /* constraints */ undefined, + /* skipUnknownArgCheck */ false, + makeInferenceContext(getNoneType()) + ); + + if (callResult.argumentErrors) { + const diag = addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.initSubclassCallFailed(), + node.d.name + ); + + const initSubclassFunction = isOverloaded(initSubclassMethodType) + ? OverloadedType.getOverloads(initSubclassMethodType)[0] + : initSubclassMethodType; + const initSubclassDecl = isFunction(initSubclassFunction) + ? initSubclassFunction.shared.declaration + : undefined; + + if (diag && initSubclassDecl) { + diag.addRelatedInfo( + LocAddendum.initSubclassLocation().format({ + name: printType(convertToInstance(initSubclassMethodInfo.classType)), + }), + initSubclassDecl.uri, + initSubclassDecl.range + ); + } + } + } + } + } + + // Evaluate all of the expressions so they are checked and marked referenced. + argList.forEach((arg) => { + if (arg.valueExpression) { + getTypeOfExpression(arg.valueExpression); + } + }); + } + + function getTypeOfFunction(node: FunctionNode): FunctionTypeResult | undefined { + initializePrefetchedTypes(node); + + // Is this predecorated function type cached? + let functionType = readTypeCache(node.d.name, EvalFlags.None); + + if (functionType) { + if (!isFunction(functionType)) { + // This can happen in certain rare circumstances where the + // function declaration falls within an unreachable code block. + return undefined; + } + + if (FunctionType.isPartiallyEvaluated(functionType)) { + return { functionType, decoratedType: functionType }; + } + } else { + functionType = getTypeOfFunctionPredecorated(node); + } + + // Is the decorated function type cached? + let decoratedType = readTypeCache(node, EvalFlags.None); + if (decoratedType) { + return { functionType, decoratedType }; + } + + // Populate the cache with a temporary value to handle recursion. + writeTypeCache(node, { type: functionType }, /* flags */ undefined); + + // If it's an async function, wrap the return type in an Awaitable or Generator. + // Set the "partially evaluated" flag around this logic to detect recursion. + functionType.shared.flags |= FunctionTypeFlags.PartiallyEvaluated; + const preDecoratedType = node.d.isAsync ? createAsyncFunction(node, functionType) : functionType; + + // Apply all of the decorators in reverse order. + decoratedType = preDecoratedType; + let foundUnknown = false; + for (let i = node.d.decorators.length - 1; i >= 0; i--) { + const decorator = node.d.decorators[i]; + + const newDecoratedType = useSignatureTracker(node.parent ?? node, () => { + assert(decoratedType !== undefined); + return applyFunctionDecorator(evaluatorInterface, decoratedType, functionType, decorator, node); + }); + + const unknownOrAny = containsAnyOrUnknown(newDecoratedType, /* recurse */ false); + + if (unknownOrAny && isUnknown(unknownOrAny)) { + // Report this error only on the first unknown type. + if (!foundUnknown) { + addDiagnostic( + DiagnosticRule.reportUntypedFunctionDecorator, + LocMessage.functionDecoratorTypeUnknown(), + node.d.decorators[i].d.expr + ); + + foundUnknown = true; + } + } else { + // Apply the decorator only if the type is known. + decoratedType = newDecoratedType; + } + } + + // See if there are any overloads provided by previous function declarations. + if (isFunction(decoratedType)) { + decoratedType.shared.deprecatedMessage = functionType.shared.deprecatedMessage; + + if (FunctionType.isOverloaded(decoratedType)) { + // Mark all the parameters as accessed. + node.d.params.forEach((param) => { + markParamAccessed(param); + }); + } + } + + decoratedType = addOverloadsToFunctionType(evaluatorInterface, node, decoratedType); + + writeTypeCache(node, { type: decoratedType }, EvalFlags.None); + + // Now that the decorator has been applied, we can clear the + // "partially evaluated" flag. + functionType.shared.flags &= ~FunctionTypeFlags.PartiallyEvaluated; + + return { functionType, decoratedType }; + } + + // Evaluates the type of a "def" statement without applying an async + // modifier or any decorators. + function getTypeOfFunctionPredecorated(node: FunctionNode): FunctionType { + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + + // Is this type already cached? + const cachedFunctionType = readTypeCache(node.d.name, EvalFlags.None); + + if (cachedFunctionType && isFunction(cachedFunctionType)) { + return cachedFunctionType; + } + + let functionDecl: FunctionDeclaration | undefined; + const decl = AnalyzerNodeInfo.getDeclaration(node); + if (decl) { + functionDecl = decl as FunctionDeclaration; + } + + // There was no cached type, so create a new one. + // Retrieve the containing class node if the function is a method. + const containingClassNode = ParseTreeUtils.getEnclosingClass(node, /* stopAtFunction */ true); + let containingClassType: ClassType | undefined; + if (containingClassNode) { + containingClassType = getTypeOfClass(containingClassNode)?.classType; + } + + const functionInfo = getFunctionInfoFromDecorators(evaluatorInterface, node, !!containingClassNode); + let functionFlags = functionInfo.flags; + if (functionDecl?.isGenerator) { + functionFlags |= FunctionTypeFlags.Generator; + } + + if (fileInfo.isStubFile) { + functionFlags |= FunctionTypeFlags.StubDefinition; + } else if (fileInfo.isInPyTypedPackage) { + functionFlags |= FunctionTypeFlags.PyTypedDefinition; + } + + if (node.d.isAsync) { + functionFlags |= FunctionTypeFlags.Async; + } + + const functionType = FunctionType.createInstance( + node.d.name.d.value, + getFunctionFullName(node, fileInfo.moduleName, node.d.name.d.value), + fileInfo.moduleName, + functionFlags | FunctionTypeFlags.PartiallyEvaluated, + ParseTreeUtils.getDocString(node.d.suite.d.statements) + ); + + functionType.shared.typeVarScopeId = ParseTreeUtils.getScopeIdForNode(node); + functionType.shared.deprecatedMessage = functionInfo.deprecationMessage; + functionType.shared.methodClass = containingClassType; + + if (node.d.name.d.value === '__init__' || node.d.name.d.value === '__new__') { + if (containingClassNode) { + functionType.priv.constructorTypeVarScopeId = ParseTreeUtils.getScopeIdForNode(containingClassNode); + } + } + + if (fileInfo.isBuiltInStubFile || fileInfo.isTypingStubFile || fileInfo.isTypingExtensionsStubFile) { + // Mark the function as a built-in stdlib function. + functionType.shared.flags |= FunctionTypeFlags.BuiltIn; + } + + functionType.shared.declaration = functionDecl; + + // Allow recursion by caching and registering the partially-constructed function type. + const scope = ScopeUtils.getScopeForNode(node); + const functionSymbol = scope?.lookUpSymbolRecursive(node.d.name.d.value); + if (functionDecl && functionSymbol) { + setSymbolResolutionPartialType(functionSymbol.symbol, functionDecl, functionType); + } + + return invalidateTypeCacheIfCanceled(() => { + writeTypeCache(node.d.name, { type: functionType }, /* flags */ undefined); + + // Is this an "__init__" method within a pseudo-generic class? If so, + // we'll add generic types to the constructor's parameters. + const addGenericParamTypes = + containingClassType && + ClassType.isPseudoGenericClass(containingClassType) && + node.d.name.d.value === '__init__'; + + const paramTypes: Type[] = []; + + // Determine if the first parameter should be skipped for comment-based + // function annotations. + let firstCommentAnnotationIndex = 0; + if (containingClassType && (functionType.shared.flags & FunctionTypeFlags.StaticMethod) === 0) { + firstCommentAnnotationIndex = 1; + } + + // If there is a function annotation comment, validate that it has the correct + // number of parameter annotations. + if (node.d.funcAnnotationComment && !node.d.funcAnnotationComment.d.isEllipsis) { + const expected = node.d.params.length - firstCommentAnnotationIndex; + const received = node.d.funcAnnotationComment.d.paramAnnotations.length; + + // For methods with "self" or "cls" parameters, the annotation list + // can either include or exclude the annotation for the first parameter. + if (firstCommentAnnotationIndex > 0 && received === node.d.params.length) { + firstCommentAnnotationIndex = 0; + } else if (received !== expected) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.annotatedParamCountMismatch().format({ + expected, + received, + }), + node.d.funcAnnotationComment + ); + } + } + + // If this function uses PEP 695 syntax for type parameters, + // accumulate the list of type parameters upfront. + const typeParamsSeen: TypeVarType[] = []; + if (node.d.typeParams) { + functionType.shared.typeParams = evaluateTypeParamList(node.d.typeParams).map((typeParam) => + convertToInstance(typeParam) + ); + } else { + functionType.shared.typeParams = typeParamsSeen; + } + + let paramsArePositionOnly = true; + const isFirstParamClsOrSelf = + containingClassType && + (FunctionType.isClassMethod(functionType) || + FunctionType.isInstanceMethod(functionType) || + FunctionType.isConstructorMethod(functionType)); + const firstNonClsSelfParamIndex = isFirstParamClsOrSelf ? 1 : 0; + + node.d.params.forEach((param, index) => { + let paramType: Type | undefined; + let annotatedType: Type | undefined; + let paramTypeNode: ExpressionNode | undefined; + + if (param.d.name) { + if (index === 0 && isFirstParamClsOrSelf) { + // Mark "self/cls" as accessed. + markParamAccessed(param); + } else if (FunctionType.isAbstractMethod(functionType)) { + // Mark all parameters in abstract methods as accessed. + markParamAccessed(param); + } else if (containingClassType && ClassType.isProtocolClass(containingClassType)) { + // Mark all parameters in protocol methods as accessed. + markParamAccessed(param); + } + } + + if (param.d.annotation) { + paramTypeNode = param.d.annotation; + } else if (param.d.annotationComment) { + paramTypeNode = param.d.annotationComment; + } else if (node.d.funcAnnotationComment && !node.d.funcAnnotationComment.d.isEllipsis) { + const adjustedIndex = index - firstCommentAnnotationIndex; + if (adjustedIndex >= 0 && adjustedIndex < node.d.funcAnnotationComment.d.paramAnnotations.length) { + paramTypeNode = node.d.funcAnnotationComment.d.paramAnnotations[adjustedIndex]; + } + } + + if (paramTypeNode) { + if ((functionInfo.flags & FunctionTypeFlags.NoTypeCheck) !== 0) { + annotatedType = UnknownType.create(); + } else { + annotatedType = getTypeOfParamAnnotation(paramTypeNode, param.d.category); + } + + if (annotatedType) { + addTypeVarsToListIfUnique( + typeParamsSeen, + getTypeVarArgsRecursive(annotatedType), + functionType.shared.typeVarScopeId + ); + } + + if (isTypeVarTuple(annotatedType) && !annotatedType.priv.isUnpacked) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.unpackedTypeVarTupleExpected().format({ + name1: annotatedType.shared.name, + name2: annotatedType.shared.name, + }), + paramTypeNode + ); + annotatedType = UnknownType.create(); + } + } + + if (!annotatedType && addGenericParamTypes) { + if ( + index > 0 && + param.d.category === ParamCategory.Simple && + param.d.name && + !param.d.defaultValue + ) { + const typeParamName = getPseudoGenericTypeVarName(param.d.name.d.value); + annotatedType = containingClassType!.shared.typeParams.find( + (param) => param.shared.name === typeParamName + ); + } + } + + if (annotatedType) { + const adjustedAnnotatedType = adjustParamAnnotatedType(param, annotatedType); + if (adjustedAnnotatedType !== annotatedType) { + annotatedType = adjustedAnnotatedType; + } + } + + let defaultValueType: Type | undefined; + if (param.d.defaultValue) { + // If this is a stub file, a protocol, an overload, or a class + // whose body is a placeholder implementation, treat a "...", as + // an "Any" value. + let treatEllipsisAsAny = fileInfo.isStubFile || ParseTreeUtils.isSuiteEmpty(node.d.suite); + if (containingClassType && ClassType.isProtocolClass(containingClassType)) { + treatEllipsisAsAny = true; + } + if (FunctionType.isOverloaded(functionType) || FunctionType.isAbstractMethod(functionType)) { + treatEllipsisAsAny = true; + } + + defaultValueType = getTypeOfExpression( + param.d.defaultValue, + treatEllipsisAsAny ? EvalFlags.ConvertEllipsisToAny : EvalFlags.None, + makeInferenceContext(annotatedType) + ).type; + } + + if (annotatedType) { + // If there was both a type annotation and a default value, verify + // that the default value matches the annotation. + if (param.d.defaultValue && defaultValueType) { + const diagAddendum = new DiagnosticAddendum(); + + if (!assignType(annotatedType, defaultValueType, diagAddendum)) { + addDiagnostic( + DiagnosticRule.reportArgumentType, + LocMessage.paramAssignmentMismatch().format({ + sourceType: printType(defaultValueType), + paramType: printType(annotatedType), + }) + diagAddendum.getString(), + param.d.defaultValue + ); + } + } + + paramType = annotatedType; + } + + // Determine whether we need to insert an implied position-only parameter. + // This is needed when a function's parameters are named using the old-style + // way of specifying position-only parameters. + if (index >= firstNonClsSelfParamIndex) { + let isImplicitPositionOnlyParam = false; + + if (param.d.category === ParamCategory.Simple && param.d.name) { + if ( + isPrivateName(param.d.name.d.value) && + !node.d.params.some((p) => p.d.category === ParamCategory.Simple && !p.d.name) + ) { + isImplicitPositionOnlyParam = true; + + // If the parameter name indicates an implicit position-only parameter + // but we have already seen non-position-only parameters, report an error. + if ( + !paramsArePositionOnly && + functionType.shared.parameters.every((p) => p.category === ParamCategory.Simple) + ) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.positionOnlyAfterNon(), + param.d.name + ); + } + } + } else { + paramsArePositionOnly = false; + } + + if ( + paramsArePositionOnly && + !isImplicitPositionOnlyParam && + functionType.shared.parameters.length > firstNonClsSelfParamIndex + ) { + FunctionType.addPositionOnlyParamSeparator(functionType); + } + + if (!isImplicitPositionOnlyParam) { + paramsArePositionOnly = false; + } + } + + // If there was no annotation for the parameter, infer its type if possible. + let isTypeInferred = false; + if (!paramTypeNode) { + isTypeInferred = true; + const inferredType = inferParamType(node, functionType.shared.flags, index, containingClassType); + if (inferredType) { + paramType = inferredType; + } + } + + paramType = paramType ?? UnknownType.create(); + + const functionParam = FunctionParam.create( + param.d.category, + paramType, + (isTypeInferred ? FunctionParamFlags.TypeInferred : FunctionParamFlags.None) | + (paramTypeNode ? FunctionParamFlags.TypeDeclared : FunctionParamFlags.None), + param.d.name ? param.d.name.d.value : undefined, + defaultValueType, + param.d.defaultValue + ); + + FunctionType.addParam(functionType, functionParam); + + if (FunctionParam.isTypeDeclared(functionParam)) { + addTypeVarsToListIfUnique( + typeParamsSeen, + getTypeVarArgsRecursive(paramType), + functionType.shared.typeVarScopeId + ); + } + + if (param.d.name) { + const variadicParamType = transformVariadicParamType(node, param.d.category, paramType); + paramTypes.push(variadicParamType); + } else { + paramTypes.push(paramType); + } + }); + + if (paramsArePositionOnly && functionType.shared.parameters.length > firstNonClsSelfParamIndex) { + FunctionType.addPositionOnlyParamSeparator(functionType); + } + + // Update the types for the nodes associated with the parameters. + const scopeIds = ParseTreeUtils.getTypeVarScopesForNode(node); + paramTypes.forEach((paramType, index) => { + const paramNameNode = node.d.params[index].d.name; + if (paramNameNode) { + if (isUnknown(paramType)) { + functionType.shared.flags |= FunctionTypeFlags.UnannotatedParams; + } + + paramType = makeTypeVarsBound(paramType, scopeIds); + + writeTypeCache(paramNameNode, { type: paramType }, EvalFlags.None); + } + }); + + // If the function ends in P.args and P.kwargs parameters, make it exempt from + // args/kwargs compatibility checks. This is important for protocol comparisons. + if (paramTypes.length >= 2) { + const paramType1 = paramTypes[paramTypes.length - 2]; + const paramType2 = paramTypes[paramTypes.length - 1]; + if ( + isParamSpec(paramType1) && + paramType1.priv.paramSpecAccess === 'args' && + isParamSpec(paramType2) && + paramType2.priv.paramSpecAccess === 'kwargs' + ) { + functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm; + } + } + + // If the function contains an *args and a **kwargs parameter and both + // are annotated as Any or are unannotated, make it exempt from + // args/kwargs compatibility checks. + const variadicsWithAnyType = functionType.shared.parameters.filter( + (param, index) => + param.category !== ParamCategory.Simple && + param.name && + isAnyOrUnknown(FunctionType.getParamType(functionType, index)) + ); + if (variadicsWithAnyType.length >= 2) { + functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm; + } + + // If there was a defined return type, analyze that first so when we + // walk the contents of the function, return statements can be + // validated against this type. + const returnTypeAnnotationNode = + node.d.returnAnnotation ?? node.d.funcAnnotationComment?.d.returnAnnotation; + if (returnTypeAnnotationNode) { + // Temporarily set the return type to unknown in case of recursion. + functionType.shared.declaredReturnType = UnknownType.create(); + + const returnType = getTypeOfAnnotation(returnTypeAnnotationNode, { + typeVarGetsCurScope: true, + }); + functionType.shared.declaredReturnType = returnType; + } else { + // If there was no return type annotation and this is a type stub, + // we have no opportunity to infer the return type, so we'll indicate + // that it's unknown. + if (fileInfo.isStubFile) { + // Special-case the __init__ method, which is commonly left without + // an annotated return type, but we can assume it returns None. + if (node.d.name.d.value === '__init__') { + functionType.shared.declaredReturnType = getNoneType(); + } else { + functionType.shared.declaredReturnType = UnknownType.create(); + } + } else if (IsViperDialect(fileInfo.executionEnvironment.dialect)) { + // Viper 扩展:@c.CReturn(T1, T2, ...) 装饰的函数返回 tuple[T1, T2, ...]。 + // CReturn 是多返回值装饰器,参数为各返回值的类型;无注解时据此构造 tuple 返回类型, + // 使 return (a, b, c) 按位置检查每个返回值的类型。 + let cReturnTupleType: Type | undefined; + if (node.d.decorators) { + for (const decorator of node.d.decorators) { + const expr = decorator.d.expr; + if ( + expr.nodeType === ParseNodeType.Call && + expr.d.leftExpr.nodeType === ParseNodeType.MemberAccess && + expr.d.leftExpr.d.member.d.value === 'CReturn' + ) { + const tupleTypeArgs: TupleTypeArg[] = []; + for (const arg of expr.d.args) { + const argType = getTypeOfExpression(arg.d.valueExpr, EvalFlags.None).type; + const instanceType = isInstantiableClass(argType) + ? convertToInstance(argType) + : argType; + tupleTypeArgs.push({ type: instanceType, isUnbounded: false }); + } + const tupleClass = getBuiltInType(node, 'tuple'); + if (isInstantiableClass(tupleClass) && tupleTypeArgs.length > 0) { + // specializeTupleClass 返回 instantiable(type[tuple[...]]), + // 需 convertToInstance 转为实例 tuple[...] 以匹配 return (a, b, c) 的实例类型。 + cReturnTupleType = convertToInstance( + specializeTupleClass( + tupleClass, + tupleTypeArgs, + /* isUnbounded */ false + ) + ); + } + break; + } + } + } + if (cReturnTupleType) { + functionType.shared.declaredReturnType = cReturnTupleType; + } else if (node.d.name.d.value === '__new__' && functionType.shared.methodClass) { + // Viper 扩展:`__new__` 无返回注解时,默认期望返回 `T | t.CPtr`。 + // Viper 中 `__new__` 负责分配对象并返回裸指针(如 `_list.vp` 的 + // `def __new__(self, pool, elem_size=0): return pool.alloc(48)`, + // pool.alloc 返回 `t.CVoid | t.CPtr`)。 + // 返回类型设为「类实例 | 指针」的联合,使 `pool.alloc(...)` 这类 + // 裸指针返回可通过同级指针隐式转换合法化,不再报 + // "类型不可分配给返回类型 None"。 + const methodClassInstance = convertToInstance(functionType.shared.methodClass); + const cptrType = getViperCPtrType(node.d.name); + if (cptrType) { + functionType.shared.declaredReturnType = combineTypes([ + methodClassInstance, + cptrType, + ]); + } else { + functionType.shared.declaredReturnType = methodClassInstance; + } + } else { + // 无返回类型注解的函数默认返回 None(显示为 CVoid)。 + // C 语义:空返回的函数等价于 void 函数。 + functionType.shared.declaredReturnType = getNoneType(); + } + } + } + + // Accumulate any type parameters used in the return type. + if (functionType.shared.declaredReturnType && returnTypeAnnotationNode) { + addTypeVarsToListIfUnique( + typeParamsSeen, + getTypeVarArgsRecursive(functionType.shared.declaredReturnType), + functionType.shared.typeVarScopeId + ); + } + + // Validate the default types for all type parameters. + functionType.shared.typeParams.forEach((typeParam, index) => { + let bestErrorNode: ExpressionNode = node.d.name; + if (node.d.typeParams && index < node.d.typeParams.d.params.length) { + const typeParamNode = node.d.typeParams.d.params[index]; + bestErrorNode = typeParamNode.d.defaultExpr ?? typeParamNode.d.name; + } + + validateTypeParamDefault( + bestErrorNode, + typeParam, + functionType.shared.typeParams.slice(0, index), + functionType.shared.typeVarScopeId! + ); + }); + + // Clear the "partially evaluated" flag to indicate that the functionType + // is fully evaluated. + functionType.shared.flags &= ~FunctionTypeFlags.PartiallyEvaluated; + + writeTypeCache(node.d.name, { type: functionType }, EvalFlags.None); + + return functionType; + }); + } + + function markParamAccessed(param: ParameterNode) { + if (param.d.name) { + const symbolWithScope = lookUpSymbolRecursive( + param.d.name, + param.d.name.d.value, + /* honorCodeFlow */ false + ); + if (symbolWithScope) { + setSymbolAccessed(AnalyzerNodeInfo.getFileInfo(param), symbolWithScope.symbol, param.d.name); + } + } + } + + function adjustParamAnnotatedType(param: ParameterNode, type: Type): Type { + // PEP 484 indicates that if a parameter has a default value of 'None' + // the type checker should assume that the type is optional (i.e. a union + // of the specified type and 'None'). Skip this step if the type is already + // optional to avoid losing alias names when combining the types. + if ( + param.d.defaultValue?.nodeType === ParseNodeType.Constant && + param.d.defaultValue.d.constType === KeywordType.None && + !isOptionalType(type) && + !AnalyzerNodeInfo.getFileInfo(param).diagnosticRuleSet.strictParameterNoneValue + ) { + return combineTypes([type, getNoneType()]); + } + + return type; + } + + // Attempts to infer an unannotated parameter type from available context. + function inferParamType( + functionNode: FunctionNode, + functionFlags: FunctionTypeFlags, + paramIndex: number, + containingClassType: ClassType | undefined + ) { + // Is the function a method within a class? If so, see if a base class + // defines the same method and provides annotations. + if (containingClassType) { + if (paramIndex === 0) { + if ((functionFlags & FunctionTypeFlags.StaticMethod) === 0) { + const hasClsParam = + (functionFlags & (FunctionTypeFlags.ClassMethod | FunctionTypeFlags.ConstructorMethod)) !== 0; + return synthesizeTypeVarForSelfCls(containingClassType, hasClsParam); + } + } + + const methodName = functionNode.d.name.d.value; + + const baseClassMemberInfo = lookUpClassMember( + containingClassType, + methodName, + MemberAccessFlags.SkipOriginalClass + ); + + if (baseClassMemberInfo) { + const memberDecls = baseClassMemberInfo.symbol.getDeclarations(); + if (memberDecls.length === 1 && memberDecls[0].type === DeclarationType.Function) { + const baseClassMethodNode = memberDecls[0].node; + + // Does the signature match exactly with the exception of annotations? + if ( + baseClassMethodNode.d.params.length === functionNode.d.params.length && + baseClassMethodNode.d.params.every((param, index) => { + const overrideParam = functionNode.d.params[index]; + return ( + overrideParam.d.name?.d.value === param.d.name?.d.value && + overrideParam.d.category === param.d.category + ); + }) + ) { + const baseClassParam = baseClassMethodNode.d.params[paramIndex]; + const baseClassParamAnnotation = + baseClassParam.d.annotation ?? baseClassParam.d.annotationComment; + if (baseClassParamAnnotation) { + let inferredParamType = getTypeOfParamAnnotation( + baseClassParamAnnotation, + functionNode.d.params[paramIndex].d.category + ); + + // If the parameter type is generic, specialize it in the context + // of the child class. + if (requiresSpecialization(inferredParamType) && isClass(baseClassMemberInfo.classType)) { + const scopeIds: TypeVarScopeId[] = getTypeVarScopeIds(baseClassMemberInfo.classType); + const solution = buildSolutionFromSpecializedClass(baseClassMemberInfo.classType); + + scopeIds.push(ParseTreeUtils.getScopeIdForNode(baseClassMethodNode)); + + // Replace any unsolved TypeVars with Unknown (including all function-scoped TypeVars). + inferredParamType = applySolvedTypeVars(inferredParamType, solution, { + replaceUnsolved: { + scopeIds, + tupleClassType: getTupleClassType(), + }, + }); + } + + const fileInfo = AnalyzerNodeInfo.getFileInfo(functionNode); + if (fileInfo.isInPyTypedPackage && !fileInfo.isStubFile) { + inferredParamType = TypeBase.cloneForAmbiguousType(inferredParamType); + } + + return inferredParamType; + } + } + } + } + } + + // If the parameter has a default argument value, we may be able to infer its + // type from this information. + const paramValueExpr = functionNode.d.params[paramIndex].d.defaultValue; + if (paramValueExpr) { + return inferParamTypeFromDefaultValue(paramValueExpr); + } + + return undefined; + } + + function inferParamTypeFromDefaultValue(paramValueExpr: ExpressionNode) { + const defaultValueType = getTypeOfExpression(paramValueExpr, EvalFlags.ConvertEllipsisToAny).type; + + let inferredParamType: Type | undefined; + + // Is the default value a "None", a sentinel, or an instance of some private + // class (one whose name starts with an underscore)? If so, we will assume + // that the value is a singleton sentinel. The actual supported type is + // going to be a union of this type and Unknown. + if ( + isNoneInstance(defaultValueType) || + isSentinelLiteral(defaultValueType) || + (isClassInstance(defaultValueType) && isPrivateOrProtectedName(defaultValueType.shared.name)) + ) { + inferredParamType = combineTypes([defaultValueType, UnknownType.create()]); + } else { + let skipInference = false; + + if (isFunctionOrOverloaded(defaultValueType)) { + // Do not infer parameter types that use a lambda or another function as a + // default value. We're likely to generate false positives in this case. + // It's not clear whether parameters should be positional-only or not. + skipInference = true; + } else if ( + isClassInstance(defaultValueType) && + ClassType.isBuiltIn(defaultValueType, ['tuple', 'list', 'set', 'dict']) + ) { + // Do not infer certain types like tuple because it's likely to be + // more restrictive (narrower) than intended. + skipInference = true; + } + + if (!skipInference) { + inferredParamType = convertSpecialFormToRuntimeValue( + defaultValueType, + EvalFlags.None, + /* convertModule */ true + ); + inferredParamType = stripTypeForm(inferredParamType); + inferredParamType = stripLiteralValue(inferredParamType); + } + } + + if (inferredParamType) { + const fileInfo = AnalyzerNodeInfo.getFileInfo(paramValueExpr); + if (fileInfo.isInPyTypedPackage && !fileInfo.isStubFile) { + inferredParamType = TypeBase.cloneForAmbiguousType(inferredParamType); + } + } + + return inferredParamType; + } + + // Transforms the parameter type based on its category. If it's a simple parameter, + // no transform is applied. If it's a var-arg or keyword-arg parameter, the type + // is wrapped in a List or Dict. + function transformVariadicParamType(node: ParseNode, paramCategory: ParamCategory, type: Type): Type { + switch (paramCategory) { + case ParamCategory.Simple: { + return type; + } + + case ParamCategory.ArgsList: { + if (isParamSpec(type) && type.priv.paramSpecAccess) { + return type; + } + + if (isUnpackedClass(type)) { + return ClassType.cloneForPacked(type); + } + + return makeTupleObject(evaluatorInterface, [{ type, isUnbounded: !isTypeVarTuple(type) }]); + } + + case ParamCategory.KwargsDict: { + // Leave a ParamSpec alone. + if (isParamSpec(type) && type.priv.paramSpecAccess) { + return type; + } + + // Is this an unpacked TypedDict? If so, return its packed version. + if (isClassInstance(type) && ClassType.isTypedDictClass(type) && type.priv.isUnpacked) { + return ClassType.cloneForPacked(type); + } + + // Wrap the type in a dict with str keys. + const dictType = getBuiltInType(node, 'dict'); + const strType = getBuiltInObject(node, 'str'); + + if (isInstantiableClass(dictType) && isClassInstance(strType)) { + return ClassType.cloneAsInstance(ClassType.specialize(dictType, [strType, type])); + } + + return UnknownType.create(); + } + } + } + + function createAsyncFunction(node: FunctionNode, functionType: FunctionType): FunctionType { + assert(FunctionType.isAsync(functionType)); + + // Clone the original function and replace its return type with an + // Awaitable[]. Mark the new function as no longer async. + const awaitableFunctionType = FunctionType.cloneWithNewFlags( + functionType, + functionType.shared.flags & ~(FunctionTypeFlags.Async | FunctionTypeFlags.PartiallyEvaluated) + ); + + if (functionType.shared.declaredReturnType) { + awaitableFunctionType.shared.declaredReturnType = createAwaitableReturnType( + node, + functionType.shared.declaredReturnType, + FunctionType.isGenerator(functionType) + ); + } else { + awaitableFunctionType.shared.inferredReturnType = { + type: createAwaitableReturnType( + node, + getInferredReturnType(functionType), + FunctionType.isGenerator(functionType) + ), + }; + } + + return awaitableFunctionType; + } + + function createAwaitableReturnType( + node: ParseNode, + returnType: Type, + isGenerator: boolean, + useCoroutine = true + ): Type { + let awaitableReturnType: Type | undefined; + + if (isClassInstance(returnType)) { + if (ClassType.isBuiltIn(returnType)) { + if (returnType.shared.name === 'Generator') { + // If the return type is a Generator, change it to an AsyncGenerator. + const asyncGeneratorType = getTypingType(node, 'AsyncGenerator'); + if (asyncGeneratorType && isInstantiableClass(asyncGeneratorType)) { + const typeArgs: Type[] = []; + const generatorTypeArgs = returnType.priv.typeArgs; + if (generatorTypeArgs && generatorTypeArgs.length > 0) { + typeArgs.push(generatorTypeArgs[0]); + } + if (generatorTypeArgs && generatorTypeArgs.length > 1) { + typeArgs.push(generatorTypeArgs[1]); + } + awaitableReturnType = ClassType.cloneAsInstance( + ClassType.specialize(asyncGeneratorType, typeArgs) + ); + } + } else if (['AsyncIterator', 'AsyncIterable'].some((name) => name === returnType.shared.name)) { + // If it's already an AsyncIterator or AsyncIterable, leave it as is. + awaitableReturnType = returnType; + } else if (returnType.shared.name === 'AsyncGenerator') { + // If it's already an AsyncGenerator and the function is a generator, + // leave it as is. + if (isGenerator) { + awaitableReturnType = returnType; + } + } + } + } + + if (!awaitableReturnType || !isGenerator) { + // Wrap in either an Awaitable or a CoroutineType, which is a subclass of Awaitable. + const awaitableType = useCoroutine ? getTypesType(node, 'CoroutineType') : getTypingType(node, 'Awaitable'); + if (awaitableType && isInstantiableClass(awaitableType)) { + awaitableReturnType = ClassType.cloneAsInstance( + ClassType.specialize( + awaitableType, + useCoroutine ? [AnyType.create(), AnyType.create(), returnType] : [returnType] + ) + ); + } else { + awaitableReturnType = UnknownType.create(); + } + } + + return awaitableReturnType; + } + + function inferFunctionReturnType( + node: FunctionNode, + isAbstract: boolean, + callerNode: ExpressionNode | undefined + ): TypeResult | undefined { + const returnAnnotation = node.d.returnAnnotation || node.d.funcAnnotationComment?.d.returnAnnotation; + + // This shouldn't be called if there is a declared return type, but it + // can happen if there are unexpected cycles between decorators and + // classes that they decorate. We'll just return an undefined type + // in this case. + if (returnAnnotation) { + return undefined; + } + + // Is this type already cached? + let inferredReturnType = readTypeCache(node.d.suite, EvalFlags.None); + let isIncomplete = false; + + if (inferredReturnType) { + return { type: inferredReturnType, isIncomplete }; + } + + const recursionEntry = functionRecursionMap.get(node.id) ?? []; + + if (functionRecursionMap.size >= maxInferFunctionReturnRecursionCount) { + inferredReturnType = UnknownType.create(); + isIncomplete = true; + } else if (recursionEntry.some((entry) => entry.callerNode === callerNode)) { + inferredReturnType = UnknownType.create(); + isIncomplete = true; + } else { + recursionEntry.push({ callerNode }); + functionRecursionMap.set(node.id, recursionEntry); + + try { + let functionDecl: FunctionDeclaration | undefined; + const decl = AnalyzerNodeInfo.getDeclaration(node); + if (decl) { + functionDecl = decl as FunctionDeclaration; + } + + const functionNeverReturns = !isAfterNodeReachable(node); + const implicitlyReturnsNone = isAfterNodeReachable(node.d.suite); + + // Infer the return type based on all of the return statements in the function's body. + if (AnalyzerNodeInfo.getFileInfo(node).isStubFile) { + // If a return type annotation is missing in a stub file, assume + // it's an "unknown" type. In normal source files, we can infer the + // type from the implementation. + inferredReturnType = UnknownType.create(); + } else { + if (functionNeverReturns) { + // If the function always raises and never returns, assume a "NoReturn" type. + // Skip this for abstract methods which often are implemented with "raise + // NotImplementedError()". + if (isAbstract || methodAlwaysRaisesNotImplemented(functionDecl)) { + inferredReturnType = UnknownType.create(); + } else { + inferredReturnType = NeverType.createNoReturn(); + } + } else { + const inferredReturnTypes: Type[] = []; + if (functionDecl?.returnStatements) { + functionDecl.returnStatements.forEach((returnNode) => { + if (isNodeReachable(returnNode)) { + if (returnNode.d.expr) { + const returnTypeResult = getTypeOfExpression(returnNode.d.expr); + if (returnTypeResult.isIncomplete) { + isIncomplete = true; + } + + let returnType = returnTypeResult.type; + + // If the type is a special form, use the special form instead. + if (returnType.props?.specialForm) { + returnType = returnType.props.specialForm; + } + + // If the return type includes an instance of a class with isEmptyContainer + // set, clear that because we don't want this flag to "leak" into the + // inferred return type. + returnType = mapSubtypes(returnType, (subtype) => { + if (isClassInstance(subtype) && subtype.priv.isEmptyContainer) { + return ClassType.specialize( + subtype, + subtype.priv.typeArgs, + !!subtype.priv.isTypeArgExplicit, + subtype.priv.includeSubclasses, + subtype.priv.tupleTypeArgs, + /* isEmptyContainer */ false + ); + } + return subtype; + }); + + // Do not retain TypeForm types in inferred return types. + returnType = stripTypeForm(returnType); + + // If we're returning a function value (or overload), force + // lazy return-type inference before caching this function's + // inferred return. Without this, a partially evaluated + // function can leak a temporary Any return type (for example, + // immediately after an edit), and that Any then gets cached + // as though it were final for the enclosing function. + inferReturnTypeIfNecessary(returnType); + + inferredReturnTypes.push(returnType); + } else { + inferredReturnTypes.push(getNoneType()); + } + } + }); + } + + if (!functionNeverReturns && implicitlyReturnsNone) { + inferredReturnTypes.push(getNoneType()); + } + + inferredReturnType = combineTypes(inferredReturnTypes); + + // Remove any unbound values since those would generate an exception + // before being returned. + inferredReturnType = removeUnbound(inferredReturnType); + } + + // Is it a generator? + if (functionDecl?.isGenerator) { + const inferredYieldTypes: Type[] = []; + let useAwaitableGenerator = false; + let isYieldResultUsed = false; + + if (functionDecl.yieldStatements) { + functionDecl.yieldStatements.forEach((yieldNode) => { + if (isNodeReachable(yieldNode)) { + if (yieldNode.nodeType === ParseNodeType.YieldFrom) { + isYieldResultUsed = true; + const iteratorTypeResult = getTypeOfExpression(yieldNode.d.expr); + if ( + isClassInstance(iteratorTypeResult.type) && + ClassType.isBuiltIn(iteratorTypeResult.type, ['Coroutine', 'CoroutineType']) + ) { + const yieldType = + iteratorTypeResult.type.priv.typeArgs && + iteratorTypeResult.type.priv.typeArgs.length > 0 + ? iteratorTypeResult.type.priv.typeArgs[0] + : UnknownType.create(); + + // Handle old-style (pre-await) Coroutines. + inferredYieldTypes.push(yieldType); + useAwaitableGenerator = true; + } else { + const yieldType = getTypeOfIterator( + iteratorTypeResult, + /* isAsync */ false, + yieldNode + )?.type; + + inferredYieldTypes.push(yieldType ?? UnknownType.create()); + } + } else { + // If the yield expression is not by itself in a statement list, + // assume that its result is consumed. + if (yieldNode?.parent?.nodeType !== ParseNodeType.StatementList) { + isYieldResultUsed = true; + } + + if (yieldNode.d.expr) { + const yieldType = getTypeOfExpression(yieldNode.d.expr).type; + inferredYieldTypes.push(yieldType ?? UnknownType.create()); + } else { + inferredYieldTypes.push(getNoneType()); + } + } + } + }); + } + + const inferredYieldType = combineTypes(inferredYieldTypes); + + // Inferred yield types need to be wrapped in a Generator or + // AwaitableGenerator to produce the final result. + const generatorType = useAwaitableGenerator + ? getTypeCheckerInternalsType(node, 'AwaitableGenerator') ?? + getTypingType(node, 'AwaitableGenerator') + : getTypingType(node, 'Generator'); + + if (generatorType && isInstantiableClass(generatorType)) { + const typeArgs: Type[] = []; + + // The "send type" for the generator (the second type argument) is + // not generally inferrable, but we can assume that it's Any + // if the function never uses the value and Unknown if it does. + // This eliminates any "partially unknown" errors in strict mode + // in the common case. + const sendType = isYieldResultUsed ? UnknownType.create() : AnyType.create(); + + typeArgs.push(inferredYieldType, sendType, inferredReturnType); + + if (useAwaitableGenerator) { + typeArgs.push(AnyType.create()); + } + + inferredReturnType = ClassType.cloneAsInstance( + ClassType.specialize(generatorType, typeArgs) + ); + } else { + inferredReturnType = UnknownType.create(); + } + } + } + + writeTypeCache(node.d.suite, { type: inferredReturnType, isIncomplete }, EvalFlags.None); + } catch (err) { + // Attempt to handle a stack overflow without crashing. In rare + // cases, we can get very deep stacks when inferring return types + // within untyped code. + if ((err as any)?.message === 'Maximum call stack size exceeded') { + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + console.error( + `Overflowed stack when inferring return type for function: ${ + node.d.name.d.value + } in file ${fileInfo.fileUri.toUserVisibleString()}` + ); + return; + } + throw err; + } finally { + recursionEntry.pop(); + if (recursionEntry.length === 0) { + functionRecursionMap.delete(node.id); + } + } + } + + return inferredReturnType ? { type: inferredReturnType, isIncomplete } : undefined; + } + + // Determines whether the method consists only of a "raise" statement + // and the exception type raised is a NotImplementedError or a subclass + // thereof. This is commonly used for abstract methods. + function methodAlwaysRaisesNotImplemented(functionDecl?: FunctionDeclaration): boolean { + if ( + !functionDecl || + !functionDecl.isMethod || + functionDecl.returnStatements || + functionDecl.yieldStatements || + !functionDecl.raiseStatements + ) { + return false; + } + + const statements = functionDecl.node.d.suite.d.statements; + if (statements.some((statement) => statement.nodeType !== ParseNodeType.StatementList)) { + return false; + } + + for (const raiseStatement of functionDecl.raiseStatements) { + if (!raiseStatement.d.expr || raiseStatement.d.fromExpr) { + return false; + } + const raiseType = getTypeOfExpression(raiseStatement.d.expr).type; + const classType = isInstantiableClass(raiseType) + ? raiseType + : isClassInstance(raiseType) + ? raiseType + : undefined; + if (!classType || !derivesFromStdlibClass(classType, 'NotImplementedError')) { + return false; + } + } + + return true; + } + + function evaluateTypesForForStatement(node: ForNode): void { + if (isTypeCached(node)) { + return; + } + + const iteratorTypeResult = getTypeOfExpression(node.d.iterableExpr); + const iteratedType = + getTypeOfIterator(iteratorTypeResult, !!node.d.isAsync, node.d.iterableExpr)?.type ?? UnknownType.create(); + + assignTypeToExpression( + node.d.targetExpr, + { type: iteratedType, isIncomplete: iteratorTypeResult.isIncomplete }, + node.d.targetExpr + ); + + writeTypeCache(node, { type: iteratedType, isIncomplete: !!iteratorTypeResult.isIncomplete }, EvalFlags.None); + } + + function evaluateTypesForExceptStatement(node: ExceptNode): void { + // This should be called only if the except node has a target exception. + assert(node.d.typeExpr !== undefined); + + if (isTypeCached(node)) { + return; + } + + const exceptionTypeResult = getTypeOfExpression(node.d.typeExpr!); + const exceptionTypes = exceptionTypeResult.type; + let includesBaseException = false; + + function getExceptionType(exceptionType: Type, errorNode: ExpressionNode) { + exceptionType = makeTopLevelTypeVarsConcrete(exceptionType); + + if (isAnyOrUnknown(exceptionType)) { + return exceptionType; + } + + if (isInstantiableClass(exceptionType)) { + if (ClassType.isBuiltIn(exceptionType, 'BaseException')) { + includesBaseException = true; + } + return ClassType.cloneAsInstance(exceptionType); + } + + if (isClassInstance(exceptionType)) { + const iterableType = + getTypeOfIterator( + { type: exceptionType, isIncomplete: exceptionTypeResult.isIncomplete }, + /* isAsync */ false, + errorNode, + /* emitNotIterableError */ false + )?.type ?? UnknownType.create(); + + return mapSubtypes(iterableType, (subtype) => { + if (isAnyOrUnknown(subtype)) { + return subtype; + } + + return UnknownType.create(); + }); + } + + return UnknownType.create(); + } + + let targetType = mapSubtypes(exceptionTypes, (subType) => { + // If more than one type was specified for the exception, we'll receive + // a specialized tuple object here. + const tupleType = getSpecializedTupleType(subType); + if (tupleType && tupleType.priv.tupleTypeArgs) { + const entryTypes = tupleType.priv.tupleTypeArgs.map((t) => { + return getExceptionType(t.type, node.d.typeExpr!); + }); + return combineTypes(entryTypes); + } + + return getExceptionType(subType, node.d.typeExpr!); + }); + + // If this is an except group, wrap the exception type in an ExceptionGroup + // or BaseExceptionGroup depending on whether the target exception is + // a BaseException. + if (node.d.isExceptGroup) { + targetType = getBuiltInObject(node, includesBaseException ? 'BaseExceptionGroup' : 'ExceptionGroup', [ + targetType, + ]); + } + + if (node.d.name) { + assignTypeToExpression(node.d.name, { type: targetType }, node.d.name); + } + + writeTypeCache(node, { type: targetType }, EvalFlags.None); + } + + function evaluateTypesForWithStatement(node: WithItemNode): void { + if (isTypeCached(node)) { + return; + } + + const exprTypeResult = getTypeOfExpression(node.d.expr); + let isIncomplete = exprTypeResult.isIncomplete; + let exprType = exprTypeResult.type; + const isAsync = node.parent && node.parent.nodeType === ParseNodeType.With && !!node.parent.d.isAsync; + + if (isOptionalType(exprType)) { + addDiagnostic( + DiagnosticRule.reportOptionalContextManager, + isAsync ? LocMessage.noneNotUsableWithAsync() : LocMessage.noneNotUsableWith(), + node.d.expr + ); + exprType = removeNoneFromUnion(exprType); + } + + // Verify that the target has an __enter__ or __aenter__ method defined. + const enterMethodName = isAsync ? '__aenter__' : '__enter__'; + const scopedType = mapSubtypes(exprType, (subtype) => { + subtype = makeTopLevelTypeVarsConcrete(subtype); + + if (isAnyOrUnknown(subtype)) { + return subtype; + } + + const enterDiag = new DiagnosticAddendum(); + + if (isClass(subtype)) { + const enterTypeResult = getTypeOfMagicMethodCall( + subtype, + enterMethodName, + [], + node.d.expr, + /* inferenceContext */ undefined, + enterDiag.createAddendum() + ); + + if (enterTypeResult) { + if (isAsync) { + if (enterTypeResult.isIncomplete) { + isIncomplete = true; + } + + const asyncResult = getTypeOfAwaitable({ type: enterTypeResult.type }, node.d.expr); + if (asyncResult.isIncomplete) { + isIncomplete = true; + } + + return asyncResult.type; + } + return enterTypeResult.type; + } + + if (!isAsync) { + if ( + getTypeOfMagicMethodCall( + subtype, + '__aenter__', + [], + node.d.expr, + /* inferenceContext */ undefined + )?.type + ) { + enterDiag.addMessage(LocAddendum.asyncHelp()); + } + } + } + + const message = isAsync ? LocMessage.typeNotUsableWithAsync() : LocMessage.typeNotUsableWith(); + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + message.format({ type: printType(subtype), method: enterMethodName }) + enterDiag.getString(), + node.d.expr + ); + return UnknownType.create(); + }); + + // Verify that the target has an __exit__ or __aexit__ method defined. + const exitMethodName = isAsync ? '__aexit__' : '__exit__'; + const exitDiag = new DiagnosticAddendum(); + + doForEachSubtype(exprType, (subtype) => { + subtype = makeTopLevelTypeVarsConcrete(subtype); + + if (isAnyOrUnknown(subtype)) { + return; + } + + if (isClass(subtype)) { + const anyArg: TypeResult = { type: AnyType.create() }; + const exitTypeResult = getTypeOfMagicMethodCall( + subtype, + exitMethodName, + [anyArg, anyArg, anyArg], + node.d.expr, + /* inferenceContext */ undefined, + exitDiag + ); + + if (exitTypeResult) { + if (exitTypeResult.isIncomplete) { + isIncomplete = true; + } + + if (isAsync) { + const asyncResult = getTypeOfAwaitable({ type: exitTypeResult.type }, node.d.expr); + if (asyncResult.isIncomplete) { + isIncomplete = true; + } + + return asyncResult.type; + } + + return exitTypeResult.type; + } + } + + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeNotUsableWith().format({ type: printType(subtype), method: exitMethodName }) + + exitDiag.getString(), + node.d.expr + ); + return UnknownType.create(); + }); + + if (node.d.target) { + assignTypeToExpression(node.d.target, { type: scopedType, isIncomplete }, node.d.target); + } + + writeTypeCache(node, { type: scopedType, isIncomplete }, EvalFlags.None); + } + + function evaluateTypesForImportAs(node: ImportAsNode): void { + if (isTypeCached(node)) { + return; + } + + let symbolNameNode: NameNode; + if (node.d.alias) { + // The symbol name is defined by the alias. + symbolNameNode = node.d.alias; + } else { + // There was no alias, so we need to use the first element of + // the name parts as the symbol. + symbolNameNode = node.d.module.d.nameParts[0]; + } + + if (!symbolNameNode) { + // This can happen in certain cases where there are parse errors. + return; + } + + // Look up the symbol to find the alias declaration. + let symbolType = getAliasedSymbolTypeForName(node, symbolNameNode.d.value) ?? UnknownType.create(); + + // Viper 扩展:模块未解析时,在符号名处标黄警告"名称找不到"。 + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + if (isUnknown(symbolType) && IsViperDialect(fileInfo.executionEnvironment.dialect)) { + const importInfo = AnalyzerNodeInfo.getImportInfo(node.d.module); + if (importInfo && !importInfo.isImportFound) { + fileInfo.diagnosticSink.addDiagnosticWithTextRange( + 'warning', + LocMessage.viperUnresolvedImportName().format({ + name: symbolNameNode.d.value, + }), + symbolNameNode + ); + } + } + + // Is there a cached module type associated with this node? If so, use + // it instead of the type we just created. + const cachedModuleType = readTypeCache(node, EvalFlags.None) as ModuleType; + if (cachedModuleType && isModule(cachedModuleType) && symbolType) { + if (isTypeSame(symbolType, cachedModuleType)) { + symbolType = cachedModuleType; + } + } + + assignTypeToNameNode(symbolNameNode, { type: symbolType }, /* ignoreEmptyContainers */ false); + + writeTypeCache(node, { type: symbolType }, EvalFlags.None); + } + + function evaluateTypesForImportFromAs(node: ImportFromAsNode): void { + if (isTypeCached(node)) { + return; + } + + const aliasNode = node.d.alias || node.d.name; + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + + // If this is a redundant form of an import, assume it is an intentional + // export and mark the symbol as accessed. + if (node.d.alias?.d.value === node.d.name.d.value) { + const symbolInScope = lookUpSymbolRecursive(node, node.d.name.d.value, /* honorCodeFlow */ true); + if (symbolInScope) { + setSymbolAccessed(fileInfo, symbolInScope.symbol, node); + } + } + + // If this is an import into a class scope, mark the symbol as accessed. + const classNode = ParseTreeUtils.getEnclosingClass(node, /* stopAtFunction */ true); + if (classNode) { + const symbolInScope = lookUpSymbolRecursive(node, aliasNode.d.value, /* honorCodeFlow */ true); + if (symbolInScope) { + setSymbolAccessed(fileInfo, symbolInScope.symbol, node); + } + } + + let symbolType = getAliasedSymbolTypeForName(node, aliasNode.d.value); + if (!symbolType) { + const parentNode = node.parent as ImportFromNode; + assert(parentNode && parentNode.nodeType === ParseNodeType.ImportFrom); + assert(!parentNode.d.isWildcardImport); + + const importInfo = AnalyzerNodeInfo.getImportInfo(parentNode.d.module); + if (importInfo && importInfo.isImportFound && !importInfo.isNativeLib) { + const resolvedPath = importInfo.resolvedUris[importInfo.resolvedUris.length - 1]; + + const importLookupInfo = importLookup(resolvedPath); + let reportError = false; + + // If we were able to resolve the import, report the error as + // an unresolved symbol. + if (importLookupInfo) { + reportError = true; + + // Viper 扩展:模块级 __getattr__(PEP 562)语义不存在,跳过动态符号解析。 + } else if (resolvedPath.isEmpty()) { + // This corresponds to the "from . import a" form. + reportError = true; + } + + if (reportError) { + addDiagnostic( + DiagnosticRule.reportAttributeAccessIssue, + LocMessage.importSymbolUnknown().format({ name: node.d.name.d.value }), + node.d.name + ); + } + } + + if (!symbolType) { + symbolType = UnknownType.create(); + } + } + + assignTypeToNameNode(aliasNode, { type: symbolType }, /* ignoreEmptyContainers */ false); + + // Viper: Explicitly mark the imported symbol as accessed. With .vp/.vpi + // resolution, the symbol id registered at import time may differ from + // the id resolved during name usage tracking, which causes the + // setSymbolAccessed call inside getTypeOfName to miss the checker's + // accessedSymbolSet lookup and produce false "is not accessed" diagnostics. + // The redundant-alias and class-scope cases above already mark the + // symbol; this covers the remaining general import case. + const symbolInScope = lookUpSymbolRecursive(node, aliasNode.d.value, /* honorCodeFlow */ true); + if (symbolInScope) { + setSymbolAccessed(fileInfo, symbolInScope.symbol, node); + } + + writeTypeCache(node, { type: symbolType }, EvalFlags.None); + } + + function evaluateTypesForMatchStatement(node: MatchNode): void { + if (isTypeCached(node)) { + return; + } + + const subjectTypeResult = getTypeOfExpression(node.d.expr); + let subjectType = subjectTypeResult.type; + + // Apply negative narrowing for each of the cases that doesn't have a guard statement. + for (const caseStatement of node.d.cases) { + if (!caseStatement.d.guardExpr) { + subjectType = narrowTypeBasedOnPattern( + evaluatorInterface, + subjectType, + caseStatement.d.pattern, + /* isPositiveTest */ false + ); + } + } + + writeTypeCache(node, { type: subjectType, isIncomplete: !!subjectTypeResult.isIncomplete }, EvalFlags.None); + } + + function evaluateTypesForCaseStatement(node: CaseNode): void { + if (isTypeCached(node)) { + return; + } + + if (!node.parent || node.parent.nodeType !== ParseNodeType.Match) { + fail('Expected parent of case statement to be match statement'); + return; + } + + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + const subjectTypeResult = getTypeOfExpression(node.parent.d.expr); + let subjectType = subjectTypeResult.type; + + // Apply negative narrowing for each of the cases prior to the current one + // except for those that have a guard expression. + for (const caseStatement of node.parent.d.cases) { + if (caseStatement === node) { + if (fileInfo.diagnosticRuleSet.reportUnnecessaryComparison !== 'none') { + if (!subjectTypeResult.isIncomplete) { + checkForUnusedPattern(evaluatorInterface, node.d.pattern, subjectType); + } + } + break; + } + + if (!caseStatement.d.guardExpr) { + subjectType = narrowTypeBasedOnPattern( + evaluatorInterface, + subjectType, + caseStatement.d.pattern, + /* isPositiveTest */ false + ); + } + } + + const narrowedSubjectType = assignTypeToPatternTargets( + evaluatorInterface, + subjectType, + !!subjectTypeResult.isIncomplete, + node.d.pattern + ); + + writeTypeCache( + node, + { type: narrowedSubjectType, isIncomplete: !!subjectTypeResult.isIncomplete }, + EvalFlags.None + ); + } + + function evaluateTypesForImportFrom(node: ImportFromNode): void { + if (isTypeCached(node)) { + return; + } + + if (node.d.isWildcardImport) { + // Write back a dummy type so we don't evaluate this node again. + writeTypeCache(node, { type: AnyType.create() }, EvalFlags.None); + + const flowNode = AnalyzerNodeInfo.getFlowNode(node); + if (flowNode && (flowNode.flags & FlowFlags.WildcardImport) !== 0) { + const wildcardFlowNode = flowNode as FlowWildcardImport; + wildcardFlowNode.names.forEach((name) => { + const importedSymbolType = getAliasedSymbolTypeForName(node, name); + + if (!importedSymbolType) { + return; + } + + const symbolWithScope = lookUpSymbolRecursive(node, name, /* honorCodeFlow */ false); + if (!symbolWithScope) { + return; + } + + const declaredType = getDeclaredTypeOfSymbol(symbolWithScope.symbol)?.type; + if (!declaredType) { + return; + } + + const diagAddendum = new DiagnosticAddendum(); + + if (!assignType(declaredType, importedSymbolType, diagAddendum)) { + addDiagnostic( + DiagnosticRule.reportAssignmentType, + LocMessage.typeAssignmentMismatchWildcard().format({ + ...printSrcDestTypes(importedSymbolType, declaredType), + name, + }) + diagAddendum.getString(), + node, + node.d.wildcardToken ?? node + ); + } + }); + } + } else { + // Use the first element of the name parts as the symbol. + const symbolNameNode = node.d.module.d.nameParts[0]; + + // Look up the symbol to find the alias declaration. + let symbolType = getAliasedSymbolTypeForName(node, symbolNameNode.d.value); + if (!symbolType) { + return; + } + + // Is there a cached module type associated with this node? If so, use + // it instead of the type we just created. + const cachedModuleType = readTypeCache(node, EvalFlags.None) as ModuleType; + if (cachedModuleType && isModule(cachedModuleType) && symbolType) { + if (isTypeSame(symbolType, cachedModuleType)) { + symbolType = cachedModuleType; + } + } + + assignTypeToNameNode(symbolNameNode, { type: symbolType }, /* ignoreEmptyContainers */ false); + + writeTypeCache(node, { type: symbolType }, EvalFlags.None); + } + } + + function evaluateTypesForTypeAnnotationNode(node: TypeAnnotationNode) { + // If this node is part of an assignment statement, use specialized + // logic that performs bidirectional inference and assignment + // type narrowing. + if (node.parent?.nodeType === ParseNodeType.Assignment) { + evaluateTypesForAssignmentStatement(node.parent); + } else { + const annotationType = getTypeOfAnnotation(node.d.annotation, { + varTypeAnnotation: true, + allowFinal: isFinalAllowedForAssignmentTarget(node.d.valueExpr), + allowClassVar: isClassVarAllowedForAssignmentTarget(node.d.valueExpr), + }); + + writeTypeCache(node.d.valueExpr, { type: annotationType }, EvalFlags.None); + } + } + + function getAliasedSymbolTypeForName( + node: ImportAsNode | ImportFromAsNode | ImportFromNode, + name: string + ): Type | undefined { + const symbolWithScope = lookUpSymbolRecursive(node, name, /* honorCodeFlow */ true); + if (!symbolWithScope) { + return undefined; + } + + // Normally there will be at most one decl associated with the import node, but + // there can be multiple in the case of the "from .X import X" statement. In such + // case, we want to choose the last declaration. + const filteredDecls = symbolWithScope.symbol + .getDeclarations() + .filter( + (decl) => ParseTreeUtils.isNodeContainedWithin(node, decl.node) && decl.type === DeclarationType.Alias + ); + let aliasDecl = filteredDecls.length > 0 ? filteredDecls[filteredDecls.length - 1] : undefined; + + // If we didn't find an exact match, look for any alias associated with + // this symbol. In cases where we have multiple ImportAs nodes that share + // the same first-part name (e.g. "import asyncio" and "import asyncio.tasks"), + // we may not find the declaration associated with this node. + if (!aliasDecl) { + aliasDecl = symbolWithScope.symbol.getDeclarations().find((decl) => decl.type === DeclarationType.Alias); + } + + if (!aliasDecl) { + return undefined; + } + + assert(aliasDecl.type === DeclarationType.Alias); + + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + + // Try to resolve the alias while honoring external visibility. + const resolvedAliasInfo = resolveAliasDeclarationWithInfo(aliasDecl, /* resolveLocalNames */ true, { + allowExternallyHiddenAccess: fileInfo.isStubFile, + }); + + if (!resolvedAliasInfo) { + return undefined; + } + + if (!resolvedAliasInfo.declaration) { + return evaluatorOptions.evaluateUnknownImportsAsAny ? AnyType.create() : UnknownType.create(); + } + + if (node.nodeType === ParseNodeType.ImportFromAs) { + if (resolvedAliasInfo.isPrivate) { + addDiagnostic( + DiagnosticRule.reportPrivateUsage, + LocMessage.privateUsedOutsideOfModule().format({ + name: node.d.name.d.value, + }), + node.d.name + ); + } + + if (resolvedAliasInfo.privatePyTypedImporter) { + const diag = new DiagnosticAddendum(); + if (resolvedAliasInfo.privatePyTypedImported) { + diag.addMessage( + LocAddendum.privateImportFromPyTypedSource().format({ + module: resolvedAliasInfo.privatePyTypedImported, + }) + ); + } + addDiagnostic( + DiagnosticRule.reportPrivateImportUsage, + LocMessage.privateImportFromPyTypedModule().format({ + name: node.d.name.d.value, + module: resolvedAliasInfo.privatePyTypedImporter, + }) + diag.getString(), + node.d.name + ); + } + } + + return getInferredTypeOfDeclaration(symbolWithScope.symbol, aliasDecl); + } + + // In some cases, an expression must be evaluated in the context of another + // expression or statement that contains it. This contextual evaluation + // allows for bidirectional type evaluation. + function evaluateTypesForExpressionInContext(node: ExpressionNode): void { + // Check for a couple of special cases where the node is a NameNode but + // is technically not part of an expression. We'll handle these here so + // callers don't need to include special-case logic. + if (node.nodeType === ParseNodeType.Name && node.parent) { + if (node.parent.nodeType === ParseNodeType.Function && node.parent.d.name === node) { + getTypeOfFunction(node.parent); + return; + } + + if (node.parent.nodeType === ParseNodeType.Class && node.parent.d.name === node) { + getTypeOfClass(node.parent); + return; + } + + if (node.parent.nodeType === ParseNodeType.ImportFromAs) { + evaluateTypesForImportFromAs(node.parent); + return; + } + + if (node.parent.nodeType === ParseNodeType.ImportAs) { + evaluateTypesForImportAs(node.parent); + return; + } + + if (node.parent.nodeType === ParseNodeType.TypeAlias && node.parent.d.name === node) { + getTypeOfTypeAlias(node.parent); + return; + } + + if (node.parent.nodeType === ParseNodeType.Global || node.parent.nodeType === ParseNodeType.Nonlocal) { + // For global and nonlocal statements, allow forward references so + // we don't use code flow during symbol lookups. + getTypeOfExpression(node, EvalFlags.ForwardRefs); + return; + } + + if (node.parent.nodeType === ParseNodeType.ModuleName) { + // A name within a module name isn't an expression, + // so there's nothing we can evaluate here. + return; + } + } + + // If the expression is part of a type annotation, we need to evaluate + // it with special evaluation flags. + const annotationNode = ParseTreeUtils.getParentAnnotationNode(node); + if (annotationNode) { + // Annotations need to be evaluated with specialized evaluation flags. + const annotationParent = annotationNode.parent; + assert(annotationParent !== undefined); + + if (annotationParent.nodeType === ParseNodeType.Assignment) { + if (annotationNode === annotationParent.d.annotationComment) { + getTypeOfAnnotation(annotationNode, { + varTypeAnnotation: true, + allowFinal: isFinalAllowedForAssignmentTarget(annotationParent.d.leftExpr), + allowClassVar: isClassVarAllowedForAssignmentTarget(annotationParent.d.leftExpr), + }); + } else { + evaluateTypesForAssignmentStatement(annotationParent); + } + return; + } + + if (annotationParent.nodeType === ParseNodeType.TypeAnnotation) { + evaluateTypesForTypeAnnotationNode(annotationParent); + return; + } + + if ( + annotationParent.nodeType === ParseNodeType.Function && + annotationNode === annotationParent.d.returnAnnotation + ) { + getTypeOfAnnotation(annotationNode, { + typeVarGetsCurScope: true, + }); + return; + } + + getTypeOfAnnotation(annotationNode, { + varTypeAnnotation: annotationNode.parent?.nodeType === ParseNodeType.TypeAnnotation, + allowUnpackedTuple: + annotationParent.nodeType === ParseNodeType.Parameter && + annotationParent.d.category === ParamCategory.ArgsList, + allowUnpackedTypedDict: + annotationParent.nodeType === ParseNodeType.Parameter && + annotationParent.d.category === ParamCategory.KwargsDict, + }); + return; + } + + // See if the expression is part of a pattern used in a case statement. + const possibleCaseNode = ParseTreeUtils.getParentNodeOfType(node, ParseNodeType.Case); + if (possibleCaseNode) { + if (ParseTreeUtils.isNodeContainedWithin(node, possibleCaseNode.d.pattern)) { + evaluateTypesForCaseStatement(possibleCaseNode); + return; + } + } + + // Scan up the parse tree until we find a node that doesn't + // require any context to be evaluated. + let nodeToEvaluate: ExpressionNode = node; + let flags = EvalFlags.None; + + while (true) { + // If we're within an argument node in a call or index expression, skip + // all of the nodes between because the entire argument expression + // needs to be evaluated contextually. + const argumentNode = ParseTreeUtils.getParentNodeOfType(nodeToEvaluate, ParseNodeType.Argument); + if (argumentNode && argumentNode !== nodeToEvaluate) { + assert(argumentNode.parent !== undefined); + + if ( + argumentNode.parent.nodeType === ParseNodeType.Call || + argumentNode.parent.nodeType === ParseNodeType.Index + ) { + nodeToEvaluate = argumentNode.parent; + continue; + } + + if (argumentNode.parent.nodeType === ParseNodeType.Class) { + // If this is an argument node within a class declaration, + // evaluate the full class declaration node. + getTypeOfClass(argumentNode.parent); + return; + } + } + + let parent = nodeToEvaluate.parent; + if (!parent) { + break; + } + + // If this is the target of an assignment expression, evaluate the + // assignment expression node instead. + if (parent.nodeType === ParseNodeType.AssignmentExpression && nodeToEvaluate === parent.d.name) { + nodeToEvaluate = parent; + continue; + } + + // Forward-declared type annotation expressions need to be be evaluated + // in context so they have the appropriate flags set. Most of these cases + // will have been detected above when calling getParentAnnotationNode, + // but TypeAlias expressions are not handled there. + const stringEnclosure = ParseTreeUtils.getParentNodeOfType(parent, ParseNodeType.StringList); + if (stringEnclosure) { + nodeToEvaluate = stringEnclosure as StringListNode; + continue; + } + + // The left expression of a call or member access expression is not generally contextual. + if (parent.nodeType === ParseNodeType.Call || parent.nodeType === ParseNodeType.MemberAccess) { + if (nodeToEvaluate === parent.d.leftExpr) { + // Handle the special case where the LHS is a call to super(). + if ( + nodeToEvaluate.nodeType === ParseNodeType.Call && + nodeToEvaluate.d.leftExpr.nodeType === ParseNodeType.Name && + nodeToEvaluate.d.leftExpr.d.value === 'super' + ) { + nodeToEvaluate = parent; + continue; + } + + // Handle the special case where the LHS is a call to a lambda. + if (parent.nodeType === ParseNodeType.Call && nodeToEvaluate.nodeType === ParseNodeType.Lambda) { + nodeToEvaluate = parent; + continue; + } + + flags = EvalFlags.CallBaseDefaults; + break; + } + } else if (parent.nodeType === ParseNodeType.Index) { + // The base expression of an index expression is not contextual. + if (nodeToEvaluate === parent.d.leftExpr) { + flags = EvalFlags.IndexBaseDefaults; + } + } + + if (!isExpressionNode(parent)) { + // If we've hit a non-expression node, we generally want to + // stop. However, there are a few special "pass through" + // node types that we can skip over to get to a known + // expression node. + if ( + parent.nodeType === ParseNodeType.DictionaryKeyEntry || + parent.nodeType === ParseNodeType.DictionaryExpandEntry || + parent.nodeType === ParseNodeType.ComprehensionFor || + parent.nodeType === ParseNodeType.ComprehensionIf + ) { + assert(parent.parent !== undefined && isExpressionNode(parent.parent)); + parent = parent.parent; + } else if (parent.nodeType === ParseNodeType.Parameter) { + assert(parent.parent !== undefined); + + // Parameters are contextual for lambdas. + if (parent.parent.nodeType === ParseNodeType.Lambda) { + parent = parent.parent; + } else { + break; + } + } else if (parent.nodeType === ParseNodeType.TypeParameter) { + // If this is a bound or default expression in a type parameter list, + // we need to evaluate it in the context of the type parameter. + if (node === parent.d.boundExpr || node === parent.d.defaultExpr) { + getTypeOfTypeParam(parent); + return; + } + + break; + } else { + break; + } + } + + nodeToEvaluate = parent; + } + + const parent = nodeToEvaluate.parent!; + assert(parent !== undefined); + + switch (parent.nodeType) { + case ParseNodeType.Del: { + verifyDeleteExpression(nodeToEvaluate); + return; + } + + case ParseNodeType.TypeParameter: { + // If this is the name node within a type parameter list, see if it's a type alias + // definition. If so, we need to evaluate the type alias contextually. + if ( + nodeToEvaluate === parent.d.name && + parent.parent?.nodeType === ParseNodeType.TypeParameterList && + parent.parent.parent?.nodeType === ParseNodeType.TypeAlias + ) { + getTypeOfTypeAlias(parent.parent.parent); + return; + } + break; + } + + case ParseNodeType.TypeAlias: { + getTypeOfTypeAlias(parent); + return; + } + + case ParseNodeType.Decorator: { + if (parent.parent?.nodeType === ParseNodeType.Class) { + getTypeOfClass(parent.parent); + } else if (parent.parent?.nodeType === ParseNodeType.Function) { + getTypeOfFunction(parent.parent); + } + return; + } + + case ParseNodeType.Parameter: { + if (nodeToEvaluate !== parent.d.defaultValue) { + evaluateTypeOfParam(parent); + return; + } + break; + } + + case ParseNodeType.Argument: { + if (nodeToEvaluate === parent.d.name) { + // A name used to specify a named parameter in an argument isn't an + // expression, so there's nothing we can evaluate here. + return; + } + + if (parent.parent?.nodeType === ParseNodeType.Class) { + // A class argument must be evaluated in the context of the class declaration. + getTypeOfClass(parent.parent); + return; + } + break; + } + + case ParseNodeType.Return: { + // Return expressions must be evaluated in the context of the expected return type. + if (parent.d.expr) { + const enclosingFunctionNode = ParseTreeUtils.getEnclosingFunction(node); + let declaredReturnType = enclosingFunctionNode + ? getDeclaredReturnType(enclosingFunctionNode) + : undefined; + if (declaredReturnType) { + const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(node); + declaredReturnType = makeTypeVarsBound(declaredReturnType, liveScopeIds); + } + getTypeOfExpression(parent.d.expr, EvalFlags.None, makeInferenceContext(declaredReturnType)); + return; + } + break; + } + + case ParseNodeType.TypeAnnotation: { + evaluateTypesForTypeAnnotationNode(parent); + return; + } + + case ParseNodeType.Assignment: { + evaluateTypesForAssignmentStatement(parent); + return; + } + + case ParseNodeType.AugmentedAssignment: { + evaluateTypesForAugmentedAssignment(parent); + return; + } + } + + if (nodeToEvaluate.nodeType === ParseNodeType.TypeAnnotation) { + evaluateTypesForTypeAnnotationNode(nodeToEvaluate); + return; + } + + getTypeOfExpression(nodeToEvaluate, flags); + } + + function evaluateTypeOfParam(node: ParameterNode): void { + // If this parameter has no name, we have nothing to do. + if (!node.d.name) { + return; + } + + // We need to handle lambdas differently from functions because + // the former never have parameter type annotations but can + // be inferred, whereas the latter sometimes have type annotations + // but cannot be inferred. + const parent = node.parent!; + if (parent.nodeType === ParseNodeType.Lambda) { + evaluateTypesForExpressionInContext(parent); + return; + } + + assert(parent.nodeType === ParseNodeType.Function); + const functionNode = parent as FunctionNode; + + const paramIndex = functionNode.d.params.findIndex((param) => param === node); + const typeAnnotation = ParseTreeUtils.getTypeAnnotationForParam(functionNode, paramIndex); + + if (typeAnnotation) { + const param = functionNode.d.params[paramIndex]; + let annotatedType = getTypeOfParamAnnotation(typeAnnotation, functionNode.d.params[paramIndex].d.category); + + const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(param); + annotatedType = makeTypeVarsBound(annotatedType, liveTypeVarScopes); + + const adjType = transformVariadicParamType( + node, + node.d.category, + adjustParamAnnotatedType(param, annotatedType) + ); + + writeTypeCache(node.d.name, { type: adjType }, EvalFlags.None); + return; + } + + const containingClassNode = ParseTreeUtils.getEnclosingClass(functionNode, /* stopAtFunction */ true); + const classInfo = containingClassNode ? getTypeOfClass(containingClassNode) : undefined; + + if ( + classInfo && + ClassType.isPseudoGenericClass(classInfo?.classType) && + functionNode.d.name.d.value === '__init__' + ) { + const typeParamName = getPseudoGenericTypeVarName(node.d.name.d.value); + const paramType = classInfo.classType.shared.typeParams.find( + (param) => param.shared.name === typeParamName + ); + + if (paramType) { + writeTypeCache(node.d.name, { type: TypeVarType.cloneAsBound(paramType) }, EvalFlags.None); + return; + } + } + + // See if the function is a method in a child class. We may be able to + // infer the type of the parameter from a method of the same name in + // a parent class if it has an annotated type. + const functionFlags = getFunctionInfoFromDecorators( + evaluatorInterface, + functionNode, + /* isInClass */ true + ).flags; + + let inferredParamType = + inferParamType(functionNode, functionFlags, paramIndex, classInfo?.classType) ?? UnknownType.create(); + const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(node); + inferredParamType = makeTypeVarsBound(inferredParamType, liveTypeVarScopes); + + writeTypeCache( + node.d.name, + { type: transformVariadicParamType(node, node.d.category, inferredParamType) }, + EvalFlags.None + ); + } + + // Evaluates the types that are assigned within the statement that contains + // the specified parse node. In some cases, a broader statement may need to + // be evaluated to provide sufficient context for the type. Evaluated types + // are written back to the type cache for later retrieval. + function evaluateTypesForStatement(node: ParseNode): void { + initializePrefetchedTypes(node); + + let curNode: ParseNode | undefined = node; + + while (curNode) { + switch (curNode.nodeType) { + case ParseNodeType.Assignment: { + // See if the assignment is part of a chain of assignments. If so, + // evaluate the entire chain. + const isInAssignmentChain = + curNode.parent && + (curNode.parent.nodeType === ParseNodeType.Assignment || + curNode.parent.nodeType === ParseNodeType.AssignmentExpression || + curNode.parent.nodeType === ParseNodeType.AugmentedAssignment) && + curNode.parent.d.rightExpr === curNode; + if (!isInAssignmentChain) { + evaluateTypesForAssignmentStatement(curNode); + return; + } + break; + } + + case ParseNodeType.TypeAlias: { + getTypeOfTypeAlias(curNode); + return; + } + + case ParseNodeType.AssignmentExpression: { + evaluateTypesForExpressionInContext(curNode); + return; + } + + case ParseNodeType.AugmentedAssignment: { + evaluateTypesForAugmentedAssignment(curNode); + return; + } + + case ParseNodeType.Class: { + getTypeOfClass(curNode); + return; + } + + case ParseNodeType.Parameter: { + evaluateTypeOfParam(curNode); + return; + } + + case ParseNodeType.Lambda: { + evaluateTypesForExpressionInContext(curNode); + return; + } + + case ParseNodeType.Function: { + getTypeOfFunction(curNode); + return; + } + + case ParseNodeType.For: { + evaluateTypesForForStatement(curNode); + return; + } + + case ParseNodeType.Except: { + evaluateTypesForExceptStatement(curNode); + return; + } + + case ParseNodeType.WithItem: { + evaluateTypesForWithStatement(curNode); + return; + } + + case ParseNodeType.ComprehensionFor: { + const comprehension = curNode.parent as ComprehensionNode; + assert(comprehension.nodeType === ParseNodeType.Comprehension); + if (curNode === comprehension.d.expr) { + evaluateTypesForExpressionInContext(comprehension); + } else { + // Evaluate the individual iterations starting with the first + // up to the curNode. + for (const forIfNode of comprehension.d.forIfNodes) { + evaluateComprehensionForIf(forIfNode); + if (forIfNode === curNode) { + break; + } + } + } + return; + } + + case ParseNodeType.ImportAs: { + evaluateTypesForImportAs(curNode); + return; + } + + case ParseNodeType.ImportFromAs: { + evaluateTypesForImportFromAs(curNode); + return; + } + + case ParseNodeType.ImportFrom: { + evaluateTypesForImportFrom(curNode); + return; + } + + case ParseNodeType.Case: { + evaluateTypesForCaseStatement(curNode); + return; + } + } + + curNode = curNode.parent; + } + + fail('Unexpected statement'); + return undefined; + } + + // Helper function for cases where we need to evaluate the types + // for a subtree so we can determine the type of one of the subnodes + // within that tree. If the type cannot be determined (because it's part + // of a cyclical dependency), the function returns undefined. + function evaluateTypeForSubnode(subnode: ParseNode, callback: () => void): TypeResult | undefined { + // If the type cache is already populated with a complete type, + // don't bother doing additional work. + let cacheEntry = readTypeCacheEntry(subnode); + if (cacheEntry && !cacheEntry.typeResult.isIncomplete) { + const typeResult = cacheEntry.typeResult; + + // Handle the special case where a function or class is partially evaluated. + // Indicate that these are not complete types. + if (isFunction(typeResult.type) && FunctionType.isPartiallyEvaluated(typeResult.type)) { + return { ...typeResult, isIncomplete: true }; + } + + if (isClass(typeResult.type) && ClassType.isPartiallyEvaluated(typeResult.type)) { + return { ...typeResult, isIncomplete: true }; + } + + return typeResult; + } + + callback(); + cacheEntry = readTypeCacheEntry(subnode); + if (cacheEntry) { + return cacheEntry.typeResult; + } + + return undefined; + } + + function getCodeFlowAnalyzerForNode( + node: ExecutionScopeNode, + typeAtStart: TypeResult | undefined + ): CodeFlowAnalyzer { + let entries = codeFlowAnalyzerCache.get(node.id); + + if (entries) { + const cachedEntry = entries.find((entry) => { + if (!typeAtStart || !entry.typeAtStart) { + return !typeAtStart && !entry.typeAtStart; + } + + if (!typeAtStart.isIncomplete !== !entry.typeAtStart.isIncomplete) { + return false; + } + + return isTypeSame(typeAtStart.type, entry.typeAtStart.type); + }); + + if (cachedEntry) { + return cachedEntry.codeFlowAnalyzer; + } + } + + // Allocate a new code flow analyzer. + const analyzer = codeFlowEngine.createCodeFlowAnalyzer(); + if (entries) { + entries.push({ typeAtStart, codeFlowAnalyzer: analyzer }); + } else { + entries = [{ typeAtStart, codeFlowAnalyzer: analyzer }]; + codeFlowAnalyzerCache.set(node.id, entries); + } + + return analyzer; + } + + // Attempts to determine the type of the reference expression at the + // point in the code. If the code flow analysis has nothing to say + // about that expression, it returns un undefined type. Normally + // flow analysis starts from the reference node, but startNode can be + // specified to override this in a few special cases (functions and + // lambdas) to support analysis of captured variables. + function getFlowTypeOfReference( + reference: CodeFlowReferenceExpressionNode, + startNode?: ClassNode | FunctionNode | LambdaNode, + options?: FlowNodeTypeOptions + ): FlowNodeTypeResult { + // See if this execution scope requires code flow for this reference expression. + const referenceKey = createKeyForReference(reference); + const executionNode = ParseTreeUtils.getExecutionScopeNode(startNode?.parent ?? reference); + const codeFlowExpressions = AnalyzerNodeInfo.getCodeFlowExpressions(executionNode); + + if ( + !codeFlowExpressions || + (!codeFlowExpressions.has(referenceKey) && !codeFlowExpressions.has(wildcardImportReferenceKey)) + ) { + return FlowNodeTypeResult.create(/* type */ undefined, /* isIncomplete */ false); + } + + if (checkCodeFlowTooComplex(reference)) { + return FlowNodeTypeResult.create( + /* type */ options?.typeAtStart && isUnbound(options.typeAtStart.type) + ? UnknownType.create() + : undefined, + /* isIncomplete */ true + ); + } + + // Is there an code flow analyzer cached for this execution scope? + let analyzer: CodeFlowAnalyzer | undefined; + + if (isNodeInReturnTypeInferenceContext(executionNode)) { + // If we're performing the analysis within a temporary + // context of a function for purposes of inferring its + // return type for a specified set of arguments, use + // a temporary analyzer that we'll use only for this context. + analyzer = getCodeFlowAnalyzerForReturnTypeInferenceContext(); + } else { + analyzer = getCodeFlowAnalyzerForNode(executionNode, options?.typeAtStart); + } + + const flowNode = AnalyzerNodeInfo.getFlowNode(startNode ?? reference); + if (flowNode === undefined) { + return FlowNodeTypeResult.create(/* type */ undefined, /* isIncomplete */ false); + } + + return analyzer.getTypeFromCodeFlow(flowNode!, reference, options); + } + + // Specializes the specified (potentially generic) class type using + // the specified type arguments, reporting errors as appropriate. + // Returns the specialized type and a boolean indicating whether + // the type indicates a class type (true) or an object type (false). + function createSpecializedClassType( + classType: ClassType, + typeArgs: TypeResultWithNode[] | undefined, + flags: EvalFlags, + errorNode: ExpressionNode + ): TypeResult { + let isValidTypeForm = true; + + // Handle the special-case classes that are not defined + // in the type stubs. + if (ClassType.isSpecialBuiltIn(classType)) { + const aliasedName = classType.priv.aliasName || classType.shared.name; + switch (aliasedName) { + case 'Callable': { + return { type: createCallableType(classType, typeArgs, errorNode) }; + } + + case 'Never': + case 'NoReturn': { + if (typeArgs && typeArgs.length > 0) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.typeArgsExpectingNone().format({ name: aliasedName }), + typeArgs[0].node + ); + } + + let resultType = aliasedName === 'Never' ? NeverType.createNever() : NeverType.createNoReturn(); + resultType = TypeBase.cloneAsSpecialForm(resultType, classType); + resultType = TypeBase.cloneWithTypeForm(resultType, convertToInstance(resultType)); + + return { type: resultType }; + } + + case 'Optional': { + return { type: createOptionalType(classType, errorNode, typeArgs, flags) }; + } + + case 'Type': { + let typeType = createSpecialType( + classType, + typeArgs, + 1, + /* allowParamSpec */ undefined, + /* isSpecialForm */ false + ); + + if (isInstantiableClass(typeType)) { + typeType = explodeGenericClass(typeType); + } + + typeType = TypeBase.cloneWithTypeForm(typeType, convertToInstance(typeType)); + + return { type: typeType }; + } + + case 'ClassVar': { + return { type: createClassVarType(classType, errorNode, typeArgs, flags) }; + } + + case 'Protocol': { + if ((flags & (EvalFlags.NoNonTypeSpecialForms | EvalFlags.TypeExpression)) !== 0) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.protocolNotAllowed(), errorNode); + } + + typeArgs?.forEach((typeArg) => { + if (typeArg.typeList || !isTypeVar(typeArg.type)) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.protocolTypeArgMustBeTypeParam(), + typeArg.node + ); + } + }); + + return { + type: createSpecialType( + classType, + typeArgs, + /* paramLimit */ undefined, + /* allowParamSpec */ true + ), + }; + } + + case 'TypedDict': { + if ((flags & (EvalFlags.NoNonTypeSpecialForms | EvalFlags.TypeExpression)) !== 0) { + const isInlinedTypedDict = + AnalyzerNodeInfo.getFileInfo(errorNode).diagnosticRuleSet.enableExperimentalFeatures && + !!typeArgs; + + if (!isInlinedTypedDict) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.typedDictNotAllowed(), + errorNode + ); + } + } + isValidTypeForm = false; + break; + } + + case 'Literal': { + if ((flags & (EvalFlags.NoNonTypeSpecialForms | EvalFlags.TypeExpression)) !== 0) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.literalNotAllowed(), errorNode); + } + isValidTypeForm = false; + break; + } + + case 'Tuple': { + return { + type: createSpecialType( + classType, + typeArgs, + /* paramLimit */ undefined, + /* allowParamSpec */ false, + /* isSpecialForm */ false + ), + }; + } + + case 'Union': { + return { type: createUnionType(classType, errorNode, typeArgs, flags) }; + } + + case 'Generic': { + return { type: createGenericType(classType, errorNode, typeArgs, flags) }; + } + + case 'Final': { + return { type: createFinalType(classType, errorNode, typeArgs, flags) }; + } + + case 'Annotated': { + return createAnnotatedType(classType, errorNode, typeArgs, flags); + } + + case 'Concatenate': { + return { type: createConcatenateType(classType, errorNode, typeArgs, flags) }; + } + + case 'TypeGuard': + case 'TypeIs': { + return { type: createTypeGuardType(classType, errorNode, typeArgs, flags) }; + } + + case 'Unpack': { + return { type: createUnpackType(classType, errorNode, typeArgs, flags) }; + } + + case 'Required': + case 'NotRequired': { + return createRequiredOrReadOnlyType(classType, errorNode, typeArgs, flags); + } + + case 'ReadOnly': { + return createRequiredOrReadOnlyType(classType, errorNode, typeArgs, flags); + } + + case 'Self': { + return { type: createSelfType(classType, errorNode, typeArgs, flags) }; + } + + case 'LiteralString': { + return { type: createSpecialType(classType, typeArgs, 0) }; + } + + case 'TypeForm': { + return { type: createTypeFormType(classType, errorNode, typeArgs) }; + } + } + } + + const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode); + if ( + fileInfo.isStubFile || + PythonVersion.isGreaterOrEqualTo(fileInfo.executionEnvironment.pythonVersion, pythonVersion3_9) || + isAnnotationEvaluationPostponed(AnalyzerNodeInfo.getFileInfo(errorNode)) || + (flags & EvalFlags.ForwardRefs) !== 0 + ) { + // Handle "type" specially, since it needs to act like "Type" + // in Python 3.9 and newer. + if (ClassType.isBuiltIn(classType, 'type') && typeArgs) { + if (typeArgs.length >= 1) { + // Treat type[function] as illegal. + if (isFunctionOrOverloaded(typeArgs[0].type)) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.typeAnnotationWithCallable(), + typeArgs[0].node + ); + + return { type: UnknownType.create() }; + } + } + + if (prefetched?.typeClass && isInstantiableClass(prefetched.typeClass)) { + let typeType = createSpecialType( + prefetched.typeClass, + typeArgs, + 1, + /* allowParamSpec */ undefined, + /* isSpecialForm */ false + ); + + if (isInstantiableClass(typeType)) { + typeType = explodeGenericClass(typeType); + } + + typeType = TypeBase.cloneWithTypeForm(typeType, convertToInstance(typeType)); + + return { type: typeType }; + } + } + + // Handle "tuple" specially, since it needs to act like "Tuple" + // in Python 3.9 and newer. + if (isTupleClass(classType)) { + let specializedClass = createSpecialType( + classType, + typeArgs, + /* paramLimit */ undefined, + /* allowParamSpec */ undefined, + /* isSpecialForm */ false + ); + + specializedClass = TypeBase.cloneWithTypeForm(specializedClass, convertToInstance(specializedClass)); + + return { type: specializedClass }; + } + } + + let typeArgCount = typeArgs ? typeArgs.length : 0; + + // Make sure the argument list count is correct. + const typeParams = ClassType.isPseudoGenericClass(classType) ? [] : ClassType.getTypeParams(classType); + + // If there are no type parameters or args, the class is already specialized. + // No need to do any more work. + if (typeParams.length === 0 && typeArgCount === 0) { + return { type: classType }; + } + + const variadicTypeParamIndex = typeParams.findIndex((param) => isTypeVarTuple(param)); + + if (typeArgs) { + let minTypeArgCount = typeParams.length; + const firstDefaultParamIndex = typeParams.findIndex((param) => !!param.shared.isDefaultExplicit); + + if (firstDefaultParamIndex >= 0) { + minTypeArgCount = firstDefaultParamIndex; + } + + // Classes that accept inlined type dict type args allow only one. + if (typeArgs.length > 0 && typeArgs[0].inlinedTypeDict) { + if (typeArgs.length > 1) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeArguments, + LocMessage.typeArgsTooMany().format({ + name: classType.priv.aliasName || classType.shared.name, + expected: 1, + received: typeArgCount, + }), + typeArgs[1].node + ); + } + + return { type: typeArgs[0].inlinedTypeDict }; + } else if (typeArgCount > typeParams.length) { + if (!ClassType.isPartiallyEvaluated(classType) && !ClassType.isTupleClass(classType)) { + if (typeParams.length === 0) { + isValidTypeForm = false; + addDiagnostic( + DiagnosticRule.reportInvalidTypeArguments, + LocMessage.typeArgsExpectingNone().format({ + name: classType.priv.aliasName || classType.shared.name, + }), + typeArgs[typeParams.length].node + ); + } else if (typeParams.length !== 1 || !isParamSpec(typeParams[0])) { + isValidTypeForm = false; + addDiagnostic( + DiagnosticRule.reportInvalidTypeArguments, + LocMessage.typeArgsTooMany().format({ + name: classType.priv.aliasName || classType.shared.name, + expected: typeParams.length, + received: typeArgCount, + }), + typeArgs[typeParams.length].node + ); + } + + typeArgCount = typeParams.length; + } + } else if (typeArgCount < minTypeArgCount) { + isValidTypeForm = false; + addDiagnostic( + DiagnosticRule.reportInvalidTypeArguments, + LocMessage.typeArgsTooFew().format({ + name: classType.priv.aliasName || classType.shared.name, + expected: minTypeArgCount, + received: typeArgCount, + }), + typeArgs.length > 0 ? typeArgs[0].node.parent! : errorNode + ); + } + + typeArgs.forEach((typeArg, index) => { + if (!typeArg.type.props?.typeForm) { + isValidTypeForm = false; + } + + if (index === variadicTypeParamIndex) { + // The types that make up the tuple that maps to the + // TypeVarTuple have already been validated when the tuple + // object was created in adjustTypeArgsForTypeVarTuple. + if (isClassInstance(typeArg.type) && isTupleClass(typeArg.type)) { + return; + } + + if (isTypeVarTuple(typeArg.type)) { + if (!validateTypeVarTupleIsUnpacked(typeArg.type, typeArg.node)) { + isValidTypeForm = false; + } + return; + } + } + + const typeParam = index < typeParams.length ? typeParams[index] : undefined; + const isParamSpecTarget = typeParam && isParamSpec(typeParam); + + if ( + !validateTypeArg(typeArg, { + allowParamSpec: true, + allowTypeArgList: isParamSpecTarget, + }) + ) { + isValidTypeForm = false; + } + }); + } + + // Handle ParamSpec arguments and fill in any missing type arguments with Unknown. + let typeArgTypes: Type[] = []; + const fullTypeParams = ClassType.getTypeParams(classType); + + typeArgs = transformTypeArgsForParamSpec(fullTypeParams, typeArgs, errorNode); + if (!typeArgs) { + isValidTypeForm = false; + } + + const constraints = new ConstraintTracker(); + + fullTypeParams.forEach((typeParam, index) => { + if (typeArgs && index < typeArgs.length) { + if (isParamSpec(typeParam)) { + const typeArg = typeArgs[index]; + const functionType = FunctionType.createSynthesizedInstance('', FunctionTypeFlags.ParamSpecValue); + + if (isEllipsisType(typeArg.type)) { + FunctionType.addDefaultParams(functionType); + functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm; + typeArgTypes.push(functionType); + constraints.setBounds(typeParam, functionType); + return; + } + + if (typeArg.typeList) { + typeArg.typeList!.forEach((paramType, paramIndex) => { + FunctionType.addParam( + functionType, + FunctionParam.create( + ParamCategory.Simple, + convertToInstance(paramType.type), + FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared, + `__p${paramIndex}` + ) + ); + }); + + if (typeArg.typeList.length > 0) { + FunctionType.addPositionOnlyParamSeparator(functionType); + } + + typeArgTypes.push(functionType); + constraints.setBounds(typeParam, functionType); + return; + } + + if (isInstantiableClass(typeArg.type) && ClassType.isBuiltIn(typeArg.type, 'Concatenate')) { + const concatTypeArgs = typeArg.type.priv.typeArgs; + if (concatTypeArgs && concatTypeArgs.length > 0) { + concatTypeArgs.forEach((typeArg, index) => { + if (index === concatTypeArgs.length - 1) { + if (isParamSpec(typeArg)) { + FunctionType.addParamSpecVariadics(functionType, typeArg); + } else if (isEllipsisType(typeArg)) { + FunctionType.addDefaultParams(functionType); + functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm; + } + } else { + FunctionType.addParam( + functionType, + FunctionParam.create( + ParamCategory.Simple, + typeArg, + FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared, + `__p${index}` + ) + ); + } + }); + } + + typeArgTypes.push(functionType); + return; + } + } + + const typeArgType = convertToInstance(typeArgs[index].type); + typeArgTypes.push(typeArgType); + constraints.setBounds(typeParam, typeArgType); + return; + } + + const solvedDefaultType = solveAndApplyConstraints(typeParam, constraints, { + replaceUnsolved: { + scopeIds: getTypeVarScopeIds(classType), + tupleClassType: getTupleClassType(), + }, + }); + typeArgTypes.push(solvedDefaultType); + constraints.setBounds(typeParam, solvedDefaultType); + }); + + typeArgTypes = typeArgTypes.map((typeArgType, index) => { + if (index < typeArgCount) { + const diag = new DiagnosticAddendum(); + let adjustedTypeArgType = applyTypeArgToTypeVar(typeParams[index], typeArgType, diag); + + // Determine if the variance must match. + if (adjustedTypeArgType && (flags & EvalFlags.EnforceVarianceConsistency) !== 0) { + const destType = typeParams[index]; + const declaredVariance = destType.shared.declaredVariance; + + if (!isVarianceOfTypeArgCompatible(adjustedTypeArgType, declaredVariance)) { + diag.addMessage( + LocAddendum.varianceMismatchForClass().format({ + typeVarName: printType(adjustedTypeArgType), + className: classType.shared.name, + }) + ); + adjustedTypeArgType = undefined; + } + } + + if (adjustedTypeArgType) { + typeArgType = adjustedTypeArgType; + } else { + // Avoid emitting this error for a partially-constructed class. + if (!isClassInstance(typeArgType) || !ClassType.isPartiallyEvaluated(typeArgType)) { + assert(typeArgs !== undefined); + isValidTypeForm = false; + addDiagnostic( + DiagnosticRule.reportInvalidTypeArguments, + LocMessage.typeVarAssignmentMismatch().format({ + type: printType(typeArgType), + name: TypeVarType.getReadableName(typeParams[index]), + }) + diag.getString(), + typeArgs[index].node + ); + } + } + } + + return typeArgType; + }); + + // If the class is partially constructed and doesn't yet have + // type parameters, assume that the number and types of supplied type + // arguments are correct. + if (typeArgs && classType.shared.typeParams.length === 0 && ClassType.isPartiallyEvaluated(classType)) { + typeArgTypes = typeArgs.map((t) => convertToInstance(t.type)); + } + + let specializedClass = ClassType.specialize(classType, typeArgTypes, typeArgs !== undefined); + + specializedClass = TypeBase.cloneWithTypeForm( + specializedClass, + isValidTypeForm ? convertToInstance(specializedClass) : undefined + ); + + return { type: specializedClass }; + } + + // PEP 612 says that if the class has only one type parameter consisting + // of a ParamSpec, the list of arguments does not need to be enclosed in + // a list. We'll handle that case specially here. + function transformTypeArgsForParamSpec( + typeParams: TypeVarType[], + typeArgs: TypeResultWithNode[] | undefined, + errorNode: ExpressionNode + ): TypeResultWithNode[] | undefined { + if (typeParams.length !== 1 || !isParamSpec(typeParams[0]) || !typeArgs) { + return typeArgs; + } + + if (typeArgs.length > 1) { + for (const typeArg of typeArgs) { + if (isParamSpec(typeArg.type)) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.paramSpecContext(), typeArg.node); + return undefined; + } + + if (isEllipsisType(typeArg.type)) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.ellipsisContext(), typeArg.node); + return undefined; + } + + if (isInstantiableClass(typeArg.type) && ClassType.isBuiltIn(typeArg.type, 'Concatenate')) { + addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.concatenateContext(), typeArg.node); + return undefined; + } + + if (typeArg.typeList) { + addDiagnostic( + DiagnosticRule.reportInvalidTypeForm, + LocMessage.typeArgListNotAllowed(), + typeArg.node + ); + return undefined; + } + } + } + + if (typeArgs.length === 1) { + // Don't transform a type list. + if (typeArgs[0].typeList) { + return typeArgs; + } + + const typeArgType = typeArgs[0].type; + + // Don't transform a single ParamSpec or ellipsis. + if (isParamSpec(typeArgType) || isEllipsisType(typeArgType)) { + return typeArgs; + } + + // Don't transform a Concatenate. + if (isInstantiableClass(typeArgType) && ClassType.isBuiltIn(typeArgType, 'Concatenate')) { + return typeArgs; + } + } + + // Package up the type arguments into a type list. + return [ + { + type: UnknownType.create(), + node: typeArgs.length > 0 ? typeArgs[0].node : errorNode, + typeList: typeArgs, + }, + ]; + } + + function getTypeOfArg(arg: Arg, inferenceContext: InferenceContext | undefined): TypeResult { + if (arg.typeResult) { + const type = arg.typeResult.type; + return { type: type?.props?.specialForm ?? type, isIncomplete: arg.typeResult.isIncomplete }; + } + + if (!arg.valueExpression) { + // We shouldn't ever get here, but just in case. + return { type: UnknownType.create() }; + } + + // If there was no defined type provided, there should always + // be a value expression from which we can retrieve the type. + return getTypeOfExpression(arg.valueExpression, /* flags */ undefined, inferenceContext); + } + + // This function is like getTypeOfArg except that it is + // used in cases where the argument is expected to be a type + // and therefore follows the normal rules of types (e.g. they + // can be forward-declared in stubs, etc.). + function getTypeOfArgExpectingType(arg: Arg, options?: ExpectedTypeOptions): TypeResult { + if (arg.typeResult) { + return { type: arg.typeResult.type, isIncomplete: arg.typeResult.isIncomplete }; + } + + // If there was no defined type provided, there should always + // be a value expression from which we can retrieve the type. + assert(arg.valueExpression !== undefined); + return getTypeOfExpressionExpectingType(arg.valueExpression, options); + } + + function getTypeOfExpressionExpectingType(node: ExpressionNode, options?: ExpectedTypeOptions): TypeResult { + let flags = EvalFlags.InstantiableType | EvalFlags.StrLiteralAsType; + + if (options?.allowTypeVarsWithoutScopeId) { + flags |= EvalFlags.AllowTypeVarWithoutScopeId; + } + + if (options?.typeVarGetsCurScope) { + flags |= EvalFlags.TypeVarGetsCurScope; + } + + if (options?.enforceClassTypeVarScope) { + flags |= EvalFlags.EnforceClassTypeVarScope; + } + + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + if ((isAnnotationEvaluationPostponed(fileInfo) || options?.forwardRefs) && !options?.runtimeTypeExpression) { + flags |= EvalFlags.ForwardRefs; + } else if (options?.parsesStringLiteral) { + flags |= EvalFlags.ParsesStringLiteral; + } + + if (!options?.allowFinal) { + flags |= EvalFlags.NoFinal; + } + + if (options?.allowRequired) { + flags |= EvalFlags.AllowRequired | EvalFlags.TypeExpression; + } + + if (options?.allowReadOnly) { + flags |= EvalFlags.AllowReadOnly | EvalFlags.TypeExpression; + } + + if (options?.allowUnpackedTuple) { + flags |= EvalFlags.AllowUnpackedTuple; + } else { + flags |= EvalFlags.NoTypeVarTuple; + } + + if (options?.allowUnpackedTypedDict) { + flags |= EvalFlags.AllowUnpackedTypedDict; + } + + if (!options?.allowParamSpec) { + flags |= EvalFlags.NoParamSpec; + } + + if (options?.typeExpression) { + flags |= EvalFlags.TypeExpression; + } + + if (options?.convertEllipsisToAny) { + flags |= EvalFlags.ConvertEllipsisToAny; + } + + if (options?.allowEllipsis) { + flags |= EvalFlags.AllowEllipsis; + } + + if (options?.noNonTypeSpecialForms) { + flags |= EvalFlags.NoNonTypeSpecialForms; + } + + if (!options?.allowClassVar) { + flags |= EvalFlags.NoClassVar; + } + + if (options?.varTypeAnnotation) { + flags |= EvalFlags.VarTypeAnnotation; + } + + if (options?.notParsed) { + flags |= EvalFlags.NotParsed; + } + + if (options?.typeFormArg) { + flags |= EvalFlags.TypeFormArg; + } + + return getTypeOfExpression(node, flags); + } + + function getBuiltInType(node: ParseNode, name: string): Type { + const scope = ScopeUtils.getScopeForNode(node); + if (scope) { + const builtInScope = ScopeUtils.getBuiltInScope(scope); + const nameType = builtInScope.lookUpSymbol(name); + if (nameType) { + return getEffectiveTypeOfSymbol(nameType); + } + } + + return UnknownType.create(); + } + + function getBuiltInObject(node: ParseNode, name: string, typeArgs?: Type[]) { + const nameType = getBuiltInType(node, name); + if (isInstantiableClass(nameType)) { + let classType = nameType; + if (typeArgs) { + classType = ClassType.specialize(classType, typeArgs); + } + + return ClassType.cloneAsInstance(classType); + } + + return nameType; + } + + function lookUpSymbolRecursive( + node: ParseNode, + name: string, + honorCodeFlow: boolean, + preferGlobalScope = false + ): SymbolWithScope | undefined { + const scopeNodeInfo = ParseTreeUtils.getEvaluationScopeNode(node); + const scope = AnalyzerNodeInfo.getScope(scopeNodeInfo.node); + + let symbolWithScope = scope?.lookUpSymbolRecursive(name, { + useProxyScope: !!scopeNodeInfo.useProxyScope, + useChainedModuleLevelScopes: !!scopeNodeInfo.useChainedModuleLevelScopes, + }); + + const scopeType = scope?.type ?? ScopeType.Module; + + // Functions and list comprehensions don't allow access to implicitly + // aliased symbols in outer scopes if they haven't yet been assigned + // within the local scope. + let scopeTypeHonorsCodeFlow = scopeType !== ScopeType.Function && scopeType !== ScopeType.Comprehension; + + // Type parameter scopes don't honor code flow. + if (symbolWithScope?.scope.type === ScopeType.TypeParameter) { + scopeTypeHonorsCodeFlow = false; + } + + if (symbolWithScope && honorCodeFlow && scopeTypeHonorsCodeFlow) { + // Filter the declarations based on flow reachability. + const reachableDecl = symbolWithScope.symbol.getDeclarations().find((decl) => { + if (decl.type !== DeclarationType.Alias && decl.type !== DeclarationType.Intrinsic) { + // Determine if the declaration is in the same execution scope as the "usageNode" node. + let usageScopeNode = ParseTreeUtils.getExecutionScopeNode(node); + const declNode: ParseNode = + decl.type === DeclarationType.Class || + decl.type === DeclarationType.Function || + decl.type === DeclarationType.TypeAlias + ? decl.node.d.name + : decl.node; + const declScopeNode = ParseTreeUtils.getExecutionScopeNode(declNode); + + // If this is a type parameter scope, it will be a proxy for its + // containing scope, so we need to use that instead. + const usageScope = AnalyzerNodeInfo.getScope(usageScopeNode); + if (usageScope?.proxy) { + const typeParamScope = AnalyzerNodeInfo.getScope(usageScopeNode); + if (!typeParamScope?.symbolTable.has(name) && usageScopeNode.parent) { + usageScopeNode = ParseTreeUtils.getExecutionScopeNode(usageScopeNode.parent); + } + } + + if (usageScopeNode === declScopeNode) { + if (!isFlowPathBetweenNodes(declNode, node)) { + // If there was no control flow path from the usage back + // to the source, see if the usage node is reachable by + // any path. + const flowNode = AnalyzerNodeInfo.getFlowNode(node); + const isReachable = + flowNode && + codeFlowEngine.getFlowNodeReachability( + flowNode, + /* sourceFlowNode */ undefined, + /* ignoreNoReturn */ true + ) === Reachability.Reachable; + return !isReachable; + } + } + } + return true; + }); + + // If none of the declarations are reachable from the current node, + // search for the symbol in outer scopes. + if (!reachableDecl) { + if (symbolWithScope.scope.type !== ScopeType.Function) { + let nextScopeToSearch = symbolWithScope.scope.parent; + const isOutsideCallerModule = + symbolWithScope.isOutsideCallerModule || symbolWithScope.scope.type === ScopeType.Module; + let isBeyondExecutionScope = + symbolWithScope.isBeyondExecutionScope || symbolWithScope.scope.isIndependentlyExecutable(); + + if (symbolWithScope.scope.type === ScopeType.Class) { + // There is an odd documented behavior for classes in that + // symbol resolution skips to the global scope rather than + // the next scope in the chain. + const globalScopeResult = symbolWithScope.scope.getGlobalScope(); + nextScopeToSearch = globalScopeResult.scope; + if (globalScopeResult.isBeyondExecutionScope) { + isBeyondExecutionScope = true; + } + } + + if (nextScopeToSearch) { + symbolWithScope = nextScopeToSearch.lookUpSymbolRecursive(name, { + isOutsideCallerModule, + isBeyondExecutionScope, + }); + } else { + symbolWithScope = undefined; + } + } else { + symbolWithScope = undefined; + } + } + } + + // PEP 563 indicates that if a forward reference can be resolved in the module + // scope (or, by implication, in the builtins scope), it should prefer that + // resolution over local resolutions. + if (symbolWithScope && preferGlobalScope) { + let curSymbolWithScope: SymbolWithScope | undefined = symbolWithScope; + while ( + curSymbolWithScope.scope.type !== ScopeType.Module && + curSymbolWithScope.scope.type !== ScopeType.Builtin && + curSymbolWithScope.scope.type !== ScopeType.TypeParameter && + curSymbolWithScope.scope.parent + ) { + curSymbolWithScope = curSymbolWithScope.scope.parent.lookUpSymbolRecursive(name, { + isOutsideCallerModule: curSymbolWithScope.isOutsideCallerModule, + isBeyondExecutionScope: + curSymbolWithScope.isBeyondExecutionScope || + curSymbolWithScope.scope.isIndependentlyExecutable(), + }); + if (!curSymbolWithScope) { + break; + } + } + + if ( + curSymbolWithScope?.scope.type === ScopeType.Module || + curSymbolWithScope?.scope.type === ScopeType.Builtin + ) { + symbolWithScope = curSymbolWithScope; + } + } + + return symbolWithScope; + } + + // Disables recording of errors and warnings. + function suppressDiagnostics( + node: ParseNode, + callback: () => T, + diagCallback?: (suppressedDiags: string[]) => void + ) { + suppressedNodeStack.push({ node, suppressedDiags: diagCallback ? [] : undefined }); + + try { + const result = callback(); + const poppedNode = suppressedNodeStack.pop(); + if (diagCallback && poppedNode?.suppressedDiags) { + diagCallback(poppedNode.suppressedDiags); + } + return result; + } catch (e) { + // We don't use finally here because the TypeScript debugger doesn't + // handle finally well when single stepping. + suppressedNodeStack.pop(); + throw e; + } + } + + function getSignatureTrackerForNode(node: ParseNode): UniqueSignatureTracker | undefined { + for (let i = signatureTrackerStack.length - 1; i >= 0; i--) { + const rootNode = signatureTrackerStack[i].rootNode; + if (ParseTreeUtils.isNodeContainedWithin(node, rootNode)) { + return signatureTrackerStack[i].tracker; + } + } + + return undefined; + } + + function useSignatureTracker(node: ParseNode, callback: () => T): T { + const tracker = getSignatureTrackerForNode(node); + + try { + // If a signature tracker doesn't already exist, allocate one. + if (!tracker) { + signatureTrackerStack.push({ + tracker: new UniqueSignatureTracker(), + rootNode: node, + }); + } + + const result = callback(); + + if (!tracker) { + signatureTrackerStack.pop(); + } + + return result; + } catch (e) { + // We don't use finally here because the TypeScript debugger doesn't + // handle finally well when single stepping. + if (!tracker) { + signatureTrackerStack.pop(); + } + + throw e; + } + } + + function ensureSignatureIsUnique(type: T, node: ParseNode): T { + const tracker = getSignatureTrackerForNode(node); + if (!tracker) { + return type; + } + + if (isFunctionOrOverloaded(type)) { + return ensureSignaturesAreUnique(type, tracker, node.start); + } + + return type; + } + + // Disables recording of errors and warnings and disables any caching of + // types, under the assumption that we're performing speculative evaluations. + // If speculativeNode is undefined, speculative mode is not used. This is + // useful in cases where we conditionally want to use speculative mode. + function useSpeculativeMode( + speculativeNode: ParseNode | undefined, + callback: () => T, + options?: SpeculativeModeOptions + ) { + if (!speculativeNode) { + return callback(); + } + + speculativeTypeTracker.enterSpeculativeContext(speculativeNode, options); + + try { + const result = callback(); + speculativeTypeTracker.leaveSpeculativeContext(); + return result; + } catch (e) { + // We don't use finally here because the TypeScript debugger doesn't + // handle finally well when single stepping. + speculativeTypeTracker.leaveSpeculativeContext(); + throw e; + } + } + + function disableSpeculativeMode(callback: () => void) { + const stack = speculativeTypeTracker.disableSpeculativeMode(); + + try { + callback(); + speculativeTypeTracker.enableSpeculativeMode(stack); + } catch (e) { + // We don't use finally here because the TypeScript debugger doesn't + // handle finally well when single stepping. + speculativeTypeTracker.enableSpeculativeMode(stack); + throw e; + } + } + + // Indicates whether the specified node is within a context that + // is currently being evaluated speculative. If node is undefined, + // returns true if any node is being evaluated speculatively. + function isSpeculativeModeInUse(node: ParseNode | undefined) { + return speculativeTypeTracker.isSpeculative(node); + } + + function getDeclarationFromKeywordParam(type: FunctionType, paramName: string): Declaration | undefined { + if (isFunction(type)) { + if (type.shared.declaration) { + const functionDecl = type.shared.declaration; + if (functionDecl.type === DeclarationType.Function) { + const functionNode = functionDecl.node; + const functionScope = AnalyzerNodeInfo.getScope(functionNode); + if (functionScope) { + const paramSymbol = functionScope.lookUpSymbol(paramName)!; + if (paramSymbol) { + return paramSymbol.getDeclarations().find((decl) => decl.type === DeclarationType.Param); + } + + const parameterDetails = getParamListDetails(type); + if (parameterDetails.unpackedKwargsTypedDictType) { + const lookupResults = lookUpClassMember( + parameterDetails.unpackedKwargsTypedDictType, + paramName + ); + if (lookupResults) { + return lookupResults.symbol + .getDeclarations() + .find((decl) => decl.type === DeclarationType.Variable); + } + } + } + } + } + } + + return undefined; + } + + function _shouldFallBackToClassEntryForKeywordArg(baseType: ClassType, paramName: string) { + return ( + ClassType.isDataClass(baseType) || + ClassType.isTypedDictClass(baseType) || + ClassType.hasNamedTupleEntry(baseType, paramName) + ); + } + + function _addSymbolDeclInfo( + symbol: Symbol, + decls: Declaration[], + synthesizedTypes: SynthesizedTypeInfo[], + preferTypedDeclarations = false + ) { + const declCountBeforeAdd = decls.length; + const typedDecls = preferTypedDeclarations ? symbol.getTypedDeclarations() : undefined; + appendArray(decls, typedDecls?.length ? typedDecls : symbol.getDeclarations()); + + const synthTypeInfo = symbol.getSynthesizedType(); + if (synthTypeInfo) { + synthesizedTypes.push(synthTypeInfo); + + // Some module members are represented only by a synthesized module type, + // with no concrete declaration node to navigate to. Synthesize a matching + // alias declaration so definition/declaration/reference features can still + // bind to the submodule's file. + if ( + decls.length === declCountBeforeAdd && + isModule(synthTypeInfo.type) && + !synthTypeInfo.type.priv.fileUri.isEmpty() + ) { + decls.push(synthesizeAliasDeclaration(synthTypeInfo.type.priv.fileUri)); + } + } + } + + function _addClassEntryDeclsForKeywordArgIfPresent( + baseType: ClassType, + paramName: string, + decls: Declaration[], + synthesizedTypes: SynthesizedTypeInfo[] + ) { + const lookupResults = lookUpClassMember(baseType, paramName); + if (!lookupResults) { + return; + } + + _addSymbolDeclInfo(lookupResults.symbol, decls, synthesizedTypes); + } + + // In general, string nodes don't have any declarations associated with them, but + // we need to handle the special case of string literals used as keys within a + // dictionary expression where those keys are associated with a known TypedDict. + function getDeclInfoForStringNode(node: StringNode): SymbolDeclInfo | undefined { + const decls: Declaration[] = []; + const synthesizedTypes: SynthesizedTypeInfo[] = []; + const expectedType = getExpectedType(node)?.type; + + if (expectedType) { + doForEachSubtype(expectedType, (subtype) => { + // If the expected type is a TypedDict then the node is either a key expression + // or a single entry in a set. We then need to check that the value of the node + // is a valid entry in the TypedDict to avoid resolving declarations for + // synthesized symbols such as 'get'. + if (isClassInstance(subtype) && ClassType.isTypedDictClass(subtype)) { + const entry = subtype.shared.typedDictEntries?.knownItems.get(node.d.value); + if (entry) { + const symbol = lookUpObjectMember(subtype, node.d.value)?.symbol; + + if (symbol) { + _addSymbolDeclInfo(symbol, decls, synthesizedTypes); + } + } + } + }); + } + + return decls.length === 0 ? undefined : { decls, synthesizedTypes }; + } + + function getAliasFromImport(node: NameNode): NameNode | undefined { + if ( + node.parent && + node.parent.nodeType === ParseNodeType.ImportFromAs && + node.parent.d.alias && + node === node.parent.d.name + ) { + return node.parent.d.alias; + } + return undefined; + } + + function getDeclInfoForNameNode(node: NameNode, skipUnreachableCode = true): SymbolDeclInfo | undefined { + if (skipUnreachableCode && AnalyzerNodeInfo.isCodeUnreachable(node)) { + return undefined; + } + + const decls: Declaration[] = []; + const synthesizedTypes: SynthesizedTypeInfo[] = []; + + // If the node is part of a "from X import Y as Z" statement and the node + // is the "Y" (non-aliased) name, we need to look up the alias symbol + // since the non-aliased name is not in the symbol table. + const alias = getAliasFromImport(node); + if (alias) { + const scope = ScopeUtils.getScopeForNode(node); + if (scope) { + // Look up the alias symbol. + const symbolInScope = scope.lookUpSymbolRecursive(alias.d.value); + if (symbolInScope) { + // The alias could have more decls that don't refer to this import. Filter + // out the one(s) that specifically associated with this import statement. + const declsForThisImport = symbolInScope.symbol.getDeclarations().filter((decl) => { + return decl.type === DeclarationType.Alias && decl.node === node.parent; + }); + + appendArray(decls, getDeclarationsWithUsesLocalNameRemoved(declsForThisImport)); + } + } + } else if ( + node.parent && + node.parent.nodeType === ParseNodeType.MemberAccess && + node === node.parent.d.member + ) { + let baseType = getType(node.parent.d.leftExpr); + if (baseType) { + baseType = makeTopLevelTypeVarsConcrete(baseType); + const memberName = node.parent.d.member.d.value; + doForEachSubtype(baseType, (subtype) => { + let symbol: Symbol | undefined; + + subtype = makeTopLevelTypeVarsConcrete(subtype); + + if (isInstantiableClass(subtype)) { + // Try to find a member that has a declared type. If so, that + // overrides any inferred types. + let member = lookUpClassMember(subtype, memberName, MemberAccessFlags.DeclaredTypesOnly); + if (!member) { + member = lookUpClassMember(subtype, memberName); + } + + if (!member) { + const metaclass = subtype.shared.effectiveMetaclass; + if (metaclass && isInstantiableClass(metaclass)) { + member = lookUpClassMember(metaclass, memberName); + } + } + + if (member) { + symbol = member.symbol; + } + } else if (isClassInstance(subtype)) { + // Viper 扩展:CPtr[T] 指针解引用转发。 + // 当 baseType 是 `T | t.CPtr`(折叠为 CPtr[T],如 + // `args: ParsedArgs | t.CPtr`)时,成员访问 `args._set_int(...)` + // 应转发到 T 的成员(模拟 C 的 ptr->field 语义)。 + // 否则 lookUpObjectMember 会在 CPtr 上找 `_set_int`,找不到时 + // 走 CPtr.__getattr__ 返回 variable 声明,导致方法显示为变量颜色。 + let subtypeForLookup: ClassType = subtype; + if ( + isCPtrType(subtypeForLookup) && + subtypeForLookup.priv.typeArgs && + subtypeForLookup.priv.typeArgs.length > 0 + ) { + const pointeeType = subtypeForLookup.priv.typeArgs[0]; + if (isClassInstance(pointeeType)) { + subtypeForLookup = pointeeType; + } else if (isInstantiableClass(pointeeType)) { + const converted = convertToInstance(pointeeType); + if (isClassInstance(converted)) { + subtypeForLookup = converted; + } + } + } + + // Try to find a member that has a declared type. If so, that + // overrides any inferred types. + let member = lookUpObjectMember( + subtypeForLookup, + memberName, + MemberAccessFlags.DeclaredTypesOnly + ); + if (!member) { + member = lookUpObjectMember(subtypeForLookup, memberName); + } + if (member) { + symbol = member.symbol; + } + } else if (isModule(subtype)) { + symbol = ModuleType.getField(subtype, memberName); + } + + if (symbol) { + // By default, report only the declarations that have type annotations. + // If there are none, then report all of the unannotated declarations, + // which includes every assignment of that symbol. + _addSymbolDeclInfo(symbol, decls, synthesizedTypes, /* preferTypedDeclarations */ true); + } + }); + } + } else if (node.parent && node.parent.nodeType === ParseNodeType.ModuleName) { + const namePartIndex = node.parent.d.nameParts.findIndex((part) => part === node); + const importInfo = AnalyzerNodeInfo.getImportInfo(node.parent); + if ( + namePartIndex >= 0 && + importInfo && + !importInfo.isNativeLib && + namePartIndex < importInfo.resolvedUris.length + ) { + if (importInfo.resolvedUris[namePartIndex]) { + evaluateTypesForStatement(node); + + // Synthesize an alias declaration for this name part. The only + // time this case is used is for IDE services such as + // the find all references, hover provider and etc. + decls.push(synthesizeAliasDeclaration(importInfo.resolvedUris[namePartIndex])); + } + } + } else if (node.parent && node.parent.nodeType === ParseNodeType.Argument && node === node.parent.d.name) { + // The target node is the name in a keyword argument. We need to determine whether + // the corresponding keyword parameter can be determined from the context. + const argNode = node.parent; + const paramName = node.d.value; + if (argNode.parent?.nodeType === ParseNodeType.Call) { + const baseType = getType(argNode.parent.d.leftExpr); + + if (baseType) { + if (isFunction(baseType) && baseType.shared.declaration) { + const paramDecl = getDeclarationFromKeywordParam(baseType, paramName); + if (paramDecl) { + decls.push(paramDecl); + } + } else if (isOverloaded(baseType)) { + OverloadedType.getOverloads(baseType).forEach((f) => { + const paramDecl = getDeclarationFromKeywordParam(f, paramName); + if (paramDecl) { + decls.push(paramDecl); + } + }); + } else if (isInstantiableClass(baseType)) { + const initMethodType = getBoundInitMethod( + evaluatorInterface, + argNode.parent.d.leftExpr, + ClassType.cloneAsInstance(baseType) + )?.type; + + if (initMethodType && isFunction(initMethodType)) { + const paramDecl = getDeclarationFromKeywordParam(initMethodType, paramName); + if (paramDecl) { + decls.push(paramDecl); + } else if (_shouldFallBackToClassEntryForKeywordArg(baseType, paramName)) { + _addClassEntryDeclsForKeywordArgIfPresent(baseType, paramName, decls, synthesizedTypes); + } + } else if (_shouldFallBackToClassEntryForKeywordArg(baseType, paramName)) { + // Some synthesized callables (notably TypedDict "constructors") don't have a + // meaningful __init__ signature we can map keyword arguments to. In these cases, + // treat the keyword as referring to the class entry so IDE features like + // go-to-definition and rename can bind to the field declaration. + _addClassEntryDeclsForKeywordArgIfPresent(baseType, paramName, decls, synthesizedTypes); + } + } + } + } else if (argNode.parent?.nodeType === ParseNodeType.Class) { + const classTypeResult = getTypeOfClass(argNode.parent); + + // Validate the init subclass args for this class so we can properly + // evaluate its custom keyword parameters. + if (classTypeResult) { + validateInitSubclassArgs(argNode.parent, classTypeResult.classType); + } + } + } else { + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + + // Determine if this node is within a quoted type annotation. + const isWithinTypeAnnotation = ParseTreeUtils.isWithinTypeAnnotation( + node, + !isAnnotationEvaluationPostponed(AnalyzerNodeInfo.getFileInfo(node)) + ); + + // Determine if this is part of a "type" statement. + const isWithinTypeAliasStatement = !!ParseTreeUtils.getParentNodeOfType(node, ParseNodeType.TypeAlias); + const allowForwardReferences = isWithinTypeAnnotation || isWithinTypeAliasStatement || fileInfo.isStubFile; + + const symbolWithScope = lookUpSymbolRecursive( + node, + node.d.value, + !allowForwardReferences, + isWithinTypeAnnotation + ); + + if (symbolWithScope) { + _addSymbolDeclInfo(symbolWithScope.symbol, decls, synthesizedTypes); + } + } + + return { decls, synthesizedTypes }; + } + + function getTypeForDeclaration(declaration: Declaration): DeclaredSymbolTypeInfo { + switch (declaration.type) { + case DeclarationType.Intrinsic: { + if (declaration.intrinsicType === 'Any') { + return { type: AnyType.create() }; + } + + if (declaration.intrinsicType === '__class__') { + const classNode = ParseTreeUtils.getEnclosingClass(declaration.node) as ClassNode; + const classTypeInfo = getTypeOfClass(classNode); + return { + type: classTypeInfo + ? specializeWithUnknownTypeArgs(classTypeInfo.classType, getTupleClassType()) + : UnknownType.create(), + }; + } + + const strType = getBuiltInObject(declaration.node, 'str'); + const intType = getBuiltInObject(declaration.node, 'int'); + if (isClassInstance(intType) && isClassInstance(strType)) { + if (declaration.intrinsicType === 'str') { + return { type: strType }; + } + + if (declaration.intrinsicType === 'str | None') { + return { type: combineTypes([strType, getNoneType()]) }; + } + + if (declaration.intrinsicType === 'int') { + return { type: intType }; + } + + if (declaration.intrinsicType === 'MutableSequence[str]') { + const sequenceType = getBuiltInType(declaration.node, 'MutableSequence'); + if (isInstantiableClass(sequenceType)) { + return { + type: ClassType.cloneAsInstance(ClassType.specialize(sequenceType, [strType])), + }; + } + } + + if (declaration.intrinsicType === 'dict[str, Any]') { + const dictType = getBuiltInType(declaration.node, 'dict'); + if (isInstantiableClass(dictType)) { + return { + type: ClassType.cloneAsInstance( + ClassType.specialize(dictType, [strType, AnyType.create()]) + ), + }; + } + } + } + + return { type: UnknownType.create() }; + } + + case DeclarationType.Class: { + const classTypeInfo = getTypeOfClass(declaration.node); + return { type: classTypeInfo?.decoratedType }; + } + + case DeclarationType.SpecialBuiltInClass: { + return { type: getTypeOfAnnotation(declaration.node.d.annotation) }; + } + + case DeclarationType.Function: { + const functionTypeInfo = getTypeOfFunction(declaration.node); + return { type: functionTypeInfo?.decoratedType }; + } + + case DeclarationType.TypeAlias: { + return { type: getTypeOfTypeAlias(declaration.node) }; + } + + case DeclarationType.Param: { + let typeAnnotationNode = declaration.node.d.annotation ?? declaration.node.d.annotationComment; + + // If there wasn't an annotation, see if the parent function + // has a function-level annotation comment that provides + // this parameter's annotation type. + if (!typeAnnotationNode) { + if (declaration.node.parent?.nodeType === ParseNodeType.Function) { + const functionNode = declaration.node.parent; + if ( + functionNode.d.funcAnnotationComment && + !functionNode.d.funcAnnotationComment.d.isEllipsis + ) { + const paramIndex = functionNode.d.params.findIndex((param) => param === declaration.node); + typeAnnotationNode = ParseTreeUtils.getTypeAnnotationForParam(functionNode, paramIndex); + } + } + } + + if (typeAnnotationNode) { + let declaredType = getTypeOfParamAnnotation(typeAnnotationNode, declaration.node.d.category); + + const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(declaration.node); + declaredType = makeTypeVarsBound(declaredType, liveTypeVarScopes); + + return { + type: transformVariadicParamType( + declaration.node, + declaration.node.d.category, + adjustParamAnnotatedType(declaration.node, declaredType) + ), + }; + } + + return { type: undefined }; + } + + case DeclarationType.TypeParam: { + return { type: getTypeOfTypeParam(declaration.node) }; + } + + case DeclarationType.Variable: { + const typeAnnotationNode = declaration.typeAnnotationNode; + + if (typeAnnotationNode) { + let declaredType: Type | undefined; + + if (declaration.isRuntimeTypeExpression) { + declaredType = convertToInstance( + getTypeOfExpressionExpectingType(typeAnnotationNode, { + allowFinal: true, + allowRequired: true, + allowReadOnly: true, + runtimeTypeExpression: true, + }).type + ); + } else { + const declNode = + declaration.isDefinedByMemberAccess && + declaration.node.parent?.nodeType === ParseNodeType.MemberAccess + ? declaration.node.parent + : declaration.node; + const allowClassVar = isClassVarAllowedForAssignmentTarget(declNode); + const allowFinal = isFinalAllowedForAssignmentTarget(declNode); + const allowRequired = + ParseTreeUtils.isRequiredAllowedForAssignmentTarget(declNode) || + !!declaration.isInInlinedTypedDict; + + declaredType = getTypeOfAnnotation(typeAnnotationNode, { + varTypeAnnotation: true, + allowClassVar, + allowFinal, + allowRequired, + allowReadOnly: allowRequired, + enforceClassTypeVarScope: declaration.isDefinedByMemberAccess, + }); + } + + if (declaredType) { + // If this is a declaration for a member variable within a method, + // we need to convert any bound TypeVars associated with the + // class to their free counterparts. + if (declaration.isDefinedByMemberAccess) { + const enclosingClass = ParseTreeUtils.getEnclosingClass(declaration.node); + + if (enclosingClass) { + declaredType = makeTypeVarsFree(declaredType, [ + ParseTreeUtils.getScopeIdForNode(enclosingClass), + ]); + } + } + + if (isClassInstance(declaredType)) { + // Viper 扩展:t.CTypedef 是 TypeAlias 的别名,但来自 + // vipershed-fallback 无 BuiltIn 标志,需额外检查类型名称。 + // 修复跨模块 from ... import * 后 typedef 显示为 int 的问题。 + const fileInfo = AnalyzerNodeInfo.getFileInfo(declaration.node); + const isViperCTypedef = + IsViperDialect(fileInfo.executionEnvironment.dialect) && + (declaredType.shared.name === 'CTypedef' || + declaredType.shared.fullName === 't.CTypedef'); + if (ClassType.isBuiltIn(declaredType, 'TypeAlias') || isViperCTypedef) { + return { type: undefined, isTypeAlias: true }; + } + } + + return { type: declaredType }; + } + } + + return { type: undefined }; + } + + case DeclarationType.Alias: { + return { type: undefined }; + } + } + } + + function getTypeOfTypeParam(node: TypeParameterNode): TypeVarType { + // Is this type already cached? + const cachedTypeVarType = readTypeCache(node.d.name, EvalFlags.None) as FunctionType; + if (cachedTypeVarType && isTypeVar(cachedTypeVarType)) { + return cachedTypeVarType; + } + + let runtimeClassName = 'TypeVar'; + let kind: TypeVarKind = TypeVarKind.TypeVar; + if (node.d.typeParamKind === TypeParamKind.TypeVarTuple) { + runtimeClassName = 'TypeVarTuple'; + kind = TypeVarKind.TypeVarTuple; + } else if (node.d.typeParamKind === TypeParamKind.ParamSpec) { + runtimeClassName = 'ParamSpec'; + kind = TypeVarKind.ParamSpec; + } + const runtimeType = getTypingType(node, runtimeClassName); + const runtimeClass = runtimeType && isInstantiableClass(runtimeType) ? runtimeType : undefined; + + let typeVar = TypeVarType.createInstantiable(node.d.name.d.value, kind); + if (runtimeClass) { + typeVar = TypeBase.cloneAsSpecialForm(typeVar, ClassType.cloneAsInstance(runtimeClass)); + } + typeVar.shared.isTypeParamSyntax = true; + + // Cache the value before we evaluate the bound or the default type in + // case it refers to itself in a circular manner. + writeTypeCache(node, { type: typeVar }, /* flags */ undefined); + writeTypeCache(node.d.name, { type: typeVar }, /* flags */ undefined); + + if (node.d.boundExpr) { + if (node.d.boundExpr.nodeType === ParseNodeType.Tuple) { + const constraints = node.d.boundExpr.d.items.map((constraint) => { + const constraintType = getTypeOfExpressionExpectingType(constraint, { + noNonTypeSpecialForms: true, + forwardRefs: true, + typeExpression: true, + }).type; + + if ( + requiresSpecialization(constraintType, { + ignorePseudoGeneric: true, + ignoreImplicitTypeArgs: true, + }) + ) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarBoundGeneric(), + constraint + ); + } + + return convertToInstance(constraintType); + }); + + if (constraints.length < 2) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarSingleConstraint(), + node.d.boundExpr + ); + } else if (node.d.typeParamKind === TypeParamKind.TypeVar) { + typeVar.shared.constraints = constraints; + } + } else { + const boundType = getTypeOfExpressionExpectingType(node.d.boundExpr, { + noNonTypeSpecialForms: true, + forwardRefs: true, + typeExpression: true, + }).type; + + if (requiresSpecialization(boundType, { ignorePseudoGeneric: true })) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.typeVarConstraintGeneric(), + node.d.boundExpr + ); + } + + if (node.d.typeParamKind === TypeParamKind.TypeVar) { + typeVar.shared.boundType = convertToInstance(boundType); + } + } + } + + if (node.d.typeParamKind === TypeParamKind.ParamSpec) { + const defaultType = node.d.defaultExpr + ? getParamSpecDefaultType(node.d.defaultExpr, /* isPep695Syntax */ true) + : undefined; + + if (defaultType) { + typeVar.shared.defaultType = defaultType; + typeVar.shared.isDefaultExplicit = true; + } else { + typeVar.shared.defaultType = ParamSpecType.getUnknown(); + } + } else if (node.d.typeParamKind === TypeParamKind.TypeVarTuple) { + const defaultType = node.d.defaultExpr + ? getTypeVarTupleDefaultType(node.d.defaultExpr, /* isPep695Syntax */ true) + : undefined; + + if (defaultType) { + typeVar.shared.defaultType = defaultType; + typeVar.shared.isDefaultExplicit = true; + } else { + typeVar.shared.defaultType = makeTupleObject(evaluatorInterface, [ + { type: UnknownType.create(), isUnbounded: true }, + ]); + } + } else { + const defaultType = node.d.defaultExpr + ? convertToInstance( + getTypeOfExpressionExpectingType(node.d.defaultExpr, { + forwardRefs: true, + typeExpression: true, + }).type + ) + : undefined; + + if (defaultType) { + typeVar.shared.defaultType = defaultType; + typeVar.shared.isDefaultExplicit = true; + } else { + typeVar.shared.defaultType = UnknownType.create(); + } + } + + // If a default is provided, make sure it is compatible with the bound + // or constraint. + if (typeVar.shared.isDefaultExplicit && node.d.defaultExpr) { + verifyTypeVarDefaultIsCompatible(typeVar, node.d.defaultExpr); + } + + // Associate the type variable with the owning scope. + const scopeNode = ParseTreeUtils.getTypeVarScopeNode(node); + if (scopeNode) { + let scopeType: TypeVarScopeType; + if (scopeNode.nodeType === ParseNodeType.Class) { + scopeType = TypeVarScopeType.Class; + + // Set the variance to "auto" for class-scoped TypeVars. + typeVar.shared.declaredVariance = + isParamSpec(typeVar) || isTypeVarTuple(typeVar) ? Variance.Invariant : Variance.Auto; + } else if (scopeNode.nodeType === ParseNodeType.Function) { + scopeType = TypeVarScopeType.Function; + } else { + assert(scopeNode.nodeType === ParseNodeType.TypeAlias); + scopeType = TypeVarScopeType.TypeAlias; + typeVar.shared.declaredVariance = + isParamSpec(typeVar) || isTypeVarTuple(typeVar) ? Variance.Invariant : Variance.Auto; + } + + typeVar = TypeVarType.cloneForScopeId( + typeVar, + ParseTreeUtils.getScopeIdForNode( + scopeNode.nodeType === ParseNodeType.TypeAlias ? scopeNode.d.name : scopeNode + ), + scopeNode.d.name.d.value, + scopeType + ); + } + + writeTypeCache(node, { type: typeVar }, /* flags */ undefined); + writeTypeCache(node.d.name, { type: typeVar }, /* flags */ undefined); + + return typeVar; + } + + function getInferredTypeOfDeclaration(symbol: Symbol, decl: Declaration): Type | undefined { + const resolvedDecl = resolveAliasDeclaration(decl, /* resolveLocalNames */ true, { + allowExternallyHiddenAccess: AnalyzerNodeInfo.getFileInfo(decl.node).isStubFile, + }); + + // We couldn't resolve the alias. Substitute an unknown + // type in this case. + if (!resolvedDecl) { + return evaluatorOptions.evaluateUnknownImportsAsAny ? AnyType.create() : UnknownType.create(); + } + + function applyLoaderActionsToModuleType( + moduleType: ModuleType, + loaderActions: ModuleLoaderActions, + importLookup: ImportLookup + ): Type { + if (!loaderActions.uri.isEmpty() && loaderActions.loadSymbolsFromPath) { + const lookupResults = importLookup(loaderActions.uri); + if (lookupResults) { + moduleType.priv.fields = lookupResults.symbolTable; + moduleType.priv.docString = lookupResults.docString; + } else { + // Note that all module attributes that are not found in the + // symbol table should be treated as Any or Unknown rather than + // as an error. + moduleType.priv.notPresentFieldType = evaluatorOptions.evaluateUnknownImportsAsAny + ? AnyType.create() + : UnknownType.create(); + } + } + + if (loaderActions.implicitImports) { + loaderActions.implicitImports.forEach((implicitImport, name) => { + const existingLoaderField = moduleType.priv.loaderFields.get(name); + + // Recursively apply loader actions. + let symbolType: Type; + + if (implicitImport.isUnresolved) { + symbolType = UnknownType.create(); + } else { + let importedModuleType: ModuleType; + + const existingType = existingLoaderField?.getSynthesizedType(); + if (existingType?.type && isModule(existingType.type)) { + importedModuleType = existingType.type; + } else { + const moduleName = moduleType.priv.moduleName + ? moduleType.priv.moduleName + '.' + name + : ''; + importedModuleType = ModuleType.create(moduleName, implicitImport.uri); + } + + symbolType = applyLoaderActionsToModuleType(importedModuleType, implicitImport, importLookup); + } + + if (!existingLoaderField) { + const importedModuleSymbol = Symbol.createWithType(SymbolFlags.None, symbolType); + moduleType.priv.loaderFields.set(name, importedModuleSymbol); + } + }); + } + + return moduleType; + } + + // If the resolved declaration is still an alias, the alias + // is pointing at a module, and we need to synthesize a + // module type. + if (resolvedDecl.type === DeclarationType.Alias) { + let moduleType: ModuleType | undefined; + + // See if this is an import that shares a ModuleType with another + // import statement. If so, used the cached type. This happens when + // multiple import statements start with the same module name, such + // as "import a.b" and "import a.c". + if (resolvedDecl.node.nodeType === ParseNodeType.ImportAs) { + const cachedType = readTypeCache(resolvedDecl.node.d.module, EvalFlags.None); + if (cachedType && isModule(cachedType)) { + moduleType = cachedType; + } + } + + if (!moduleType) { + // Build a module type that corresponds to the declaration and + // its associated loader actions. + moduleType = ModuleType.create(resolvedDecl.moduleName, resolvedDecl.uri); + + if (resolvedDecl.node.nodeType === ParseNodeType.ImportAs) { + writeTypeCache(resolvedDecl.node.d.module, { type: moduleType }, EvalFlags.None); + } + } + + return applyLoaderActionsToModuleType( + moduleType, + resolvedDecl.symbolName && resolvedDecl.submoduleFallback + ? resolvedDecl.submoduleFallback + : resolvedDecl, + importLookup + ); + } + + const declaredType = getTypeForDeclaration(resolvedDecl); + if (declaredType.type) { + return declaredType.type; + } + + // If this is part of a "py.typed" package, don't fall back on type inference + // unless it's marked Final, is a constant, or is a declared type alias. + const fileInfo = AnalyzerNodeInfo.getFileInfo(resolvedDecl.node); + let isUnambiguousType = !fileInfo.isInPyTypedPackage || fileInfo.isStubFile; + + // If this is a py.typed package, determine if this is a case where an unannotated + // variable is considered "unambiguous" because all type checkers are almost + // guaranteed to infer its type the same. + if (!isUnambiguousType) { + if (resolvedDecl.type === DeclarationType.Variable) { + // Special-case variables within an enum class. These are effectively + // constants, so we'll treat them as unambiguous. + const enclosingClass = ParseTreeUtils.getEnclosingClass(resolvedDecl.node, /* stopAtFunction */ true); + if (enclosingClass) { + const classTypeInfo = getTypeOfClass(enclosingClass); + if (classTypeInfo && ClassType.isEnumClass(classTypeInfo.classType)) { + isUnambiguousType = true; + } + } + + // Special-case constants, which are treated as unambiguous. + if (isFinalVariableDeclaration(resolvedDecl) || resolvedDecl.isConstant) { + isUnambiguousType = true; + } + + // Special-case calls to certain built-in type functions. + if (resolvedDecl.inferredTypeSource?.nodeType === ParseNodeType.Call) { + const baseTypeResult = getTypeOfExpression( + resolvedDecl.inferredTypeSource.d.leftExpr, + EvalFlags.CallBaseDefaults + ); + const callType = baseTypeResult.type; + + const exemptBuiltins = [ + 'TypeVar', + 'ParamSpec', + 'TypeVarTuple', + 'TypedDict', + 'NamedTuple', + 'NewType', + 'TypeAliasType', + ]; + + if (isInstantiableClass(callType) && ClassType.isBuiltIn(callType, exemptBuiltins)) { + isUnambiguousType = true; + } else if ( + isFunction(callType) && + exemptBuiltins.some((name) => FunctionType.isBuiltIn(callType, name)) + ) { + isUnambiguousType = true; + } + } + } + } + + // If the resolved declaration had no defined type, use the + // inferred type for this node. + if (resolvedDecl.type === DeclarationType.Param) { + assert(resolvedDecl.node.d.name !== undefined); + return evaluateTypeForSubnode(resolvedDecl.node.d.name, () => { + evaluateTypeOfParam(resolvedDecl.node); + })?.type; + } + + if (resolvedDecl.type === DeclarationType.Variable && resolvedDecl.inferredTypeSource) { + const isTypeAlias = + isExplicitTypeAliasDeclaration(resolvedDecl) || isPossibleTypeAliasOrTypedDict(resolvedDecl); + + // If this is a type alias, evaluate types for the entire assignment + // statement rather than just the RHS of the assignment. + const typeSource = + isTypeAlias && resolvedDecl.inferredTypeSource.parent + ? resolvedDecl.inferredTypeSource.parent + : resolvedDecl.inferredTypeSource; + let inferredType = evaluateTypeForSubnode(resolvedDecl.node, () => { + evaluateTypesForStatement(typeSource); + })?.type; + + if (inferredType && isTypeAlias && resolvedDecl.typeAliasName) { + // If this was a speculative type alias, it becomes a real type alias only + // in the event that its inferred type is instantiable or explicitly Any + // (but not an ellipsis). + if (isLegalImplicitTypeAliasType(inferredType)) { + const typeAliasTypeVar = synthesizeTypeAliasPlaceholder(resolvedDecl.typeAliasName); + + inferredType = transformTypeForTypeAlias( + inferredType, + resolvedDecl.node, + typeAliasTypeVar, + /* isPep695TypeVarType */ false + ); + + isUnambiguousType = true; + } + } + + // Determine whether we need to mark the annotation as ambiguous. + if (inferredType && fileInfo.isInPyTypedPackage && !fileInfo.isStubFile) { + if (!isUnambiguousType) { + // See if this particular inference can be considered "unambiguous". + // Any symbol that is assigned more than once is considered ambiguous. + if (isUnambiguousInference(symbol, decl, inferredType)) { + isUnambiguousType = true; + } + } + + if (!isUnambiguousType) { + inferredType = TypeBase.cloneForAmbiguousType(inferredType); + } + } + + return inferredType; + } + + return undefined; + } + + // Applies some heuristics to determine whether it's likely that all Python + // type checkers will infer the same type. + function isUnambiguousInference(symbol: Symbol, decl: Declaration, inferredType: Type): boolean { + const nonSlotsDecls = symbol.getDeclarations().filter((decl) => { + return decl.type !== DeclarationType.Variable || !decl.isInferenceAllowedInPyTyped; + }); + + // Any symbol with more than one assignment is considered ambiguous. + if (nonSlotsDecls.length > 1) { + return false; + } + + if (decl.type !== DeclarationType.Variable) { + return false; + } + + // If there are no non-slots declarations, don't mark the inferred type as ambiguous. + if (nonSlotsDecls.length === 0) { + return true; + } + + // TypeVar definitions don't require a declaration. + if (isTypeVar(inferredType)) { + return true; + } + + let assignmentNode: AssignmentNode | undefined; + + const parentNode = decl.node.parent; + if (parentNode) { + // Is this a simple assignment (x = y) or an assignment of an instance variable (self.x = y)? + if (parentNode.nodeType === ParseNodeType.Assignment) { + assignmentNode = parentNode; + } else if ( + parentNode.nodeType === ParseNodeType.MemberAccess && + parentNode.parent?.nodeType === ParseNodeType.Assignment + ) { + assignmentNode = parentNode.parent; + } + } + + if (!assignmentNode) { + return false; + } + + const assignedType = getTypeOfExpression(assignmentNode.d.rightExpr).type; + + // Assume that literal values will always result in the same inferred type. + if (isClassInstance(assignedType) && isLiteralType(assignedType)) { + return true; + } + + // If the assignment is a simple name corresponding to an unambiguous + // type, we'll assume the resulting variable will receive the same + // unambiguous type. + if (assignmentNode.d.rightExpr.nodeType === ParseNodeType.Name && !TypeBase.isAmbiguous(assignedType)) { + return true; + } + + return false; + } + + // If the specified declaration is an alias declaration that points to a symbol, + // it resolves the alias and looks up the symbol, then returns the first declaration + // associated with that symbol. It does this recursively if necessary. If a symbol + // lookup fails, undefined is returned. If resolveLocalNames is true, the method + // resolves aliases through local renames ("as" clauses found in import statements). + function resolveAliasDeclaration( + declaration: Declaration, + resolveLocalNames: boolean, + options?: ResolveAliasOptions + ): Declaration | undefined { + return resolveAliasDeclarationUtil(importLookup, declaration, { + resolveLocalNames, + allowExternallyHiddenAccess: options?.allowExternallyHiddenAccess ?? false, + skipFileNeededCheck: options?.skipFileNeededCheck ?? false, + })?.declaration; + } + + function resolveAliasDeclarationWithInfo( + declaration: Declaration, + resolveLocalNames: boolean, + options?: ResolveAliasOptions + ): ResolvedAliasInfo | undefined { + return resolveAliasDeclarationUtil(importLookup, declaration, { + resolveLocalNames, + allowExternallyHiddenAccess: options?.allowExternallyHiddenAccess ?? false, + skipFileNeededCheck: options?.skipFileNeededCheck ?? false, + }); + } + + // Returns the type of the symbol. If the type is explicitly declared, that type + // is returned. If not, the type is inferred from assignments to the symbol. All + // assigned types are evaluated and combined into a union. + function getEffectiveTypeOfSymbol(symbol: Symbol): Type { + return getEffectiveTypeOfSymbolForUsage(symbol).type; + } + + // If a "usageNode" node is specified, only declarations that are outside + // of the current execution scope or that are reachable (as determined by + // code flow analysis) are considered. This helps in cases where there + // are cyclical dependencies between symbols. + function getEffectiveTypeOfSymbolForUsage( + symbol: Symbol, + usageNode?: NameNode, + useLastDecl = false + ): EffectiveTypeResult { + let declaredTypeInfo: DeclaredSymbolTypeInfo | undefined; + + // If there's a declared type, it takes precedence over inferred types. + if (symbol.hasTypedDeclarations()) { + declaredTypeInfo = getDeclaredTypeOfSymbol(symbol, usageNode); + const declaredType = declaredTypeInfo?.type; + + let isIncomplete = false; + if (declaredType) { + if (isFunction(declaredType) && FunctionType.isPartiallyEvaluated(declaredType)) { + isIncomplete = true; + } else if (isClass(declaredType) && ClassType.isPartiallyEvaluated(declaredType)) { + isIncomplete = true; + } + } + + // If the "declared" type uses a "TypeAlias" type annotation, then + // we need to use the inferred type path to evaluate its type. + if (declaredType || !declaredTypeInfo.isTypeAlias) { + const typedDecls = symbol.getTypedDeclarations(); + + // If we received an undefined declared type, this can be caused by + // exceeding the max number of type declarations, speculative + // evaluation, or a recursive definition. + const isRecursiveDefinition = + !declaredType && + !declaredTypeInfo.exceedsMaxDecls && + !speculativeTypeTracker.isSpeculative(/* node */ undefined); + + const result: EffectiveTypeResult = { + type: declaredType ?? UnknownType.create(), + isIncomplete, + includesVariableDecl: includesVariableTypeDecl(typedDecls), + includesIllegalTypeAliasDecl: !typedDecls.every((decl) => isPossibleTypeAliasDeclaration(decl)), + includesSpeculativeResult: false, + isRecursiveDefinition, + }; + + return result; + } + } + + return inferTypeOfSymbolForUsage(symbol, usageNode, useLastDecl); + } + + // Determines whether the set of declarations includes a variable declaration + // that is not part of a typing.pyi or typingExtensions.pyi file. + function includesVariableTypeDecl(decls: Declaration[]): boolean { + return decls.some((decl) => { + if (decl.type === DeclarationType.Variable) { + // Exempt typing.pyi and typingExtensions.pyi, which use variables to + // define some special forms. + const fileInfo = AnalyzerNodeInfo.getFileInfo(decl.node); + + if (!fileInfo.isTypingStubFile && !fileInfo.isTypingExtensionsStubFile) { + return true; + } + } + + if (decl.type === DeclarationType.Param) { + return true; + } + + return false; + }); + } + + function inferTypeOfSymbolForUsage(symbol: Symbol, usageNode?: NameNode, useLastDecl = false): EffectiveTypeResult { + // Look in the inferred type cache to see if we've computed this already. + let cacheEntries = effectiveTypeCache.get(symbol.id); + const usageNodeId = usageNode ? usageNode.id : undefined; + const effectiveTypeCacheKey = `${usageNodeId === undefined ? '.' : usageNodeId.toString()}${ + useLastDecl ? '*' : '' + }`; + const cacheEntry = cacheEntries?.get(effectiveTypeCacheKey); + + if (cacheEntry && !cacheEntry.isIncomplete) { + return cacheEntry; + } + + // Infer the type. + const decls = symbol.getDeclarations(); + + let declIndexToConsider: number | undefined; + + // Limit the number of declarations to explore. + if (decls.length > maxDeclarationsToUseForInference) { + const result: EffectiveTypeResult = { + type: UnknownType.create(), + isIncomplete: false, + includesVariableDecl: false, + includesIllegalTypeAliasDecl: !decls.every((decl) => isPossibleTypeAliasDeclaration(decl)), + includesSpeculativeResult: false, + isRecursiveDefinition: false, + }; + + addToEffectiveTypeCache(result); + return result; + } + + // If the caller has requested that we use only the last decl, we + // will use only the last one, but we'll ignore decls that are in + // except clauses. + if (useLastDecl) { + decls.forEach((decl, index) => { + if (!decl.isInExceptSuite) { + declIndexToConsider = index; + } + }); + } else { + // Handle the case where there are multiple imports — one of them in + // a try block and one or more in except blocks. In this case, we'll + // use the one in the try block rather than the excepts. + if (decls.length > 1 && decls.every((decl) => decl.type === DeclarationType.Alias)) { + const nonExceptDecls = decls.filter( + (decl) => decl.type === DeclarationType.Alias && !decl.isInExceptSuite + ); + if (nonExceptDecls.length === 1) { + declIndexToConsider = decls.findIndex((decl) => decl === nonExceptDecls[0]); + } + } + } + + // Determine which declarations to use for inference. + const declsToConsider: Declaration[] = []; + let includesVariableDecl = false; + let includesIllegalTypeAliasDecl = false; + + let sawExplicitTypeAlias = false; + decls.forEach((decl, index) => { + const resolvedDecl = + resolveAliasDeclaration(decl, /* resolveLocalNames */ true, { + allowExternallyHiddenAccess: AnalyzerNodeInfo.getFileInfo(decl.node).isStubFile, + }) ?? decl; + + if (!isPossibleTypeAliasDeclaration(resolvedDecl) && !isExplicitTypeAliasDeclaration(resolvedDecl)) { + includesIllegalTypeAliasDecl = true; + } + + if (includesVariableTypeDecl([resolvedDecl])) { + includesVariableDecl = true; + } + + if (declIndexToConsider !== undefined && declIndexToConsider !== index) { + return; + } + + // If we have already seen an explicit type alias, do not consider + // additional decls. This can happen if multiple TypeAlias declarations + // are provided -- normally an error, but it can happen in stdlib stubs + // if the user sets the pythonPlatform to "All". + if (sawExplicitTypeAlias) { + return; + } + + // If the symbol is explicitly marked as a ClassVar, consider only the + // declarations that assign to it from within the class body, not through + // a member access expression. + if ( + isEffectivelyClassVar(symbol, /* isDataclass */ false) && + decl.type === DeclarationType.Variable && + decl.isDefinedByMemberAccess + ) { + return; + } + + if (usageNode !== undefined) { + if (decl.type !== DeclarationType.Alias) { + // Is the declaration in the same execution scope as the "usageNode" node? + // If so, we can skip it because code flow analysis will allow us + // to determine the type in this context. + const usageScope = ParseTreeUtils.getExecutionScopeNode(usageNode); + const declScope = ParseTreeUtils.getExecutionScopeNode(decl.node); + if (usageScope === declScope) { + // Skip declarations that appear after the usage in the source. + // Such declarations are typically only reached via loop back-edges, + // and including them causes circular evaluation (producing Unknown). + // Declarations that textually precede the usage are retained so + // that order-independent type aliases resolve correctly; for + // branching constructs (if/else, try/except), a retained + // declaration may be flow-unreachable at the usage site, but the + // code-flow engine overrides the effective type for local + // variables, so the narrowed type at the usage site is correct. + // Note: for the cases filtered out here (decl after usage in same + // scope), the code-flow engine handles loop-carried narrowing when + // it evaluates the type at the actual usage site. + if (decl.node.start >= usageNode.start) { + return; + } + } + } + } + + const isExplicitTypeAlias = isExplicitTypeAliasDeclaration(resolvedDecl); + const isTypeAlias = isExplicitTypeAlias || isPossibleTypeAliasOrTypedDict(resolvedDecl); + + if (isExplicitTypeAlias) { + sawExplicitTypeAlias = true; + } + + // If this is a type alias, evaluate it outside of the recursive symbol + // resolution check so we can evaluate the full assignment statement. + if ( + isTypeAlias && + resolvedDecl.type === DeclarationType.Variable && + resolvedDecl.inferredTypeSource?.parent?.nodeType === ParseNodeType.Assignment + ) { + evaluateTypesForAssignmentStatement(resolvedDecl.inferredTypeSource.parent); + } + + // Viper 扩展:显式类型别名声明(如 `UINT32PTR: t.CTypedef = ...`) + // 产生两个声明:类型注解声明(有 typeAnnotationNode,无 inferredTypeSource) + // 和赋值声明(有 inferredTypeSource,无 typeAnnotationNode)。 + // 类型注解声明被识别为显式类型别名后,赋值声明会被 sawExplicitTypeAlias 跳过。 + // 此处补充:当显式类型别名声明没有 inferredTypeSource 时, + // 查找同符号的其他声明中是否有 inferredTypeSource,触发赋值语句求值。 + if ( + isExplicitTypeAlias && + resolvedDecl.type === DeclarationType.Variable && + !resolvedDecl.inferredTypeSource + ) { + const otherDecl = decls.find( + (d) => + d !== decl && + d.type === DeclarationType.Variable && + (d as VariableDeclaration).inferredTypeSource?.parent?.nodeType === ParseNodeType.Assignment + ); + if (otherDecl) { + const otherVarDecl = otherDecl as VariableDeclaration; + evaluateTypesForAssignmentStatement(otherVarDecl.inferredTypeSource!.parent as AssignmentNode); + } + } + + declsToConsider.push(resolvedDecl); + }); + + // If all of the decls come from augmented assignments, we won't be able to + // determine its type. At least one declaration must be a simple assignment. + if ( + declsToConsider.every( + (decl) => + isVariableDeclaration(decl) && + ParseTreeUtils.isNodeContainedWithinNodeType(decl.node, ParseNodeType.AugmentedAssignment) + ) + ) { + declsToConsider.splice(0); + } + + const result = getTypeOfSymbolForDecls(symbol, declsToConsider, effectiveTypeCacheKey); + result.includesVariableDecl = includesVariableDecl; + result.includesIllegalTypeAliasDecl = includesIllegalTypeAliasDecl; + + // Add the result to the effective type cache if it doesn't include speculative results. + if (!result.includesSpeculativeResult) { + addToEffectiveTypeCache(result); + } + + return result; + + function addToEffectiveTypeCache(result: EffectiveTypeResult) { + // Add the entry to the cache so we don't need to compute it next time. + if (!cacheEntries) { + cacheEntries = new Map(); + effectiveTypeCache.set(symbol.id, cacheEntries); + } + + cacheEntries.set(effectiveTypeCacheKey, result); + } + } + + // Returns the type of a symbol based on a subset of its declarations. + function getTypeOfSymbolForDecls(symbol: Symbol, decls: Declaration[], typeCacheKey: string): EffectiveTypeResult { + const typesToCombine: Type[] = []; + let isIncomplete = false; + let sawPendingEvaluation = false; + let includesSpeculativeResult = false; + + decls.forEach((decl) => { + if (pushSymbolResolution(symbol, decl)) { + let symbolPopped = false; + try { + let type = getInferredTypeOfDeclaration(symbol, decl); + + if (!popSymbolResolution(symbol)) { + isIncomplete = true; + } + symbolPopped = true; + + if (type) { + if (decl.type === DeclarationType.Variable) { + let isConstant = false; + if (decl.type === DeclarationType.Variable) { + if (decl.isConstant || isFinalVariableDeclaration(decl)) { + isConstant = true; + } + } + + // Treat enum values declared within an enum class as though they are const even + // though they may not be named as such. + if ( + isClassInstance(type) && + ClassType.isEnumClass(type) && + isDeclInEnumClass(evaluatorInterface, decl) + ) { + isConstant = true; + } + + // If the symbol is constant, we can retain the literal + // value and TypeForm types. Otherwise, strip literal values + // and TypeForm types to widen. + if (TypeBase.isInstance(type) && !isConstant && !isExplicitTypeAliasDeclaration(decl)) { + type = stripTypeForm(stripLiteralValue(type)); + } + } + + typesToCombine.push(type); + + if (isSpeculativeModeInUse(decl.node)) { + includesSpeculativeResult = true; + } + } else { + isIncomplete = true; + } + } catch (e: any) { + // Clean up the stack before rethrowing, but only if we haven't already popped. + if (!symbolPopped) { + popSymbolResolution(symbol); + } + throw e; + } + } else { + if (decl.type === DeclarationType.Class) { + const classTypeInfo = getTypeOfClass(decl.node); + if (classTypeInfo?.decoratedType) { + typesToCombine.push(classTypeInfo.decoratedType); + } + } + + isIncomplete = true; + + // Note that at least one decl could not be evaluated because + // it was already in the process of being evaluated. + sawPendingEvaluation = true; + } + }); + + // How many times have we already attempted to evaluate this declaration already? + const cacheEntries = effectiveTypeCache.get(symbol.id); + const evaluationAttempts = (cacheEntries?.get(typeCacheKey)?.evaluationAttempts ?? 0) + 1; + + let type: Type; + + if (typesToCombine.length > 0) { + // Ignore the pending evaluation flag if we've already attempted the + // type evaluation many times because this probably means there's a + // cyclical dependency that cannot be broken. + isIncomplete = sawPendingEvaluation && evaluationAttempts < maxEffectiveTypeEvaluationAttempts; + + type = combineTypes(typesToCombine); + } else { + // We can encounter this situation in the case of a bare ClassVar annotation. + if (symbol.isClassVar()) { + type = UnknownType.create(); + isIncomplete = false; + } else { + type = UnboundType.create(); + } + } + + return { type, isIncomplete, includesSpeculativeResult, evaluationAttempts }; + } + + // If a declaration has an explicit type (e.g. a variable with an annotation), + // this function evaluates the type and returns it. If the symbol has no + // explicit declared type, its type will need to be inferred instead. In some + // cases, non-type information (such as Final or ClassVar attributes) may be + // provided, but type inference is still required. In such cases, the attributes + // are returned as flags. + function getDeclaredTypeOfSymbol(symbol: Symbol, usageNode?: NameNode): DeclaredSymbolTypeInfo { + const synthesizedType = symbol.getSynthesizedType()?.type; + if (synthesizedType) { + return { type: synthesizedType }; + } + + let typedDecls = symbol.getTypedDeclarations(); + + if (typedDecls.length === 0) { + // If the symbol has no type declaration but is assigned many times, + // treat it as though it has an explicit type annotation of "Unknown". + // This will avoid a pathological performance condition for unannotated + // code that reassigns the same variable hundreds of times. If the symbol + // effectively has an "Any" annotation, it won't be narrowed. + if (symbol.getDeclarations().length > maxDeclarationsToUseForInference) { + return { type: UnknownType.create() }; + } + + // There was no declaration with a defined type. + return { type: undefined }; + } + + // If there is more than one typed decl, filter out any that are not + // reachable from the usage node (if specified). This can happen in + // cases where a property symbol is redefined to add a setter, deleter, + // etc. + let exceedsMaxDecls = false; + // Viper 扩展:跳过 flow 可达性过滤(AOT 静态类型语义)。 + // Viper 是 AOT 静态语言,变量类型在声明时确定(如 `arg: Argument | t.CPtr`), + // 不应因 flow 路径未建立而过滤 typed declarations。否则函数内多赋值变量 + // 的第一个使用点可能拿到 Unbound(如 argparse.vp 的 `arg.action` 报 + // "Cannot access member on unresolved type"),而后续使用点才正常。 + // 命中场景:parse_args 内 `arg: Argument | t.CPtr` 被循环内多次赋值, + // 第一个 `arg.action` 访问时 flow 图尚未覆盖声明节点。 + let isViperTypedDecl = false; + if (usageNode) { + const usageFileInfo = AnalyzerNodeInfo.getFileInfo(usageNode); + isViperTypedDecl = + IsViperDialect(usageFileInfo.executionEnvironment.dialect) && typedDecls.length > 0; + } + if (usageNode && typedDecls.length > 1 && !isViperTypedDecl) { + if (typedDecls.length > maxTypedDeclsPerSymbol) { + // If there are too many typed decls, don't bother filtering them + // because this can be very expensive. Simply use the last one + // in this case. + typedDecls = [typedDecls[typedDecls.length - 1]]; + exceedsMaxDecls = true; + } else { + const filteredTypedDecls = typedDecls.filter((decl) => { + if (decl.type !== DeclarationType.Alias) { + // Is the declaration in the same execution scope as the "usageNode" node? + const usageScope = ParseTreeUtils.getExecutionScopeNode(usageNode); + const declScope = ParseTreeUtils.getExecutionScopeNode(decl.node); + + if (usageScope === declScope) { + // For typed declarations we use the precise flow-graph reachability + // check rather than a simple position comparison, because typed decls + // (e.g. explicit annotations) can legitimately appear after the usage + // in the source text (e.g. a class attribute annotated below a method + // that references it) and must not be excluded by position alone. + if (!isFlowPathBetweenNodes(decl.node, usageNode, /* allowSelf */ false)) { + return false; + } + } + } + return true; + }); + + if (filteredTypedDecls.length === 0) { + return { type: UnboundType.create() }; + } + + typedDecls = filteredTypedDecls; + } + } + + // Start with the last decl. If that's already being resolved, + // use the next-to-last decl, etc. This can happen when resolving + // property methods. Often the setter method is defined in reference to + // the initial property, which defines the getter method with the same + // symbol name. + let declIndex = typedDecls.length - 1; + while (declIndex >= 0) { + const decl = typedDecls[declIndex]; + + // If there's a partially-constructed type that is allowed + // for recursive symbol resolution, return it as the resolved type. + const partialType = getSymbolResolutionPartialType(symbol, decl); + if (partialType) { + return { type: partialType }; + } + + if (getIndexOfSymbolResolution(symbol, decl) < 0) { + if (pushSymbolResolution(symbol, decl)) { + try { + const declaredTypeInfo = getTypeForDeclaration(decl); + + // If there was recursion detected, don't use this declaration. + // The exception is it's a class declaration because getTypeOfClass + // handles recursion by populating a partially-created class type + // in the type cache. This exception is required to handle the + // circular dependency between the "type" and "object" classes in + // builtins.pyi (since "object" is a "type" and "type" is an "object"). + if (popSymbolResolution(symbol) || decl.type === DeclarationType.Class) { + return declaredTypeInfo; + } + } catch (e: any) { + // Clean up the stack before rethrowing. + popSymbolResolution(symbol); + throw e; + } + } + } + + declIndex--; + } + + return { type: undefined, exceedsMaxDecls }; + } + + function inferReturnTypeIfNecessary(type: Type) { + if (isFunction(type)) { + getEffectiveReturnType(type); + } else if (isOverloaded(type)) { + OverloadedType.getOverloads(type).forEach((overload) => { + getEffectiveReturnType(overload); + }); + + const impl = OverloadedType.getImplementation(type); + if (impl && isFunction(impl)) { + getEffectiveReturnType(impl); + } + } + } + + function getEffectiveReturnType(type: FunctionType): Type { + return getEffectiveReturnTypeResult(type).type; + } + + function getInferredReturnType(type: FunctionType): Type { + return getInferredReturnTypeResult(type).type; + } + + // Returns the return type of the function. If the type is explicitly provided in + // a type annotation, that type is returned. If not, an attempt is made to infer + // the return type. If a list of args is provided, the inference logic may take + // into account argument types to infer the return type. + function getEffectiveReturnTypeResult(type: FunctionType, options?: EffectiveReturnTypeOptions): TypeResult { + const specializedReturnType = FunctionType.getEffectiveReturnType(type, /* includeInferred */ false); + if (specializedReturnType && !isUnknown(specializedReturnType)) { + return { type: specializedReturnType }; + } + + return getInferredReturnTypeResult(type, options?.callSiteInfo); + } + + function _getInferredReturnTypeResult(type: FunctionType, callSiteInfo?: CallSiteEvaluationInfo): TypeResult { + let returnType: Type | undefined; + let isIncomplete = false; + const analyzeUnannotatedFunctions = true; + + // Don't attempt to infer the return type for a stub file. + if (FunctionType.isStubDefinition(type)) { + return { type: UnknownType.create() }; + } + + // Don't infer the return type for a ParamSpec value. + if (FunctionType.isParamSpecValue(type)) { + return { type: UnknownType.create() }; + } + + // Don't infer the return type for an overloaded function (unless it's synthesized, + // which is needed for proper operation of the __get__ method in properties). + if (FunctionType.isOverloaded(type) && !FunctionType.isSynthesizedMethod(type)) { + return { type: UnknownType.create() }; + } + + const evalCount = type.shared.inferredReturnType?.evaluationCount ?? 0; + + // If the return type has already been lazily evaluated, + // don't bother computing it again. + if (type.shared.inferredReturnType && !type.shared.inferredReturnType.isIncomplete) { + returnType = type.shared.inferredReturnType.type; + } else if (evalCount > maxReturnTypeInferenceAttempts) { + // Detect a case where a return type won't converge because of recursion. + returnType = UnknownType.create(); + } else { + // Don't bother inferring the return type of __init__ because it's + // always None. + if (FunctionType.isInstanceMethod(type) && type.shared.name === '__init__') { + returnType = getNoneType(); + } else if (type.shared.declaration) { + const functionNode = type.shared.declaration.node; + const skipUnannotatedFunction = + !AnalyzerNodeInfo.getFileInfo(functionNode).diagnosticRuleSet.analyzeUnannotatedFunctions && + ParseTreeUtils.isUnannotatedFunction(functionNode); + + // Skip return type inference if we are in "skip unannotated function" mode. + if (!skipUnannotatedFunction && !checkCodeFlowTooComplex(functionNode.d.suite)) { + const codeFlowComplexity = AnalyzerNodeInfo.getCodeFlowComplexity(functionNode); + + // For very complex functions that have no annotated parameter types, + // don't attempt to infer the return type because it can be extremely + // expensive. + const parametersAreAnnotated = + type.shared.parameters.length <= 1 || + type.shared.parameters.some((param) => FunctionParam.isTypeDeclared(param)); + + if (parametersAreAnnotated || codeFlowComplexity < maxReturnTypeInferenceCodeFlowComplexity) { + // Temporarily disable speculative mode while we + // lazily evaluate the return type. + let returnTypeResult: TypeResult | undefined; + disableSpeculativeMode(() => { + returnTypeResult = inferFunctionReturnType( + functionNode, + FunctionType.isAbstractMethod(type), + callSiteInfo?.errorNode + ); + }); + + returnType = returnTypeResult?.type; + if (returnTypeResult?.isIncomplete) { + isIncomplete = true; + } + } + } + } + + if (!returnType) { + returnType = UnknownType.create(); + } + + // Externalize any TypeVars that appear in the type. + const typeVarScopes: TypeVarScopeId[] = []; + if (type.shared.typeVarScopeId) { + typeVarScopes.push(type.shared.typeVarScopeId); + } + if (type.shared.methodClass?.shared.typeVarScopeId) { + typeVarScopes.push(type.shared.methodClass.shared.typeVarScopeId); + } + returnType = makeTypeVarsFree(returnType, typeVarScopes); + + // Cache the type for next time. + type.shared.inferredReturnType = { type: returnType, isIncomplete, evaluationCount: evalCount + 1 }; + } + + // If the type is partially unknown and the function has one or more unannotated + // params, try to analyze the function with the provided argument types and + // attempt to do a better job at inference. + if ( + !isIncomplete && + analyzeUnannotatedFunctions && + isPartlyUnknown(returnType) && + FunctionType.hasUnannotatedParams(type) && + !FunctionType.isStubDefinition(type) && + !FunctionType.isPyTypedDefinition(type) && + callSiteInfo + ) { + let hasDecorators = false; + let isAsync = false; + const declNode = type.shared.declaration?.node; + if (declNode) { + if (declNode.d.decorators.length > 0) { + hasDecorators = true; + } + if (declNode.d.isAsync) { + isAsync = true; + } + } + + // We can't use this technique if decorators or async are used because they + // would need to be applied to the inferred return type. + if (!hasDecorators && !isAsync) { + const contextualReturnType = inferReturnTypeForCallSite(type, callSiteInfo); + if (contextualReturnType) { + returnType = contextualReturnType; + + if (type.shared.declaration?.node) { + // Externalize any TypeVars that appear in the type. + const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(type.shared.declaration.node); + returnType = makeTypeVarsFree(returnType, liveScopeIds); + } + } + } + } + + return { type: returnType, isIncomplete }; + } + + function inferReturnTypeForCallSite(type: FunctionType, callSiteInfo: CallSiteEvaluationInfo): Type | undefined { + const args = callSiteInfo.args; + let contextualReturnType: Type | undefined; + + if (!type.shared.declaration) { + return undefined; + } + const functionNode = type.shared.declaration.node; + const codeFlowComplexity = AnalyzerNodeInfo.getCodeFlowComplexity(functionNode); + + if (codeFlowComplexity >= maxReturnCallSiteTypeInferenceCodeFlowComplexity) { + return undefined; + } + + // If an arg hasn't been matched to a specific named parameter, + // it's an unpacked value that corresponds to multiple parameters. + // That's an edge case that we don't handle here. + if (args.some((arg) => !arg.paramName)) { + return undefined; + } + + // Detect recurrence. If a function invokes itself either directly + // or indirectly, we won't attempt to infer contextual return + // types any further. + if (returnTypeInferenceContextStack.some((context) => context.functionNode === functionNode)) { + return undefined; + } + + const functionTypeResult = getTypeOfFunction(functionNode); + if (!functionTypeResult) { + return undefined; + } + + // Very complex functions with many arguments can take a long time to analyze, + // so we'll use a heuristic and avoiding this inference technique for any + // call site that involves too many arguments. + if (args.length > maxReturnTypeInferenceArgCount) { + return undefined; + } + + // Don't explore arbitrarily deep in the call graph. + if (returnTypeInferenceContextStack.length >= maxReturnTypeInferenceStackSize) { + return undefined; + } + + const paramTypes: Type[] = []; + let isResultFromCache = false; + + // If the call is located in a loop, don't use literal argument types + // for the same reason we don't do literal math in loops. + const stripLiteralArgTypes = ParseTreeUtils.isWithinLoop(callSiteInfo.errorNode); + + // Suppress diagnostics because we don't want to generate errors. + suppressDiagnostics(functionNode, () => { + // Allocate a new temporary type cache for the context of just + // this function so we can analyze it separately without polluting + // the main type cache. + const prevTypeCache = returnTypeInferenceTypeCache; + returnTypeInferenceContextStack.push({ + functionNode, + codeFlowAnalyzer: codeFlowEngine.createCodeFlowAnalyzer(), + }); + + try { + returnTypeInferenceTypeCache = new Map(); + + let allArgTypesAreUnknown = true; + functionNode.d.params.forEach((param, index) => { + if (param.d.name) { + let paramType: Type | undefined; + const arg = args.find((arg) => param.d.name!.d.value === arg.paramName); + + if (arg && arg.argument.valueExpression) { + paramType = getTypeOfExpression(arg.argument.valueExpression).type; + if (!isUnknown(paramType)) { + allArgTypesAreUnknown = false; + } + } else if (param.d.defaultValue) { + paramType = getTypeOfExpression(param.d.defaultValue).type; + if (!isUnknown(paramType)) { + allArgTypesAreUnknown = false; + } + } else if (index === 0) { + // If this is an instance or class method, use the implied + // parameter type for the "self" or "cls" parameter. + if ( + FunctionType.isInstanceMethod(functionTypeResult.functionType) || + FunctionType.isClassMethod(functionTypeResult.functionType) + ) { + if (functionTypeResult.functionType.shared.parameters.length > 0) { + if (functionNode.d.params[0].d.name) { + paramType = FunctionType.getParamType(functionTypeResult.functionType, 0); + } + } + } + } + + if (!paramType) { + paramType = UnknownType.create(); + } + + if (stripLiteralArgTypes) { + paramType = stripTypeForm( + convertSpecialFormToRuntimeValue( + stripLiteralValue(paramType), + EvalFlags.None, + /* convertModule */ true + ) + ); + } + + paramTypes.push(paramType); + writeTypeCache(param.d.name, { type: paramType }, EvalFlags.None); + } + }); + + // Don't bother trying to determine the contextual return + // type if none of the argument types are known. + if (!allArgTypesAreUnknown) { + // See if the return type is already cached. If so, skip the + // inference step, which is potentially very expensive. + const cacheEntry = functionTypeResult.functionType.priv.callSiteReturnTypeCache?.find((entry) => { + return ( + entry.paramTypes.length === paramTypes.length && + entry.paramTypes.every((t, i) => isTypeSame(t, paramTypes[i])) + ); + }); + + if (cacheEntry) { + contextualReturnType = cacheEntry.returnType; + isResultFromCache = true; + } else { + contextualReturnType = inferFunctionReturnType( + functionNode, + FunctionType.isAbstractMethod(type), + callSiteInfo?.errorNode + )?.type; + } + } + } finally { + returnTypeInferenceContextStack.pop(); + returnTypeInferenceTypeCache = prevTypeCache; + } + }); + + if (contextualReturnType) { + contextualReturnType = removeUnbound(contextualReturnType); + + if (!isResultFromCache) { + // Cache the resulting type. + if (!functionTypeResult.functionType.priv.callSiteReturnTypeCache) { + functionTypeResult.functionType.priv.callSiteReturnTypeCache = []; + } + if ( + functionTypeResult.functionType.priv.callSiteReturnTypeCache.length >= + maxCallSiteReturnTypeCacheSize + ) { + functionTypeResult.functionType.priv.callSiteReturnTypeCache = + functionTypeResult.functionType.priv.callSiteReturnTypeCache.slice(1); + } + functionTypeResult.functionType.priv.callSiteReturnTypeCache.push({ + paramTypes, + returnType: contextualReturnType, + }); + } + + return contextualReturnType; + } + + return undefined; + } + + // If the function has an explicitly-declared return type, it is returned + // unaltered unless the function is a generator, in which case it is + // modified to return only the return type for the generator. + function getDeclaredReturnType(node: FunctionNode): Type | undefined { + const functionTypeInfo = getTypeOfFunction(node); + const returnType = functionTypeInfo?.functionType.shared.declaredReturnType; + + if (!returnType) { + return undefined; + } + + if (FunctionType.isGenerator(functionTypeInfo.functionType)) { + return getDeclaredGeneratorReturnType(functionTypeInfo.functionType); + } + + return returnType; + } + + function getTypeOfMember(member: ClassMember): Type { + if (isInstantiableClass(member.classType)) { + return partiallySpecializeType( + getEffectiveTypeOfSymbol(member.symbol), + member.classType, + getTypeClassType(), + /* selfClass */ undefined + ); + } + return UnknownType.create(); + } + + function getTypeOfMemberInternal( + errorNode: ExpressionNode | undefined, + member: ClassMember, + selfClass: ClassType | TypeVarType | undefined, + flags: MemberAccessFlags + ): TypeResult | undefined { + if (isAnyOrUnknown(member.classType)) { + return { + type: member.classType, + isIncomplete: false, + }; + } + + if (!isInstantiableClass(member.classType)) { + return undefined; + } + + const typeResult = getEffectiveTypeOfSymbolForUsage(member.symbol); + + if (!typeResult) { + return undefined; + } + + // Report inappropriate use of variables in type expressions. + if ((flags & MemberAccessFlags.TypeExpression) !== 0 && errorNode) { + typeResult.type = validateSymbolIsTypeExpression( + errorNode, + typeResult.type, + !!typeResult.includesVariableDecl + ); + } + + // If the type is a function or overloaded function, infer + // and cache the return type if necessary. This needs to be done + // prior to specializing. + inferReturnTypeIfNecessary(typeResult.type); + + // Check for ambiguous accesses to attributes with generic types? + if ( + errorNode && + selfClass && + isClass(selfClass) && + member.isInstanceMember && + isClass(member.unspecializedClassType) && + (flags & MemberAccessFlags.DisallowGenericInstanceVariableAccess) !== 0 && + requiresSpecialization(typeResult.type, { ignoreSelf: true, ignoreImplicitTypeArgs: true }) + ) { + const specializedType = partiallySpecializeType( + typeResult.type, + member.unspecializedClassType, + getTypeClassType(), + selfSpecializeClass(selfClass, { overrideTypeArgs: true }) + ); + + if ( + findSubtype( + specializedType, + (subtype) => + !isFunctionOrOverloaded(subtype) && + requiresSpecialization(subtype, { ignoreSelf: true, ignoreImplicitTypeArgs: true }) + ) + ) { + addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.genericInstanceVariableAccess(), + errorNode + ); + } + } + + return { + type: partiallySpecializeType(typeResult.type, member.classType, getTypeClassType(), selfClass), + isIncomplete: !!typeResult.isIncomplete, + }; + } + + function assignClass( + destType: ClassType, + srcType: ClassType, + diag: DiagnosticAddendum | undefined, + constraints: ConstraintTracker | undefined, + flags: AssignTypeFlags, + recursionCount: number, + reportErrorsUsingObjType: boolean + ): boolean { + // If the source or dest types are partially evaluated (i.e. they are in the + // process of being constructed), assume they are assignable rather than risk + // emitting false positives. + if (ClassType.isHierarchyPartiallyEvaluated(destType) || ClassType.isHierarchyPartiallyEvaluated(srcType)) { + return true; + } + + // Handle typed dicts. They also use a form of structural typing for type + // checking, as defined in PEP 589. + if (ClassType.isTypedDictClass(srcType)) { + if (ClassType.isTypedDictClass(destType) && !ClassType.isSameGenericClass(destType, srcType)) { + if ( + !assignTypedDictToTypedDict( + evaluatorInterface, + destType, + srcType, + diag, + constraints, + flags, + recursionCount + ) + ) { + return false; + } + + // If invariance is being enforced, the two TypedDicts must be assignable to each other. + if ((flags & AssignTypeFlags.Invariant) !== 0) { + return assignTypedDictToTypedDict( + evaluatorInterface, + srcType, + destType, + /* diag */ undefined, + /* constraints */ undefined, + flags, + recursionCount + ); + } + + return true; + } + + // Handle some special cases where a TypedDict can act like + // a Mapping[str, T] or a dict[str, T]. + if (ClassType.isBuiltIn(destType, 'Mapping')) { + const mappingValueType = getTypedDictMappingEquivalent(evaluatorInterface, srcType); + + if ( + mappingValueType && + prefetched?.mappingClass && + isInstantiableClass(prefetched.mappingClass) && + prefetched?.strClass && + isInstantiableClass(prefetched.strClass) + ) { + srcType = ClassType.specialize(prefetched.mappingClass, [ + ClassType.cloneAsInstance(prefetched.strClass), + mappingValueType, + ]); + } + } else if (ClassType.isBuiltIn(destType, ['dict', 'MutableMapping'])) { + const dictValueType = getTypedDictDictEquivalent(evaluatorInterface, srcType, recursionCount); + + if ( + dictValueType && + prefetched?.dictClass && + isInstantiableClass(prefetched.dictClass) && + prefetched.strClass && + isInstantiableClass(prefetched.strClass) + ) { + srcType = ClassType.specialize(prefetched.dictClass, [ + ClassType.cloneAsInstance(prefetched.strClass), + dictValueType, + ]); + } + } + } + + // Handle special-case type promotions. + if (destType.priv.includePromotions) { + const promotionList = typePromotions.get(destType.shared.fullName); + if ( + promotionList && + promotionList.some((srcName) => + srcType.shared.mro.some((mroClass) => isClass(mroClass) && srcName === mroClass.shared.fullName) + ) + ) { + if ((flags & AssignTypeFlags.Invariant) === 0) { + return true; + } + } + } + + // Is it a structural type (i.e. a protocol)? If so, we need to + // perform a member-by-member check. + const inheritanceChain: InheritanceChain = []; + const isDerivedFrom = ClassType.isDerivedFrom(srcType, destType, inheritanceChain); + + // Viper 扩展:CType 整数/浮点子类 -> 协议类(如 SupportsIndex/SupportsFloat)隐式转换。 + // Viper 中 CInt/CUInt32T 等 CType 整数子类语义上等价于 Python int, + // CFloat/CDouble 等 CType 浮点子类语义上等价于 Python float。 + // int 满足 SupportsIndex(有 __index__)等数值协议, + // float 满足 SupportsFloat(有 __float__)等数值协议。 + // 因此 CType 整数/浮点子类也应满足对应协议,避免误报。 + // 覆盖场景: + // - int.__new__(cls, ry: CInt) 中 ry 作为 SupportsIndex 参数传入 + // - float.__new__(cls, val: CFloat) 中 val 作为 SupportsFloat 参数传入 + if ( + ClassType.isProtocolClass(destType) && !isDerivedFrom && + isCTypeSubclassType(srcType) && ( + isViperCIntegerTypeName(srcType.shared.name) || + isViperCFloatTypeName(srcType.shared.name) + ) + ) { + return true; + } + + // Use the slow path for protocols if the dest doesn't explicitly + // derive from the source. We also need to use this path if we're + // testing to see if the metaclass matches the protocol. + if (ClassType.isProtocolClass(destType) && !isDerivedFrom) { + if ( + !assignClassToProtocol( + evaluatorInterface, + destType, + ClassType.cloneAsInstance(srcType), + diag?.createAddendum(), + constraints, + flags, + recursionCount + ) + ) { + diag?.addMessage( + LocAddendum.protocolIncompatible().format({ + sourceType: printType(convertToInstance(srcType)), + destType: printType(convertToInstance(destType)), + }) + ); + return false; + } + + return true; + } + + if ((flags & AssignTypeFlags.Invariant) === 0 || ClassType.isSameGenericClass(srcType, destType)) { + if (isDerivedFrom) { + assert(inheritanceChain.length > 0); + + if ( + assignClassWithTypeArgs( + destType, + srcType, + inheritanceChain, + diag?.createAddendum(), + constraints, + flags, + recursionCount + ) + ) { + return true; + } + } + } + + // Everything is assignable to an object. + if (ClassType.isBuiltIn(destType, 'object')) { + if ((flags & AssignTypeFlags.Invariant) === 0) { + return true; + } + } + + if (diag) { + const destErrorType = reportErrorsUsingObjType ? ClassType.cloneAsInstance(destType) : destType; + const srcErrorType = reportErrorsUsingObjType ? ClassType.cloneAsInstance(srcType) : srcType; + + let destErrorTypeText = printType(destErrorType); + let srcErrorTypeText = printType(srcErrorType); + + // If the text is the same, use the fully-qualified name rather than the short name. + if (destErrorTypeText === srcErrorTypeText && destType.shared.fullName && srcType.shared.fullName) { + destErrorTypeText = destType.shared.fullName; + srcErrorTypeText = srcType.shared.fullName; + } + + diag?.addMessage( + LocAddendum.typeIncompatible().format({ + sourceType: srcErrorTypeText, + destType: destErrorTypeText, + }) + ); + + // Tell the user about the disableBytesTypePromotions if that is involved. + if (ClassType.isBuiltIn(destType, 'bytes')) { + const promotions = typePromotions.get(destType.shared.fullName); + if (promotions && promotions.some((name) => name === srcType.shared.fullName)) { + diag?.addMessage(LocAddendum.bytesTypePromotions()); + } + } + } + + return false; + } + + // This function is used to validate or infer the variance of type + // parameters within a class. If ignoreBaseClassVariance is set to false, + // the type parameters for the base class are honored. This is useful for + // variance inference (PEP 695). For validation of protocol variance, we + // want to ignore the variance for all base classes in the class hierarchy. + function assignClassToSelf( + destType: ClassType, + srcType: ClassType, + assumedVariance: Variance, + ignoreBaseClassVariance = true, + recursionCount = 0 + ): boolean { + assert(ClassType.isSameGenericClass(destType, srcType)); + assert(destType.shared.typeParams.length > 0); + + srcType = makeTypeVarsBound(srcType, getTypeVarScopeIds(srcType)); + destType = makeTypeVarsBound(destType, getTypeVarScopeIds(destType)); + + let isAssignable = true; + + // Use a try/catch block here to make sure that we reset + // the assignClassToSelfClass to undefined if an exception occurs. + try { + // Stash the current class type so any references to it are treated + // as though all type parameters are invariant. + assignClassToSelfStack.push({ class: destType, assumedVariance }); + + ClassType.getSymbolTable(destType).forEach((symbol, name) => { + if (!isAssignable || symbol.isIgnoredForProtocolMatch()) { + return; + } + + // Constructor methods are exempt from variance calculations. + if (name === '__new__' || name === '__init__') { + return; + } + + const memberInfo = lookUpClassMember(srcType, name); + assert(memberInfo !== undefined); + + let destMemberType = getEffectiveTypeOfSymbol(symbol); + const srcMemberType = getTypeOfMember(memberInfo); + destMemberType = partiallySpecializeType(destMemberType, destType, getTypeClassType()); + + // Properties require special processing. + if ( + isClassInstance(destMemberType) && + ClassType.isPropertyClass(destMemberType) && + isClassInstance(srcMemberType) && + ClassType.isPropertyClass(srcMemberType) + ) { + if ( + !assignProperty( + evaluatorInterface, + ClassType.cloneAsInstantiable(destMemberType), + ClassType.cloneAsInstantiable(srcMemberType), + destType, + srcType, + /* diag */ undefined, + /* constraints */ undefined, + /* selfConstraints */ undefined, + recursionCount + ) + ) { + isAssignable = false; + } + } else { + const primaryDecl = symbol.getDeclarations()[0]; + + let flags = AssignTypeFlags.Default; + if ( + primaryDecl?.type === DeclarationType.Variable && + !isFinalVariableDeclaration(primaryDecl) && + !isMemberReadOnly(destType, name) + ) { + // Class and instance variables that are mutable need to + // enforce invariance. We will exempt variables that are + // private or protected, since these are presumably + // not modifiable outside of the class. + if (!isPrivateOrProtectedName(name)) { + flags |= AssignTypeFlags.Invariant; + } + } + + if ( + !assignType( + destMemberType, + srcMemberType, + /* diag */ undefined, + /* constraints */ undefined, + flags | AssignTypeFlags.SkipSelfClsParamCheck, + recursionCount + ) + ) { + isAssignable = false; + } + } + }); + + if (!isAssignable) { + return false; + } + + // Now handle generic base classes. + destType.shared.baseClasses.forEach((baseClass) => { + if ( + !isAssignable || + !isInstantiableClass(baseClass) || + ClassType.isBuiltIn(baseClass, ['object', 'Protocol', 'Generic']) || + baseClass.shared.typeParams.length === 0 + ) { + return; + } + + const specializedDestBaseClass = specializeForBaseClass(destType, baseClass); + const specializedSrcBaseClass = specializeForBaseClass(srcType, baseClass); + + if (!ignoreBaseClassVariance) { + specializedDestBaseClass.shared.typeParams.forEach((param, index) => { + if (isParamSpec(param) || isTypeVarTuple(param) || param.shared.isSynthesized) { + return; + } + + if ( + !specializedSrcBaseClass.priv.typeArgs || + index >= specializedSrcBaseClass.priv.typeArgs.length || + !specializedDestBaseClass.priv.typeArgs || + index >= specializedDestBaseClass.priv.typeArgs.length + ) { + return; + } + + const paramVariance = param.shared.declaredVariance; + if (isTypeVar(specializedSrcBaseClass.priv.typeArgs[index])) { + if (paramVariance === Variance.Invariant || paramVariance === Variance.Contravariant) { + isAssignable = false; + return; + } + } + + if (isTypeVar(specializedDestBaseClass.priv.typeArgs[index])) { + if (paramVariance === Variance.Invariant || paramVariance === Variance.Covariant) { + isAssignable = false; + return; + } + } + }); + } + + if (!isAssignable) { + return; + } + + // Handle tuples specially since their type arguments are variadic. + if (ClassType.isTupleClass(specializedDestBaseClass)) { + return; + } + + if ( + !assignClassToSelf( + specializedDestBaseClass, + specializedSrcBaseClass, + assumedVariance, + ignoreBaseClassVariance, + recursionCount + ) + ) { + isAssignable = false; + } + }); + + return isAssignable; + } finally { + assignClassToSelfStack.pop(); + } + } + + // Determines whether the specified type can be assigned to the + // specified inheritance chain, taking into account its type arguments. + function assignClassWithTypeArgs( + destType: ClassType, + srcType: ClassType, + inheritanceChain: InheritanceChain, + diag: DiagnosticAddendum | undefined, + constraints: ConstraintTracker | undefined, + flags: AssignTypeFlags, + recursionCount: number + ): boolean { + let curSrcType = srcType; + let prevSrcType: ClassType | undefined; + + inferVarianceForClass(destType); + + // If we're enforcing invariance, literal types must match. + if ((flags & AssignTypeFlags.Invariant) !== 0) { + const srcIsLiteral = isLiteralLikeType(srcType); + const destIsLiteral = isLiteralLikeType(destType); + + if (srcIsLiteral !== destIsLiteral) { + return false; + } + } + + for (let ancestorIndex = inheritanceChain.length - 1; ancestorIndex >= 0; ancestorIndex--) { + const ancestorType = inheritanceChain[ancestorIndex]; + + // If we've hit an "unknown", all bets are off, and we need to assume + // that the type is assignable. If the destType is marked "@final", + // we should be able to assume that it's not assignable, but we can't do + // this in the general case because it breaks assumptions with the + // NotImplemented symbol exported by typeshed's builtins.pyi. Instead, + // we'll special-case only None. + if (isUnknown(ancestorType)) { + return !isNoneTypeClass(destType); + } + + // If this isn't the first time through the loop, specialize + // for the next ancestor in the chain. + if (ancestorIndex < inheritanceChain.length - 1) { + // If the curSrcType is a NamedTuple and the ancestorType is a tuple, + // we need to handle this as a special case because the NamedTuple may + // include typeParams from its parent class. + let effectiveCurSrcType = curSrcType; + if ( + ClassType.isBuiltIn(curSrcType, 'NamedTuple') && + ClassType.isBuiltIn(ancestorType, 'tuple') && + prevSrcType + ) { + effectiveCurSrcType = prevSrcType; + } + + curSrcType = specializeForBaseClass(effectiveCurSrcType, ancestorType); + } + + // If there are no type parameters on this class, we're done. + const ancestorTypeParams = ClassType.getTypeParams(ancestorType); + if (ancestorTypeParams.length === 0) { + continue; + } + + // If the dest type isn't specialized, there are no type args to validate. + if (!ancestorType.priv.typeArgs) { + return true; + } + + prevSrcType = curSrcType; + } + + // Handle tuple, which supports a variable number of type arguments. + if (destType.priv.tupleTypeArgs && curSrcType.priv.tupleTypeArgs) { + return assignTupleTypeArgs( + evaluatorInterface, + destType, + curSrcType, + diag, + constraints, + flags, + recursionCount + ); + } + + if (destType.priv.typeArgs) { + // If the dest type is specialized, make sure the specialized source + // type arguments are assignable to the dest type arguments. + return assignTypeArgs( + destType, + curSrcType, + // Don't emit a diag addendum if we're in an invariant context. It's + // sufficient to simply indicate that the types are not the same + // in this case. Adding more information is unnecessary and confusing. + (flags & AssignTypeFlags.Invariant) === 0 ? diag : undefined, + constraints, + flags, + recursionCount + ); + } + + if (constraints && curSrcType.priv.typeArgs) { + // Populate the typeVar map with type arguments of the source. + const srcTypeArgs = curSrcType.priv.typeArgs; + for (let i = 0; i < destType.shared.typeParams.length; i++) { + let typeArgType: Type; + const typeParam = destType.shared.typeParams[i]; + const variance = TypeVarType.getVariance(typeParam); + + if (curSrcType.priv.tupleTypeArgs) { + typeArgType = convertToInstance( + makeTupleObject(evaluatorInterface, curSrcType.priv.tupleTypeArgs, /* isUnpacked */ true) + ); + } else { + typeArgType = i < srcTypeArgs.length ? srcTypeArgs[i] : UnknownType.create(); + } + + constraints.setBounds( + typeParam, + variance !== Variance.Contravariant ? typeArgType : undefined, + variance !== Variance.Covariant ? typeArgType : undefined, + /* retainLiterals */ true + ); + } + } + + return true; + } + + function getGetterTypeFromProperty(propertyClass: ClassType): Type | undefined { + if (!ClassType.isPropertyClass(propertyClass)) { + return undefined; + } + + if (propertyClass.priv.fgetInfo && isFunction(propertyClass.priv.fgetInfo.methodType)) { + return getEffectiveReturnType(propertyClass.priv.fgetInfo.methodType); + } + + return undefined; + } + + function assignTypeArgs( + destType: ClassType, + srcType: ClassType, + diag: DiagnosticAddendum | undefined, + constraints: ConstraintTracker | undefined, + flags: AssignTypeFlags, + recursionCount: number + ): boolean { + assert(ClassType.isSameGenericClass(destType, srcType)); + + inferVarianceForClass(destType); + + const destTypeParams = ClassType.getTypeParams(destType); + let destTypeArgs: Type[]; + let srcTypeArgs: Type[] | undefined; + + // Are we performing protocol variance validation for this class? If so, + // treat all of the type parameters as invariant even if they are declared + // otherwise. + const assignClassToSelfInfo = assignClassToSelfStack.find((info) => + ClassType.isSameGenericClass(info.class, destType) + ); + const assumedVariance = assignClassToSelfInfo?.assumedVariance; + + // If either source or dest type arguments are missing, they are + // treated as "Any", so they are assumed to be assignable. + if (!destType.priv.typeArgs || !srcType.priv.typeArgs) { + return true; + } + + if (ClassType.isTupleClass(destType)) { + destTypeArgs = destType.priv.tupleTypeArgs?.map((t) => t.type) ?? []; + srcTypeArgs = srcType.priv.tupleTypeArgs?.map((t) => t.type); + } else { + destTypeArgs = destType.priv.typeArgs; + srcTypeArgs = srcType.priv.typeArgs; + } + + let isCompatible = true; + + srcTypeArgs?.forEach((srcTypeArg, srcArgIndex) => { + // In most cases, the number of type args should match the number + // of type arguments, but there are a few special cases where this + // isn't true (e.g. assigning a Tuple[X, Y, Z] to a tuple[W]). + const destArgIndex = srcArgIndex >= destTypeArgs.length ? destTypeArgs.length - 1 : srcArgIndex; + const destTypeArg = destArgIndex >= 0 ? destTypeArgs[destArgIndex] : UnknownType.create(); + const destTypeParam = destArgIndex < destTypeParams.length ? destTypeParams[destArgIndex] : undefined; + const assignmentDiag = new DiagnosticAddendum(); + const variance = + assumedVariance ?? (destTypeParam ? TypeVarType.getVariance(destTypeParam) : Variance.Covariant); + let effectiveFlags: AssignTypeFlags; + let errorSource: () => ParameterizedString<{ name: string; sourceType: string; destType: string }>; + let includeDiagAddendum = true; + + if (variance === Variance.Covariant) { + effectiveFlags = flags | AssignTypeFlags.RetainLiteralsForTypeVar; + errorSource = LocAddendum.typeVarIsCovariant; + } else if (variance === Variance.Contravariant) { + effectiveFlags = flags | AssignTypeFlags.Contravariant | AssignTypeFlags.RetainLiteralsForTypeVar; + errorSource = LocAddendum.typeVarIsContravariant; + } else { + effectiveFlags = flags | AssignTypeFlags.Invariant | AssignTypeFlags.RetainLiteralsForTypeVar; + errorSource = LocAddendum.typeVarIsInvariant; + + // Omit the diagnostic addendum for the invariant case because it's obvious + // why two types are not the same. + includeDiagAddendum = false; + } + + // Special-case TypeForm to retain literals when solving TypeVars. + if (ClassType.isBuiltIn(destType, 'TypeForm')) { + effectiveFlags |= AssignTypeFlags.RetainLiteralsForTypeVar; + } + + if ( + !assignType( + variance === Variance.Contravariant ? srcTypeArg : destTypeArg, + variance === Variance.Contravariant ? destTypeArg : srcTypeArg, + assignmentDiag, + constraints, + effectiveFlags, + recursionCount + ) + ) { + // Don't report errors with type variables in "pseudo-random" + // classes since these type variables are not real. + if (!ClassType.isPseudoGenericClass(destType)) { + if (diag) { + if (destTypeParam) { + const childDiag = diag.createAddendum(); + + childDiag.addMessage( + errorSource().format({ + name: TypeVarType.getReadableName(destTypeParam), + ...printSrcDestTypes(srcTypeArg, destTypeArg), + }) + ); + + if (includeDiagAddendum) { + childDiag.addAddendum(assignmentDiag); + } + + if (isCompatible && ClassType.isSameGenericClass(destType, srcType)) { + // Add additional notes to help the user if this is a common type mismatch. + if (ClassType.isBuiltIn(destType, 'dict') && srcArgIndex === 1) { + childDiag.addMessage(LocAddendum.invariantSuggestionDict()); + } else if (ClassType.isBuiltIn(destType, 'list')) { + childDiag.addMessage(LocAddendum.invariantSuggestionList()); + } else if (ClassType.isBuiltIn(destType, 'set')) { + childDiag.addMessage(LocAddendum.invariantSuggestionSet()); + } + } + } else { + diag.addAddendum(assignmentDiag); + } + } + isCompatible = false; + } + } + }); + + return isCompatible; + } + + // Determines if the source type can be assigned to the dest type. + // If constraint are provided, type variables within the destType are + // matched against existing type variables in the map. If a type variable + // in the dest type is not in the type map already, it is assigned a type + // and added to the map. + function assignType( + destType: Type, + srcType: Type, + diag?: DiagnosticAddendum, + constraints?: ConstraintTracker, + flags = AssignTypeFlags.Default, + recursionCount = 0 + ): boolean { + // Viper 扩展:t.CDefine 任意转参转换语义。 + // CDefine 是编译时元指令,类型检查时视为"任意类型": + // - destType 是 CDefine:接受任何源类型(x: t.CDefine = 任意值) + // - srcType 是 CDefine:CDefine 值可传给任何参数(func(t.CDefine_value)) + if (isCDefineType(destType) || isCDefineType(srcType)) { + return true; + } + + // Viper 扩展:None → 指针类型隐式转换语义(NULL 指针)。 + // 在 C 语义中,NULL 宏等价于 (void*)0,用于表示空指针。 + // Viper 中 None 对应 C 的 NULL,可赋值给任何指针类型: + // - CPtr[T](任意指针) + // - CVoid(void*) + // - 包含 CPtr/CVoid 的 TypeSet(如 CVoid | CPtr) + // 这允许 _send_control(..., data_buf: CVoid | CPtr) 接受 None 作为参数。 + // CPtr/CVoid 是 Viper 特有类型,目标包含它们即为 Viper 代码,无需 dialect 检查。 + // 函数指针类型(FunctionType,如 irq_handler_t = t.Callable[[], t.CInt])同样可接受 + // None(NULL 函数指针语义),允许 def isr0() -> irq_handler_t: pass 或 return None。 + // 返回类型包含 C 声明标记(t.State/t.CExport 等)的也接受 None(无返回值), + // 允许 def CloseHandle() -> BOOL | t.State: pass 这类声明性函数的隐式 None。 + // str/bytes 在 Viper 中表示 C 字符串指针(i8*),同样接受 None(NULL 字符串指针), + // 允许 def strcpy(...) -> str: ... return None 这类返回 str 的函数返回空指针。 + if (isNoneInstance(srcType)) { + if (isPureCModifierType(destType)) { + return true; + } + const destVariants = isUnion(destType) ? destType.priv.subtypes : [destType]; + const hasPtrLike = destVariants.some( + (variant) => + isCPtrType(variant) || + isCVoidType(variant) || + isFunction(variant) || + (isClassInstance(variant) && ClassType.isBuiltIn(variant, ['str', 'bytes'])) + ); + if (hasPtrLike) { + return true; + } + } + + // Viper 扩展:t.CArray 自动降级语义。 + // C 字符数组 t.CArray[t.CChar, N] 可自动降级为 C 字符串 str 或字节序列 bytes, + // 实现 serial.puts(ncpu_cr) 等 API 的兼容传参。 + // 降级方向:CArray → str/bytes(单向,str/bytes 不自动升级为 CArray)。 + if (isCArrayType(srcType)) { + const destIsStrBytes = + (isClassInstance(destType) && ClassType.isBuiltIn(destType, 'str')) || + (isClassInstance(destType) && ClassType.isBuiltIn(destType, 'bytes')); + if (destIsStrBytes) { + return true; + } + } + + // Viper 扩展:函数名退化为函数指针(C 语义)。 + // C 语言中函数名在需要函数指针的位置自动退化为指针,如 + // create_thread(desktop_thread, ...) 中 desktop_thread 退化为 CPtr 传入 func: CVoid | CPtr。 + // 类似数组名退化为指针,函数名退化为函数指针是 C 标准行为。 + if (isFunction(srcType)) { + const destVariants = isUnion(destType) ? destType.priv.subtypes : [destType]; + const hasPtrLike = destVariants.some( + (variant) => isCPtrType(variant) || isCVoidType(variant) + ); + if (hasPtrLike) { + return true; + } + } + + // Viper 扩展:跨级指针级数守卫(双向,精确同级匹配)。 + // 规则:允许同级指针目标类型隐式转换;任何不同级的指针间隐式转换都拒绝, + // 无论 src 高→dest 低 还是 src 低→dest 高("不是同级都不行")。 + // 实现:src 为非联合指针时,收集 dest 所有指针子类型的级数集合, + // 若 dest 含指针但无与 src 同级者,则跨级拒绝。 + // 联合 src(含 TypeSet)由 assignType 后续拆分逻辑递归处理,子类型重新进入本守卫。 + // 命中场景: + // - a: t.CPtr = t.CPtr(x, t.CPtr) (src 2级,dest 指针级数集合 {1}) + // - a: t.CPtr[t.CPtr[T]] = t.CPtr(x) (src 1级,dest 指针级数集合 {2}) + // - t.CPtr(x, t.CPtr) 传 arg: CVoid|CPtr (src 2级,dest 指针级数集合 {1}) + // 不影响:值↔值、同级指针、值↔指针现有逻辑(dest 无指针子类型时不介入)。 + // 多级 CPtr 是 Viper 特有类型,无需 dialect 检查。 + if (!isUnion(srcType)) { + const srcPtrLevel = getCPtrLevel(srcType); + if (srcPtrLevel >= 1) { + const destVariants = isUnion(destType) ? destType.priv.subtypes : [destType]; + const destPtrLevels = new Set(); + for (const variant of destVariants) { + const lvl = getCPtrLevel(variant); + if (lvl >= 1) { + destPtrLevels.add(lvl); + } + } + // dest 含指针但无与 src 同级者 → 跨级拒绝 + if (destPtrLevels.size > 0 && !destPtrLevels.has(srcPtrLevel)) { + diag?.addMessage( + `跨级指针隐式转换不允许:源为 ${srcPtrLevel} 级指针,` + + `目标指针级数为 [${Array.from(destPtrLevels).join(', ')}](Viper 要求同级)` + ); + return false; + } + } + } + + // Viper 扩展:整数 ↔ 指针隐式转换语义。 + // 在 C/Viper 语义中,整数(地址值)与指针类型可相互转换: + // - 整数地址 → CPtr/CVoid(如 framebuffer_addr: CUnsignedLong 赋值给 CVoid | CPtr) + // - 指针 → 整数(如 uintptr_t = (uintptr_t)ptr) + // 这模拟 C 语言的整数-指针转换规则。 + const destIsPtrLike = isCPtrType(destType) || isCVoidType(destType); + const srcIsPtrLike = isCPtrType(srcType) || isCVoidType(srcType); + if (destIsPtrLike && isViperIntegerType(srcType)) { + return true; + } + if (srcIsPtrLike && isViperIntegerType(destType)) { + return true; + } + + // Viper 扩展:字符串/字节字面量 → C 字符指针联合类型的转换。 + // 在 Viper 语义中,str/bytes 可表示 i8*(char*),因此可赋值给包含 + // CConst/CPtr/CVoid 的联合类型(如 CConst | CChar | CPtr 表示 const char*)。 + // 这允许 msg: t.CConst | t.CChar | t.CPtr = "Hello" 这样的赋值。 + // CConst/CPtr/CVoid 是 Viper 特有类型,目标包含它们即说明是 Viper 代码。 + const srcIsStrBytes = isClassInstance(srcType) && + (ClassType.isBuiltIn(srcType, 'str') || ClassType.isBuiltIn(srcType, 'bytes')); + if (srcIsStrBytes) { + const destVariants = isUnion(destType) ? destType.priv.subtypes : [destType]; + const hasCStringLike = destVariants.some((variant) => + isCConstType(variant) || isCPtrType(variant) || isCVoidType(variant) + ); + if (hasCStringLike) { + return true; + } + } + + // Viper 扩展:CPtr[CChar] → str/bytes 转换(char* → C 字符串)。 + // C 语义中 char* 可以赋值给字符串变量(如 c.Addr(arr[0]) → str 参数)。 + // 覆盖场景:c.Addr(win.title[0]) 返回 CPtr[CChar],赋值给 _draw_text 的 str 参数。 + // CPtr 是 Viper 特有类型,源包含它即为 Viper 代码,无需 dialect 检查。 + if ( + isClassInstance(srcType) && isCPtrType(srcType) && + srcType.priv.typeArgs && srcType.priv.typeArgs.length > 0 + ) { + const targetType = srcType.priv.typeArgs[0]; + if (isClassInstance(targetType)) { + const targetName = targetType.shared.name; + if ( + targetName === 'CChar' || targetName === 'CSignedChar' || + targetName === 'CUnsignedChar' || targetName === 'CChar8T' || + targetName === 'CChar16T' || targetName === 'CChar32T' || + targetName === 'CWCharT' + ) { + const destIsStrBytes = + (isClassInstance(destType) && ClassType.isBuiltIn(destType, 'str')) || + (isClassInstance(destType) && ClassType.isBuiltIn(destType, 'bytes')); + if (destIsStrBytes) { + return true; + } + } + } + } + // Viper 扩展:str ↔ bytes 隐式互转(C 字符串指针语义)。 // Viper 中 str/bytes/t.CChar|t.CPtr 都是 i8*(C 字符串指针), // 本质是同一类型的别名,应忽略区别。覆盖场景: @@ -26071,4434 +26122,4434 @@ export function createTypeEvaluator( return true; } - // Viper 扩展:TypeSet 类型组合赋值语义。 - // 在 Viper 中,`|` 表示类型组合(TypeSet),而非 Python 的联合类型。 - // TypeSet 中的每个子类型都是"同一值的不同视角",因此: - // - 如果 srcType 是 TypeSet(联合类型且包含 Viper 修饰类型), - // 且 destType 可以赋值给 TypeSet 中的某个子类型,则允许赋值。 - // 例如:get_block_size() -> CStatic | CUInt64T,可赋值给 CUInt64T 变量。 - if (isUnion(srcType)) { - const srcSubtypes = srcType.priv.subtypes; - const hasViperModifier = srcSubtypes.some( - (subtype) => - isClassInstance(subtype) && - (isCModifierType(subtype) || - isCPtrType(subtype) || - isCVoidType(subtype) || - isCArrayType(subtype)) - ); - if (hasViperModifier) { - // 检查 destType 是否可以赋值给 TypeSet 中的某个子类型 - for (const subtype of srcSubtypes) { - if (assignType(destType, subtype, undefined, constraints, flags, recursionCount + 1)) { - return true; - } - } - - // Viper 扩展:CPtr[T] 目标类型与包含 CPtr 的 TypeSet 的兼容。 - // 当 destType 是 CPtr[T] 且 srcType 是包含 CPtr 的 TypeSet 时, - // 允许赋值(如 volinfo | CPtr → CPtr[volinfo])。 - // 这是因为 Viper 中 T | CPtr 表示 T*(指向 T 的指针),等价于 CPtr[T]。 - if (isClassInstance(destType) && isCPtrType(destType)) { - return true; - } - } - } - - // Viper 扩展:Self 类型 → CPtr[T] 隐式取地址语义。 - // 在 Viper 中,结构体方法的 self 参数(Self@T)本身就是 CPtr[T](指针), - // 因此 Self@volinfo 可以赋值给 CPtr[volinfo]。 - // 仅对 Self 类型允许,不对普通结构体实例做隐式取地址。 - if ( - isClassInstance(destType) && isCPtrType(destType) && - destType.priv.typeArgs && destType.priv.typeArgs.length > 0 && - isTypeVar(srcType) && TypeVarType.isSelf(srcType) - ) { - return true; - } - - // Viper 扩展:类实例 → CPtr[T] 隐式取地址语义(堆对象即指针)。 - // Viper 中 `T | t.CPtr` 注解表示 T*(指向 T 的指针),构造调用返回的是 - // 堆上分配的类实例(如 `HashTable(mb)` 返回 HashTable 实例)。 - // 允许类实例赋值给 CPtr[同类](相当于 C 的 `HashTable* p = &obj`,Viper - // 中实例本身就是指针),覆盖场景: - // self._ht: hashtable.HashTable | t.CPtr = hashtable.HashTable(mb) - // 仅当 CPtr 的目标类型是源类实例的同类/基类时允许(保证指针类型安全)。 - if ( - isClassInstance(destType) && isCPtrType(destType) && - destType.priv.typeArgs && destType.priv.typeArgs.length > 0 && - isClassInstance(srcType) - ) { - const pointeeType = destType.priv.typeArgs[0]; - // 目标可以是实例或可实例化形式,统一转成可比较形式。 - let pointeeAsClass: ClassType | undefined; - if (isClassInstance(pointeeType)) { - pointeeAsClass = pointeeType; - } else if (isInstantiableClass(pointeeType)) { - const converted = convertToInstance(pointeeType); - pointeeAsClass = isClassInstance(converted) ? converted : undefined; - } - if (pointeeAsClass) { - const inheritanceChain: InheritanceChain = []; - const isDerivedFrom = ClassType.isDerivedFrom( - srcType, - pointeeAsClass, - inheritanceChain - ); - if (isDerivedFrom) { - return true; - } - } - } - - // Viper 扩展:字符串字面量 → int(CChar)转换语义。 - // C 语言中 char c = '_' 合法(字符字面量是整数)。Viper 中 CChar 是 int 别名, - // 但 '_' 是字符串字面量。允许字符串字面量赋值给 int,模拟 C 的字符赋值。 - // Python 中 str 永远不能赋值给 int,所以此扩展只在 Viper 场景生效。 - if ( - isClassInstance(destType) && ClassType.isBuiltIn(destType, 'int') && - isClassInstance(srcType) && ClassType.isBuiltIn(srcType, 'str') && - srcType.priv.literalValue !== undefined && - typeof srcType.priv.literalValue === 'string' - ) { - return true; - } - - // Viper 扩展:CEnum 子类 → int 隐式转换语义。 - // C 语言中 enum 值可以隐式转换为 int(如 int x = FR_OK)。 - // Viper 的 CEnum 子类(如 FRESULT)的成员(如 FRESULT.FR_OK) - // 可以赋值给 int 类型的变量/字段。 - if ( - isClassInstance(destType) && ClassType.isBuiltIn(destType, 'int') && - isClassInstance(srcType) && isCEnumSubclassType(srcType) - ) { - return true; - } - - // Viper 扩展:Python 内置类型 → CType 子类隐式转换。 - // Viper 中所有 C 类型(CInt, CFloat, CChar 等)都是 CType 的子类, - // 在底层等价于 Python 内置类型(int, float, str 等)。 - // 允许 `k: t.CInt = 1`(int → CInt)、`k: t.CFloat = 1.0`(float → CFloat)等赋值。 - if ( - isClassInstance(destType) && isCTypeSubclassType(destType) && - isClassInstance(srcType) && ClassType.isBuiltIn(srcType, ['int', 'float', 'str', 'bool', 'bytes']) - ) { - return true; - } - - // Viper 扩展:CType 子类 -> Python 内置类型隐式转换(反向)。 - // CInt/CFloat/CChar 等 CType 子类在底层等价于 Python 内置类型, - // 可隐式转换回 int/float/str/bool/bytes。 - // 覆盖场景:CInt 值传给 int 形参(如 def __init__(self, w: int) 接收 CInt 实参)。 - if ( - isClassInstance(destType) && ClassType.isBuiltIn(destType, ['int', 'float', 'str', 'bool', 'bytes']) && - isClassInstance(srcType) && isCTypeSubclassType(srcType) - ) { - return true; - } - - // Viper 扩展:CType 子类 -> CType 子类隐式转换。 - // 按 C 语言隐式转换规则判断(canImplicitlyConvertCType): - // - 整数/浮点:允许所有方向(高向低可安全降级,低向高无损) - // - 整数 -> 浮点:浮点位宽 >= 整数位宽时允许(无损) - // - 浮点 -> 整数:不允许(有损) - // - 同级指针(都是 CPtr 子类):允许不同类型转换 - // - 不同级(一个是指针,另一个不是):不允许 - // 覆盖场景:CUInt64T 传给 CInt 形参(高向低降级,允许)。 - if ( - isClassInstance(srcType) && isCTypeSubclassType(srcType) && - isClassInstance(destType) && isCTypeSubclassType(destType) - ) { - return canImplicitlyConvertCType(srcType, destType); - } - - // Viper 扩展:函数类型 → Callable 类实例赋值。 - // 当类型别名通过 t.CTypedef(=TypeAlias)声明时,如 - // irq_handler_t: t.CTypedef = t.Callable[[], t.CInt] - // 在某些场景下 irq_handler_t 可能被解析为 Callable 类实例而非 - // FunctionType。此时允许将函数类型赋值给 Callable 类实例。 - if ( - isClassInstance(destType) && ClassType.isBuiltIn(destType, 'Callable') && - isFunction(srcType) - ) { - return true; - } - - // Handle the case where the dest and src types are the same object. - // We can normally shortcut this and say that they are compatible, - // but if the type includes TypeVars, we need to go through - // the rest of the logic. - if (destType === srcType && !requiresSpecialization(destType)) { - return true; - } - - // If the source type is a special form, use the literal special form - // class rather than the symbolic form. - const specialForm = srcType.props?.specialForm; - if (specialForm) { - let isSpecialFormExempt = false; - - // A few special forms that are normally not compatible with type[T] - // are compatible specifically in the context of isinstance and issubclass. - if ((flags & AssignTypeFlags.AllowIsinstanceSpecialForms) !== 0) { - if (ClassType.isBuiltIn(specialForm, ['Callable', 'UnionType', 'Generic'])) { - isSpecialFormExempt = true; - } - } - - if (!isSpecialFormExempt) { - if (srcType.props?.typeForm && !specialForm.props?.typeForm) { - srcType = TypeBase.cloneWithTypeForm(specialForm, srcType.props.typeForm); - } else { - srcType = specialForm; - } - } - } - - // If the source is a class-like type created by a call to NewType, treat it - // as a FunctionClass instance rather than an instantiable class for - // purposes of assignability. This reflects its actual runtime type. - if (isInstantiableClass(srcType) && ClassType.isNewTypeClass(srcType) && !srcType.priv.includeSubclasses) { - if (prefetched?.functionClass && isInstantiableClass(prefetched?.functionClass)) { - srcType = ClassType.cloneAsInstance(prefetched.functionClass); - } - } - - if (recursionCount > maxTypeRecursionCount) { - return true; - } - recursionCount++; - - // If the source and dest refer to the recursive type aliases, handle - // the case specially to avoid recursing down both type aliases. - if ( - isTypeVar(destType) && - destType.shared.recursiveAlias && - isTypeVar(srcType) && - srcType.shared.recursiveAlias - ) { - const destAliasInfo = destType.props?.typeAliasInfo; - const srcAliasInfo = srcType.props?.typeAliasInfo; - - // Do the source and dest refer to the same recursive type alias? - if ( - destAliasInfo?.typeArgs && - srcAliasInfo?.typeArgs && - destType.shared.recursiveAlias.typeVarScopeId === srcType.shared.recursiveAlias.typeVarScopeId - ) { - return assignRecursiveTypeAliasToSelf( - destAliasInfo, - srcAliasInfo, - diag, - constraints, - flags, - recursionCount - ); - } else { - // Have we already recursed once? - if ((flags & AssignTypeFlags.SkipRecursiveTypeCheck) !== 0) { - return true; - } - - // Note that we are comparing two recursive types and do - // not recursive more than once. - flags |= AssignTypeFlags.SkipRecursiveTypeCheck; - } - } - - // If one or both of the types has an instantiable depth greater than - // zero, convert both to instances first. - if (TypeBase.isInstantiable(destType) && TypeBase.isInstantiable(srcType)) { - if (TypeBase.getInstantiableDepth(destType) > 0 || TypeBase.getInstantiableDepth(srcType) > 0) { - return assignType( - convertToInstance(destType), - convertToInstance(srcType), - diag, - constraints, - flags, - recursionCount - ); - } - } - - // Transform recursive type aliases if necessary. - const transformedDestType = transformPossibleRecursiveTypeAlias(destType); - const transformedSrcType = transformPossibleRecursiveTypeAlias(srcType); - - // Did either the source or dest include recursive type aliases? - // If so, we could be dealing with different recursive type aliases - // or a recursive type alias and a recursive protocol definition. - if ( - (transformedDestType !== destType && isUnion(transformedDestType)) || - (transformedSrcType !== srcType && isUnion(transformedSrcType)) - ) { - // Use a smaller recursive limit in this case to prevent runaway recursion. - if (recursionCount > maxRecursiveTypeAliasRecursionCount) { - // Add a special case for when the source is a str, which is itself - // a recursive type (since it derives from Sequence[str]). - if (isClassInstance(srcType) && ClassType.isBuiltIn(srcType, 'str') && isUnion(transformedDestType)) { - return transformedDestType.priv.subtypes.some( - (subtype) => isClassInstance(subtype) && ClassType.isBuiltIn(subtype, ['object', 'str']) - ); - } - return true; - } - } - - destType = transformedDestType; - srcType = transformedSrcType; - - // If the source or dest is unbound, allow the assignment. The - // error will be reported elsewhere. - if (isUnbound(destType) || isUnbound(srcType)) { - return true; - } - - if (isTypeVar(destType)) { - if (isTypeVarSame(destType, srcType)) { - return true; - } - - // If the dest is a constrained or bound type variable and all of the - // types in the source are conditioned on that same type variable - // and have compatible types, we'll consider it assignable. - if (assignConditionalTypeToTypeVar(destType, srcType, recursionCount)) { - return true; - } - - // If the source is a conditional type associated with a bound TypeVar - // and the bound TypeVar matches the condition, the types are compatible. - const destTypeVar = destType; - if ( - TypeBase.isInstantiable(destType) === TypeBase.isInstantiable(srcType) && - srcType.props?.condition && - srcType.props.condition.some((cond) => { - return ( - !TypeVarType.hasConstraints(cond.typeVar) && - cond.typeVar.priv.nameWithScope === destTypeVar.priv.nameWithScope - ); - }) - ) { - return true; - } - - if (isUnion(srcType)) { - const srcWithoutAny = removeFromUnion(srcType, (type) => isAnyOrUnknown(type)); - if (isTypeSame(destType, srcWithoutAny)) { - return true; - } - } - - // Handle the special case where both types are Self types. We'll allow - // them to be treated as equivalent to handle certain common idioms. - if ( - isTypeVar(srcType) && - TypeVarType.isSelf(srcType) && - TypeVarType.hasBound(srcType) && - TypeVarType.isSelf(destType) && - TypeVarType.hasBound(destType) && - TypeVarType.isBound(destType) === TypeVarType.isBound(srcType) && - TypeBase.isInstance(srcType) === TypeBase.isInstance(destType) - ) { - if ((flags & AssignTypeFlags.Contravariant) === 0 && constraints) { - assignTypeVar(evaluatorInterface, destType, srcType, diag, constraints, flags, recursionCount); - } - return true; - } - - // If the dest is a TypeVarTuple, and the source is a tuple - // with a single entry that is the same TypeVarTuple, it's a match. - if ( - isTypeVarTuple(destType) && - isClassInstance(srcType) && - isTupleClass(srcType) && - srcType.priv.tupleTypeArgs && - srcType.priv.tupleTypeArgs.length === 1 - ) { - if (isTypeSame(destType, srcType.priv.tupleTypeArgs[0].type, {}, recursionCount)) { - return true; - } - } - - if ((flags & AssignTypeFlags.Contravariant) === 0 || !isTypeVar(srcType)) { - if (!assignTypeVar(evaluatorInterface, destType, srcType, diag, constraints, flags, recursionCount)) { - return false; - } - - if (isAnyOrUnknown(srcType) && (flags & AssignTypeFlags.OverloadOverlap) !== 0) { - return false; - } - - return true; - } - } - - if (isTypeVar(srcType)) { - if ((flags & AssignTypeFlags.Contravariant) !== 0) { - if (TypeVarType.isBound(srcType)) { - return assignType( - makeTopLevelTypeVarsConcrete(destType), - makeTopLevelTypeVarsConcrete(srcType), - diag, - /* constraints */ undefined, - flags, - recursionCount - ); - } - - if (assignTypeVar(evaluatorInterface, srcType, destType, diag, constraints, flags, recursionCount)) { - return true; - } - - // If the dest type is a union, only one of the subtypes needs to match. - let isAssignable = false; - if (isUnion(destType)) { - doForEachSubtype(destType, (destSubtype) => { - if ( - assignTypeVar( - evaluatorInterface, - srcType as TypeVarType, - destSubtype, - diag, - constraints, - flags, - recursionCount - ) - ) { - isAssignable = true; - } - }); - } - return isAssignable; - } - - if ((flags & AssignTypeFlags.Invariant) !== 0) { - if (isAnyOrUnknown(destType)) { - return true; - } - - // If the source is a ParamSpec and the dest is a "...", this is - // effectively like an "Any" signature, so we'll treat it as though - // it's Any. - if ( - isParamSpec(srcType) && - isFunction(destType) && - FunctionType.isGradualCallableForm(destType) && - destType.shared.parameters.length <= 2 - ) { - return true; - } - - // If the source is an unpacked TypeVarTuple and the dest is a - // *tuple[Any, ...], we'll treat it as compatible. - if ( - isUnpackedTypeVarTuple(srcType) && - isClassInstance(destType) && - isUnpackedClass(destType) && - destType.priv.tupleTypeArgs && - destType.priv.tupleTypeArgs.length === 1 && - destType.priv.tupleTypeArgs[0].isUnbounded && - isAnyOrUnknown(destType.priv.tupleTypeArgs[0].type) - ) { - return true; - } - - if (!isUnion(destType)) { - diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType))); - return false; - } - } - } - - if (isAnyOrUnknown(destType)) { - return true; - } - - if (isAnyOrUnknown(srcType) && !srcType.props?.specialForm) { - if (constraints) { - // If it's an ellipsis type, convert it to a regular "Any" - // type. These are functionally equivalent, but "Any" looks - // better in the text representation. - const typeVarSubstitution = isEllipsisType(srcType) ? AnyType.create() : srcType; - setConstraintsForFreeTypeVars(destType, typeVarSubstitution, constraints); - } - if ((flags & AssignTypeFlags.OverloadOverlap) === 0) { - return true; - } - } - - if (isNever(srcType)) { - if ((flags & AssignTypeFlags.Invariant) !== 0) { - if (isNever(destType)) { - return true; - } - - diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType))); - return false; - } - - if (constraints) { - setConstraintsForFreeTypeVars(destType, UnknownType.create(), constraints); - } - return true; - } - - if (isUnion(destType)) { - // If both the source and dest are unions, use assignFromUnionType which has - // special-case logic to handle this case. - if (isUnion(srcType)) { - return assignFromUnionType(destType, srcType, diag, constraints, flags, recursionCount); - } - - const clonedConstraints = constraints?.clone(); - if (assignToUnionType(destType, srcType, /* diag */ undefined, clonedConstraints, flags, recursionCount)) { - if (constraints && clonedConstraints) { - constraints.copyFromClone(clonedConstraints); - } - return true; - } - } - - const expandedSrcType = makeTopLevelTypeVarsConcrete(srcType); - if (isUnion(expandedSrcType)) { - return assignFromUnionType(destType, expandedSrcType, diag, constraints, flags, recursionCount); - } - - if (isUnion(destType)) { - return assignToUnionType(destType, srcType, diag, constraints, flags, recursionCount); - } - - // Is the src a specialized "type" object? - if (isClassInstance(expandedSrcType) && ClassType.isBuiltIn(expandedSrcType, 'type')) { - const srcTypeArgs = expandedSrcType.priv.typeArgs; - let typeTypeArg: Type; - - if (srcTypeArgs && srcTypeArgs.length >= 1) { - typeTypeArg = srcTypeArgs[0]; - } else { - typeTypeArg = UnknownType.create(); - } - - if (isAnyOrUnknown(typeTypeArg)) { - if (isEffectivelyInstantiable(destType)) { - return true; - } - } else if (isClassInstance(typeTypeArg) || isTypeVar(typeTypeArg)) { - if ( - assignType( - destType, - convertToInstantiable(typeTypeArg), - diag?.createAddendum(), - constraints, - flags, - recursionCount - ) - ) { - return true; - } - - diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType))); - return false; - } - } - - if (isInstantiableClass(destType)) { - if (isInstantiableClass(expandedSrcType)) { - // PEP 544 says that if the dest type is a type[Proto] class, - // the source must be a "concrete" (non-protocol) class. - if (ClassType.isProtocolClass(destType)) { - if ( - (flags & AssignTypeFlags.AllowProtocolClassSource) === 0 && - ClassType.isProtocolClass(expandedSrcType) && - isInstantiableClass(srcType) && - !srcType.priv.includeSubclasses - ) { - diag?.addMessage( - LocAddendum.protocolSourceIsNotConcrete().format({ - sourceType: printType(convertToInstance(srcType)), - destType: printType(destType), - }) - ); - return false; - } - } - - if (ClassType.isBuiltIn(destType, 'type') && (srcType.props?.instantiableDepth ?? 0) > 0) { - return true; - } - - if (isSpecialFormClass(expandedSrcType, flags)) { - // Special form classes are compatible only with other special form - // classes, not with 'object' or 'type'. - const destSpecialForm = destType.props?.specialForm ?? destType; - if (isSpecialFormClass(destSpecialForm, flags)) { - return assignType(destSpecialForm, expandedSrcType, diag, constraints, flags, recursionCount); - } - } else if ( - assignClass( - destType, - expandedSrcType, - diag, - constraints, - flags, - recursionCount, - /* reportErrorsUsingObjType */ false - ) - ) { - return true; - } - - diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType))); - return false; - } - } - - if (isClassInstance(destType)) { - if (ClassType.isBuiltIn(destType, 'type')) { - if ( - isInstantiableClass(srcType) && - isSpecialFormClass(srcType, flags) && - TypeBase.getInstantiableDepth(srcType) === 0 - ) { - return false; - } - - if (isAnyOrUnknown(srcType) && (flags & AssignTypeFlags.OverloadOverlap) !== 0) { - return false; - } - - const destTypeArgs = destType.priv.typeArgs; - if (destTypeArgs && destTypeArgs.length >= 1) { - if (TypeBase.isInstance(destTypeArgs[0]) && TypeBase.isInstantiable(srcType)) { - return assignType( - destTypeArgs[0], - convertToInstance(srcType), - diag, - constraints, - flags, - recursionCount - ); - } - } - - // Is the dest a "type" object? Assume that all instantiable - // types are assignable to "type". - if (TypeBase.isInstantiable(srcType)) { - const isLiteral = isClass(srcType) && srcType.priv.literalValue !== undefined; - return !isLiteral; - } - } - - let concreteSrcType = makeTopLevelTypeVarsConcrete(srcType); - - // Handle the TypeForm special form. Add a special case for - // type[T] to be assignable to TypeForm[T]. - if (ClassType.isBuiltIn(destType, 'TypeForm')) { - const destTypeArg = - destType.priv.typeArgs && destType.priv.typeArgs.length > 0 - ? destType.priv.typeArgs[0] - : UnknownType.create(); - - let srcTypeArg: Type | undefined; - if (isClassInstance(concreteSrcType) && ClassType.isBuiltIn(concreteSrcType, 'type')) { - srcTypeArg = concreteSrcType; - } else if (isInstantiableClass(concreteSrcType)) { - srcTypeArg = convertToInstance(concreteSrcType); - } - - if (srcTypeArg) { - return assignType(destTypeArg, srcTypeArg, diag, constraints, flags, recursionCount); - } - } - - if (isClass(concreteSrcType) && TypeBase.isInstance(concreteSrcType)) { - // Handle the case where the source is an unpacked tuple. - if ( - !destType.priv.isUnpacked && - concreteSrcType.priv.isUnpacked && - concreteSrcType.priv.tupleTypeArgs - ) { - return assignType( - destType, - combineTupleTypeArgs(concreteSrcType.priv.tupleTypeArgs), - diag, - constraints, - flags, - recursionCount - ); - } - - if ( - destType.priv.literalValue !== undefined && - ClassType.isSameGenericClass(destType, concreteSrcType) - ) { - const srcLiteral = concreteSrcType.priv.literalValue; - if (srcLiteral === undefined || !ClassType.isLiteralValueSame(concreteSrcType, destType)) { - diag?.addMessage( - LocAddendum.literalAssignmentMismatch().format({ - sourceType: printType(srcType), - destType: printType(destType), - }) - ); - - return false; - } - } - - // Handle LiteralString special form. - if (ClassType.isBuiltIn(destType, 'LiteralString')) { - if ( - ClassType.isBuiltIn(concreteSrcType, 'str') && - concreteSrcType.priv.literalValue !== undefined - ) { - return (flags & AssignTypeFlags.Invariant) === 0; - } else if (ClassType.isBuiltIn(concreteSrcType, 'LiteralString')) { - return true; - } - } else if ( - ClassType.isBuiltIn(concreteSrcType, 'LiteralString') && - prefetched?.strClass && - isInstantiableClass(prefetched.strClass) && - (flags & AssignTypeFlags.Invariant) === 0 - ) { - concreteSrcType = ClassType.cloneAsInstance(prefetched.strClass); - } - - if ( - !assignClass( - ClassType.cloneAsInstantiable(destType), - ClassType.cloneAsInstantiable(concreteSrcType), - diag, - constraints, - flags, - recursionCount, - /* reportErrorsUsingObjType */ true - ) - ) { - return false; - } - - return true; - } else if (isFunctionOrOverloaded(concreteSrcType)) { - // Is the destination a callback protocol (defined in PEP 544)? - const destCallbackType = getCallbackProtocolType(destType, recursionCount); - if (destCallbackType) { - return assignType(destCallbackType, concreteSrcType, diag, constraints, flags, recursionCount); - } - - // All functions are considered instances of "types.FunctionType" or "types.MethodType". - const altClass = isMethodType(concreteSrcType) ? prefetched?.methodClass : prefetched?.functionClass; - if (altClass) { - return assignType(destType, convertToInstance(altClass), diag, constraints, flags, recursionCount); - } - } else if (isModule(concreteSrcType)) { - // Is the destination the built-in "ModuleType"? - if (ClassType.isBuiltIn(destType, 'ModuleType')) { - return true; - } - - if (ClassType.isProtocolClass(destType)) { - return assignModuleToProtocol( - evaluatorInterface, - ClassType.cloneAsInstantiable(destType), - concreteSrcType, - diag, - constraints, - flags, - recursionCount - ); - } - } else if (isInstantiableClass(concreteSrcType)) { - // See if the destType is an instantiation of a Protocol - // class that is effectively a function. - const callbackType = getCallbackProtocolType(destType, recursionCount); - if (callbackType) { - return assignType(callbackType, concreteSrcType, diag, constraints, flags, recursionCount); - } - - // If the destType is an instantiation of a Protocol, - // see if the class type itself satisfies the protocol. - if (ClassType.isProtocolClass(destType)) { - return assignClassToProtocol( - evaluatorInterface, - ClassType.cloneAsInstantiable(destType), - concreteSrcType, - diag, - constraints, - flags, - recursionCount - ); - } - - // Determine if the metaclass can be assigned to the object. - const metaclass = concreteSrcType.shared.effectiveMetaclass; - if (metaclass) { - if (!isAnyOrUnknown(metaclass)) { - if ( - assignClass( - ClassType.cloneAsInstantiable(destType), - metaclass, - /* diag */ undefined, - constraints, - flags, - recursionCount, - /* reportErrorsUsingObjType */ true - ) - ) { - return true; - } - } - } - } else if (isAnyOrUnknown(concreteSrcType) && !concreteSrcType.props?.specialForm) { - return (flags & AssignTypeFlags.OverloadOverlap) === 0; - } else if (isUnion(concreteSrcType)) { - return assignType(destType, concreteSrcType, diag, constraints, flags, recursionCount); - } - } - - if (isFunction(destType)) { - let concreteSrcType = makeTopLevelTypeVarsConcrete(srcType); - - if (isClassInstance(concreteSrcType)) { - const boundMethod = getBoundMagicMethod( - concreteSrcType, - '__call__', - /* selfType */ undefined, - /* errorNode */ undefined, - /* diag */ undefined, - recursionCount - ); - if (boundMethod) { - concreteSrcType = boundMethod; - } - } - - // If it's a class, use the constructor for type compatibility checking. - if (isInstantiableClass(concreteSrcType) && concreteSrcType.priv.literalValue === undefined) { - const constructor = createFunctionFromConstructor( - evaluatorInterface, - concreteSrcType, - isTypeVar(srcType) ? convertToInstance(srcType) : undefined, - recursionCount - ); - if (constructor) { - concreteSrcType = constructor; - - // The constructor conversion may result in a union of the - // __init__ and __new__ callables. - if (isUnion(concreteSrcType)) { - return assignType(destType, concreteSrcType, diag, constraints, flags, recursionCount); - } - } - } - - if (isAnyOrUnknown(concreteSrcType)) { - return (flags & AssignTypeFlags.OverloadOverlap) === 0; - } - - if (isOverloaded(concreteSrcType)) { - // If this is the first pass of an argument assignment, skip - // all attempts to assign an overloaded function to a function - // because we probably don't have enough information to properly - // filter the overloads at this time. We will do this work on - // subsequent passes. - if ((flags & AssignTypeFlags.ArgAssignmentFirstPass) !== 0) { - return true; - } - - // Find all of the overloaded functions that match the parameters. - const overloads = OverloadedType.getOverloads(concreteSrcType); - const filteredOverloads: FunctionType[] = []; - const typeVarSignatures: ConstraintSet[] = []; - - overloads.forEach((overload) => { - const overloadScopeId = getTypeVarScopeId(overload) ?? ''; - const constraintsClone = constraints?.cloneWithSignature(overloadScopeId); - - if (assignType(destType, overload, /* diag */ undefined, constraintsClone, flags, recursionCount)) { - filteredOverloads.push(overload); - - if (constraintsClone) { - appendArray(typeVarSignatures, constraintsClone.getConstraintSets()); - } - } - }); - - if (filteredOverloads.length === 0) { - diag?.addMessage(LocAddendum.noOverloadAssignable().format({ type: printType(destType) })); - return false; - } - - if (filteredOverloads.length === 1 || (flags & AssignTypeFlags.ArgAssignmentFirstPass) === 0) { - if (constraints) { - constraints.addConstraintSets(typeVarSignatures); - } - } - - return true; - } - - if (isFunction(concreteSrcType)) { - if ( - assignFunction( - destType, - concreteSrcType, - diag?.createAddendum(), - constraints ?? new ConstraintTracker(), - flags, - recursionCount - ) - ) { - return true; - } - } - } - - if (isOverloaded(destType)) { - const overloadDiag = diag?.createAddendum(); - - // All overloads in the dest must be assignable. - const destOverloads = OverloadedType.getOverloads(destType); - - // If the source is also an overload with the same number of overloads, - // there's a good chance that there's a one-to-one mapping. Try this - // first before using an n^2 algorithm. - if (isOverloaded(srcType)) { - const srcOverloads = OverloadedType.getOverloads(srcType); - if (destOverloads.length === srcOverloads.length) { - if ( - destOverloads.every((destOverload, index) => { - const srcOverload = srcOverloads[index]; - return assignType( - destOverload, - srcOverload, - /* diag */ undefined, - constraints, - flags, - recursionCount - ); - }) - ) { - return true; - } - } - } - - const isAssignable = destOverloads.every((destOverload) => { - const result = assignType( - destOverload, - srcType, - overloadDiag?.createAddendum(), - constraints, - flags, - recursionCount - ); - return result; - }); - - if (!isAssignable) { - const overloads = OverloadedType.getOverloads(destType); - - if (overloadDiag && overloads.length > 0) { - overloadDiag.addMessage( - LocAddendum.overloadNotAssignable().format({ - name: overloads[0].shared.name, - }) - ); - } - return false; - } - - return true; - } - - if (isClass(destType) && ClassType.isBuiltIn(destType, 'object')) { - if ((isInstantiableClass(destType) && TypeBase.isInstantiable(srcType)) || isClassInstance(destType)) { - if ((flags & AssignTypeFlags.Invariant) === 0) { - // All types (including None, Module, Overloaded) derive from object. - return true; - } - } - } - - // Are we trying to assign None to a protocol? - if (isNoneInstance(srcType) && isClassInstance(destType) && ClassType.isProtocolClass(destType)) { - if (prefetched?.noneTypeClass && isInstantiableClass(prefetched.noneTypeClass)) { - return assignClassToProtocol( - evaluatorInterface, - ClassType.cloneAsInstantiable(destType), - ClassType.cloneAsInstance(prefetched.noneTypeClass), - diag, - constraints, - flags, - recursionCount - ); - } - } - - if (isNoneInstance(destType)) { - diag?.addMessage(LocAddendum.assignToNone()); - return false; - } - - diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType))); - - return false; - } - - // Determines whether a recursive type alias can be assigned to itself - // given the source and dest type args and computed variance for its - // type params. - function assignRecursiveTypeAliasToSelf( - destAliasInfo: TypeAliasInfo, - srcAliasInfo: TypeAliasInfo, - diag?: DiagnosticAddendum, - constraints?: ConstraintTracker, - flags = AssignTypeFlags.Default, - recursionCount = 0 - ) { - assert(destAliasInfo.typeArgs !== undefined); - assert(srcAliasInfo.typeArgs !== undefined); - - let isAssignable = true; - const srcTypeArgs = srcAliasInfo.typeArgs; - const variances = destAliasInfo.shared.computedVariance; - - destAliasInfo.typeArgs.forEach((destTypeArg, index) => { - const srcTypeArg = index < srcTypeArgs.length ? srcTypeArgs[index] : UnknownType.create(); - - let adjFlags = flags; - const variance = variances && index < variances.length ? variances[index] : Variance.Covariant; - - if (variance === Variance.Invariant) { - adjFlags |= AssignTypeFlags.Invariant; - } else if (variance === Variance.Contravariant) { - adjFlags ^= AssignTypeFlags.Contravariant; - } - - if (!assignType(destTypeArg, srcTypeArg, diag, constraints, adjFlags, recursionCount)) { - isAssignable = false; - } - }); - - return isAssignable; - } - - function expectedTypeWantsTypeForm(expectedType: Type): boolean { - return someSubtypes( - expectedType, - (subtype) => isClassInstance(subtype) && ClassType.isBuiltIn(subtype, 'TypeForm') - ); - } - - // If the expected type is an explicit TypeForm type, see if the source - // type has an implicit TypeForm type that can be assigned to it. If so, - // convert to an explicit TypeForm type. - function convertToTypeFormType(expectedType: Type, srcType: Type): Type { - // Is the source is a TypeForm type? - if (!srcType.props?.typeForm) { - return srcType; - } - - let srcTypeFormType: Type | undefined; - - // Is the source is a TypeForm type? - if (srcType.props?.typeForm) { - srcTypeFormType = srcType.props.typeForm; - } else if (isClass(srcType)) { - if (TypeBase.isInstantiable(srcType)) { - if (!ClassType.isSpecialBuiltIn(srcType)) { - srcTypeFormType = ClassType.cloneAsInstance(srcType); - } - } else if (ClassType.isBuiltIn(srcType, 'type')) { - srcTypeFormType = - srcType.priv.typeArgs?.length && srcType.priv.typeArgs.length > 0 - ? srcType.priv.typeArgs[0] - : UnknownType.create(); - } - } else if (isTypeVar(srcType) && TypeBase.isInstantiable(srcType)) { - if (!isTypeVarTuple(srcType) || !srcType.priv.isInUnion) { - srcTypeFormType = convertToInstance(srcType); - } - } - - if (!srcTypeFormType) { - return srcType; - } - - let resultType: Type | undefined; - - doForEachSubtype(expectedType, (subtype) => { - if (resultType || !isClassInstance(subtype) || !ClassType.isBuiltIn(subtype, 'TypeForm')) { - return; - } - - const destTypeFormType = - subtype.priv.typeArgs && subtype.priv.typeArgs.length > 0 - ? subtype.priv.typeArgs[0] - : UnknownType.create(); - - if (assignType(destTypeFormType, srcTypeFormType)) { - resultType = ClassType.specialize(subtype, [srcTypeFormType]); - } - }); - - return resultType ?? srcType; - } - - function assignFromUnionType( - destType: Type, - srcType: UnionType, - diag: DiagnosticAddendum | undefined, - constraints: ConstraintTracker | undefined, - flags: AssignTypeFlags, - recursionCount: number - ): boolean { - // Start by checking for an exact match. This is needed to handle unions - // that contain recursive type aliases. - if (isTypeSame(srcType, destType, {}, recursionCount)) { - return true; - } - - if ( - (flags & AssignTypeFlags.OverloadOverlap) !== 0 && - srcType.priv.subtypes.some((subtype) => isAnyOrUnknown(subtype)) - ) { - return false; - } - - // Sort the subtypes so we have a deterministic order for unions. - let sortedSrcTypes: Type[] = sortTypes(srcType.priv.subtypes); - let matchedSomeSubtypes = false; - - // Handle the case where the source and dest are both unions. Try - // to eliminate as many exact type matches between the src and dest. - if (isUnion(destType)) { - // Handle the special case where the dest is a union of Any and - // a type variable. This occurs, for example, with the return type of - // the getattr function. - const nonAnySubtypes = destType.priv.subtypes.filter((t) => !isAnyOrUnknown(t)); - if (nonAnySubtypes.length === 1 && isTypeVar(nonAnySubtypes[0])) { - assignType(nonAnySubtypes[0], srcType, /* diag */ undefined, constraints, flags, recursionCount); - - // This always succeeds because the destination contains Any. - return true; - } - - const remainingDestSubtypes: Type[] = []; - let remainingSrcSubtypes: Type[] = sortedSrcTypes; - let canUseFastPath = true; - - // First attempt to match all of the non-generic types in the dest - // to non-generic types in the source. - sortTypes(destType.priv.subtypes).forEach((destSubtype) => { - if (requiresSpecialization(destSubtype)) { - remainingDestSubtypes.push(destSubtype); - } else { - const srcTypeIndex = remainingSrcSubtypes.findIndex((srcSubtype) => - isTypeSame(srcSubtype, destSubtype, {}, recursionCount) - ); - - if (srcTypeIndex >= 0) { - remainingSrcSubtypes.splice(srcTypeIndex, 1); - matchedSomeSubtypes = true; - } else { - remainingDestSubtypes.push(destSubtype); - } - } - }); - - // For all remaining source subtypes, attempt to find a dest subtype - // whose primary type matches. - remainingSrcSubtypes.forEach((srcSubtype) => { - const destTypeIndex = remainingDestSubtypes.findIndex((destSubtype) => { - if (isTypeSame(destSubtype, srcSubtype)) { - return true; - } - - if ( - isClass(srcSubtype) && - isClass(destSubtype) && - TypeBase.isInstance(srcSubtype) === TypeBase.isInstance(destSubtype) - ) { - if (ClassType.isSameGenericClass(srcSubtype, destSubtype)) { - return true; - } - - // Are they equivalent TypedDicts? - if (ClassType.isTypedDictClass(srcSubtype) && ClassType.isTypedDictClass(destSubtype)) { - if ( - assignType( - srcSubtype, - destSubtype, - /* diag */ undefined, - /* constraints */ undefined, - flags, - recursionCount - ) - ) { - return true; - } - } - } - - if (isFunctionOrOverloaded(srcSubtype) && isFunctionOrOverloaded(destSubtype)) { - return true; - } - - return false; - }); - - if (destTypeIndex >= 0) { - if ( - assignType( - remainingDestSubtypes[destTypeIndex], - srcSubtype, - /* diag */ undefined, - constraints, - flags, - recursionCount - ) - ) { - // Note that we have matched at least one subtype indicating - // there is at least some overlap. - matchedSomeSubtypes = true; - } else { - canUseFastPath = false; - } - - remainingDestSubtypes.splice(destTypeIndex, 1); - remainingSrcSubtypes = remainingSrcSubtypes.filter((t) => t !== srcSubtype); - } - }); - - // If there is are remaining dest subtypes and they're all type variables, - // attempt to assign the remaining source subtypes to them. - if (canUseFastPath && (remainingDestSubtypes.length !== 0 || remainingSrcSubtypes.length !== 0)) { - if ((flags & AssignTypeFlags.Invariant) !== 0) { - // If we have no src subtypes remaining but not all dest types have been subsumed - // by other dest types, then the types are not compatible if we're enforcing invariance. - if (remainingSrcSubtypes.length === 0) { - return remainingDestSubtypes.every((destSubtype) => - isTypeSubsumedByOtherType( - destSubtype, - destType, - /* allowAnyToSubsume */ true, - recursionCount - ) - ); - } - } - - const isContra = (flags & AssignTypeFlags.Contravariant) !== 0; - const effectiveDestSubtypes = isContra ? remainingSrcSubtypes : remainingDestSubtypes; - - if (effectiveDestSubtypes.length === 0 || effectiveDestSubtypes.some((t) => !isTypeVar(t))) { - canUseFastPath = false; - - // We can avoid checking the source subtypes that have already been checked. - sortedSrcTypes = remainingSrcSubtypes; - } else if (remainingDestSubtypes.length === remainingSrcSubtypes.length) { - // If the number of remaining source subtypes is the same as the number - // of dest TypeVars, try to assign each source subtype to its own dest TypeVar. - const reorderedDestSubtypes = [...remainingDestSubtypes]; - - for (let srcIndex = 0; srcIndex < remainingSrcSubtypes.length; srcIndex++) { - let foundMatchForSrc = false; - - for (let destIndex = 0; destIndex < reorderedDestSubtypes.length; destIndex++) { - if ( - assignType( - reorderedDestSubtypes[destIndex], - remainingSrcSubtypes[srcIndex], - diag?.createAddendum(), - constraints, - flags, - recursionCount - ) - ) { - foundMatchForSrc = true; - // Move the matched dest TypeVar to the end of the list so the other - // dest TypeVars have a better chance of being assigned to. - reorderedDestSubtypes.push(...reorderedDestSubtypes.splice(destIndex, 1)); - break; - } - } - - if (!foundMatchForSrc) { - canUseFastPath = false; - break; - } - } - - // We can avoid checking the source subtypes that have already been checked. - sortedSrcTypes = remainingSrcSubtypes; - } else if (remainingSrcSubtypes.length === 0) { - if ((flags & AssignTypeFlags.PopulateExpectedType) !== 0) { - // If we're populating an expected type, try not to leave - // any TypeVars unsolved. Assign the full type to the remaining - // dest TypeVars. - remainingDestSubtypes.forEach((destSubtype) => { - assignType(destSubtype, srcType, /* diag */ undefined, constraints, flags, recursionCount); - }); - } - - // If we've assigned all of the source subtypes but one or more dest - // TypeVars have gone unmatched, treat this as success. - } else { - // Try to assign a union of the remaining source types to - // the first destination TypeVar. If this is a contravariant - // context, use the full dest type rather than the remaining - // dest subtypes to keep the lower bound as wide as possible. - if ( - !assignType( - isContra ? destType : remainingDestSubtypes[0], - isContra ? remainingSrcSubtypes[0] : combineTypes(remainingSrcSubtypes), - diag?.createAddendum(), - constraints, - flags, - recursionCount - ) - ) { - canUseFastPath = false; - } - } - } - - if (canUseFastPath) { - return true; - } - - // If we're looking for type overlaps and at least one type was matched, - // consider it as assignable. - if ((flags & AssignTypeFlags.PartialOverloadOverlap) !== 0 && matchedSomeSubtypes) { - return true; - } - } - - let isIncompatible = false; - - sortedSrcTypes.forEach((subtype) => { - if (isIncompatible) { - return; - } - - if (!assignType(destType, subtype, /* diag */ undefined, constraints, flags, recursionCount)) { - // Determine if the current subtype is subsumed by another subtype - // in the same union. If so, we can ignore this. - const isSubtypeSubsumed = isTypeSubsumedByOtherType( - subtype, - srcType, - /* allowAnyToSubsume */ false, - recursionCount - ); - - // Try again with a concrete version of the subtype. - if ( - !isSubtypeSubsumed && - !assignType(destType, subtype, diag?.createAddendum(), constraints, flags, recursionCount) - ) { - isIncompatible = true; - } - } else { - matchedSomeSubtypes = true; - } - }, /* sortSubtypes */ true); - - if (isIncompatible) { - // If we're looking for type overlaps and at least one type was matched, - // consider it as assignable. - if ((flags & AssignTypeFlags.PartialOverloadOverlap) !== 0 && matchedSomeSubtypes) { - return true; - } - - diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType))); - return false; - } - - return true; - } - - function isSpecialFormClass(classType: ClassType, flags: AssignTypeFlags): boolean { - if ((flags & AssignTypeFlags.AllowIsinstanceSpecialForms) !== 0) { - return false; - } - - return ClassType.isSpecialFormClass(classType); - } - - // Finds unsolved type variables in the destType and establishes constraints - // in the constraint tracker for them based on the srcType. - function setConstraintsForFreeTypeVars( - destType: Type, - srcType: UnknownType | AnyType, - constraints: ConstraintTracker - ) { - const typeVars = getTypeVarArgsRecursive(destType); - typeVars.forEach((typeVar) => { - if (!TypeVarType.isBound(typeVar) && !constraints.getMainConstraintSet().getTypeVar(typeVar)) { - // Don't set ParamSpecs or TypeVarTuples. - if (!isParamSpec(srcType) && !isTypeVarTuple(srcType)) { - constraints.setBounds(typeVar, srcType); - } - } - }); - } - - // Determines whether a type is "subsumed by" (i.e. is a proper subtype of) another type. - function isTypeSubsumedByOtherType(type: Type, otherType: Type, allowAnyToSubsume: boolean, recursionCount = 0) { - const concreteType = makeTopLevelTypeVarsConcrete(type); - const otherSubtypes = isUnion(otherType) ? otherType.priv.subtypes : [otherType]; - - for (const otherSubtype of otherSubtypes) { - if (isTypeSame(otherSubtype, type)) { - continue; - } - - if (isAnyOrUnknown(otherSubtype)) { - if (allowAnyToSubsume) { - return true; - } - } else if (isProperSubtype(otherSubtype, concreteType, recursionCount)) { - return true; - } - } - - return false; - } - - // Determines whether the srcType is a subtype of destType but the converse - // is not true. It's important that we check both directions to avoid - // matches for types like `tuple[Any]` and `tuple[int]` from being considered - // proper subtypes of each other. - function isProperSubtype(destType: Type, srcType: Type, recursionCount: number) { - // If the destType has a condition, don't consider the srcType a proper subtype. - if (destType.props?.condition) { - return false; - } - - // Shortcut the check if either type is Any or Unknown. - if (isAnyOrUnknown(destType) || isAnyOrUnknown(srcType)) { - return true; - } - - // Shortcut the check if either type is a class whose hierarchy contains an unknown type. - if (isClass(destType) && destType.shared.mro.some((mro) => isAnyOrUnknown(mro))) { - return true; - } - - if (isClass(srcType) && srcType.shared.mro.some((mro) => isAnyOrUnknown(mro))) { - return true; - } - - return ( - assignType( - destType, - srcType, - /* diag */ undefined, - /* constraints */ undefined, - AssignTypeFlags.Default, - recursionCount - ) && - !assignType( - srcType, - destType, - /* diag */ undefined, - /* constraints */ undefined, - AssignTypeFlags.Default, - recursionCount - ) - ); - } - - // Determines whether the two types are potentially comparable -- i.e. - // their types overlap in such a way that it makes sense for them to - // be compared with an == or != operator. The functional also supports - // a special variant that can be used for the "is" and "is not" operator. - // This variant can be less conservative in some cases. - function isTypeComparable(leftType: Type, rightType: Type, assumeIsOperator = false) { - if (isAnyOrUnknown(leftType) || isAnyOrUnknown(rightType)) { - return true; - } - - // Viper 扩展:t.CDefine 类型可与任何类型比较(任意转参转换语义) - if (isCDefineType(leftType) || isCDefineType(rightType)) { - return true; - } - - if (isNever(leftType) || isNever(rightType)) { - return false; - } - - if (isModule(leftType) || isModule(rightType)) { - return isTypeSame(leftType, rightType, { ignoreConditions: true }); - } - - const isLeftCallable = isFunctionOrOverloaded(leftType); - const isRightCallable = isFunctionOrOverloaded(rightType); - - // If either type is a function, assume that it may be comparable. The other - // operand might be a callable object, an 'object' instance, etc. We could - // make this more precise for specific cases (e.g. if the other operand is - // None or a literal or an instance of a nominal class that doesn't override - // __call__ and is marked final, etc.), but coming up with a comprehensive - // list is probably not feasible. - if (isLeftCallable || isRightCallable) { - return true; - } - - if (isInstantiableClass(leftType) || (isClassInstance(leftType) && ClassType.isBuiltIn(leftType, 'type'))) { - if ( - isInstantiableClass(rightType) || - (isClassInstance(rightType) && ClassType.isBuiltIn(rightType, 'type')) - ) { - const genericLeftType = ClassType.specialize(leftType, /* typeArgs */ undefined); - const genericRightType = ClassType.specialize(rightType, /* typeArgs */ undefined); - - if (assignType(genericLeftType, genericRightType) || assignType(genericRightType, genericLeftType)) { - return true; - } - } - - // Does the class have an operator overload for eq? - const metaclass = leftType.shared.effectiveMetaclass; - if (metaclass && isClass(metaclass)) { - if (lookUpClassMember(metaclass, '__eq__', MemberAccessFlags.SkipObjectBaseClass)) { - return true; - } - } - - return false; - } - - if (isClassInstance(leftType)) { - if (isClass(rightType)) { - const genericLeftType = ClassType.specialize(leftType, /* typeArgs */ undefined); - const genericRightType = ClassType.specialize(rightType, /* typeArgs */ undefined); - - if (assignType(genericLeftType, genericRightType) || assignType(genericRightType, genericLeftType)) { - return true; - } - - // Check for the "is None" or "is not None" case. - if (assumeIsOperator && isNoneInstance(rightType)) { - if (isNoneInstance(leftType)) { - return true; - } - - // The LHS could be a protocol or 'object', in which case None is - // potentially comparable to it. In other cases, None is not comparable - // because the types are disjoint. - return assignType(leftType, rightType); - } - - // Assume that if the types are disjoint and built-in classes that they - // will never be comparable. - if (ClassType.isBuiltIn(leftType) && ClassType.isBuiltIn(rightType) && TypeBase.isInstance(rightType)) { - // We need to be careful with bool and int literals because - // they are comparable under certain circumstances. - let boolType: ClassType | undefined; - let intType: ClassType | undefined; - if (ClassType.isBuiltIn(leftType, 'bool') && ClassType.isBuiltIn(rightType, 'int')) { - boolType = leftType; - intType = rightType; - } else if (ClassType.isBuiltIn(rightType, 'bool') && ClassType.isBuiltIn(leftType, 'int')) { - boolType = rightType; - intType = leftType; - } - - if (boolType && intType) { - const intVal = intType.priv?.literalValue as number | BigInt | undefined; - if (intVal === undefined) { - return true; - } - if (intVal !== 0 && intVal !== 1) { - return false; - } - - const boolVal = boolType.priv?.literalValue as boolean | undefined; - if (boolVal === undefined) { - return true; - } - - return boolVal === (intVal === 1); - } - - return false; - } - } - - // Does the class have an operator overload for eq? - const eqMethod = lookUpClassMember( - ClassType.cloneAsInstantiable(leftType), - '__eq__', - MemberAccessFlags.SkipObjectBaseClass - ); - - if (eqMethod) { - // If this is a synthesized method for a dataclass, we can assume - // that other dataclass types will not be comparable. - if (ClassType.isDataClass(leftType) && eqMethod.symbol.getSynthesizedType()) { - return false; - } - - return true; - } - - return false; - } - - return true; - } - - function assignToUnionType( - destType: UnionType, - srcType: Type, - diag: DiagnosticAddendum | undefined, - constraints: ConstraintTracker | undefined, - flags: AssignTypeFlags, - recursionCount: number - ): boolean { - // If we need to enforce invariance, the source needs to be compatible - // with all subtypes in the dest, unless those subtypes are subclasses - // of other subtypes. - if (flags & AssignTypeFlags.Invariant) { - let isIncompatible = false; - - doForEachSubtype(destType, (subtype, index) => { - if ( - !isIncompatible && - !assignType(subtype, srcType, diag?.createAddendum(), constraints, flags, recursionCount) - ) { - // Determine whether this subtype is subsumed by some other - // subtype in the union. If so, we can ignore the incompatibility. - let skipSubtype = false; - if (!isAnyOrUnknown(subtype)) { - const adjSubtype = makeTypeVarsBound(subtype, /* scopeIds */ undefined); - - doForEachSubtype(destType, (otherSubtype, otherIndex) => { - if (index !== otherIndex && !skipSubtype) { - const adjOtherSubtype = makeTypeVarsBound(otherSubtype, /* scopeIds */ undefined); - - if ( - assignType( - adjOtherSubtype, - adjSubtype, - /* diag */ undefined, - /* constraints */ undefined, - AssignTypeFlags.Default, - recursionCount - ) - ) { - skipSubtype = true; - } - } - }); - } - if (!skipSubtype) { - isIncompatible = true; - } - } - }); - - if (isIncompatible) { - diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType))); - return false; - } - - return true; - } - - // For union destinations, we just need to match one of the types. - const diagAddendum = diag ? new DiagnosticAddendum() : undefined; - - let foundMatch = false; - - // Does the union contain any type variables that need to be solved? - // If so, we need to use a slower path. - if (!requiresSpecialization(destType)) { - for (const subtype of destType.priv.subtypes) { - if (assignType(subtype, srcType, diagAddendum?.createAddendum(), constraints, flags, recursionCount)) { - foundMatch = true; - break; - } - } - } else { - // Run through all subtypes in the union. Don't stop at the first - // match we find because we may need to match TypeVars in other - // subtypes. We special-case "None" so we can handle Optional[T] - // without matching the None to the type var. - if (isNoneInstance(srcType) && isOptionalType(destType)) { - foundMatch = true; - } else { - let bestConstraints: ConstraintTracker | undefined; - let bestConstraintsScore: number | undefined; - let nakedTypeVarMatches = 0; - - // If the srcType is a literal, try to use the fast-path lookup - // in case the destType is a union with hundreds of literals. - if ( - isClassInstance(srcType) && - isLiteralType(srcType) && - UnionType.containsType( - destType, - srcType, - /* options */ undefined, - /* exclusionSet */ undefined, - recursionCount - ) - ) { - return true; - } - - doForEachSubtype( - destType, - (subtype) => { - // Make a temporary clone of the constraints. We don't want to modify - // the original constraints until we find the "optimal" typeVar mapping. - const constraintsClone = constraints?.clone(); - if ( - assignType( - subtype, - srcType, - diagAddendum?.createAddendum(), - constraintsClone, - flags, - recursionCount - ) - ) { - foundMatch = true; - if (constraintsClone) { - // Ask the constraints to compute a "score" for the current - // contents of the table. - let constraintsScore = constraintsClone.getScore(); - - if (isTypeVar(subtype)) { - if (!constraints?.getMainConstraintSet().getTypeVar(subtype)) { - nakedTypeVarMatches++; - - // Handicap the solution slightly so another type var with - // existing constraints will be preferred. - constraintsScore += 0.001; - } - } - - // If the type matches exactly, prefer it over other types. - if (isTypeSame(subtype, stripLiteralValue(srcType))) { - constraintsScore = Number.POSITIVE_INFINITY; - } - - if (bestConstraintsScore === undefined || bestConstraintsScore <= constraintsScore) { - // We found a typeVar mapping with a higher score than before. - bestConstraintsScore = constraintsScore; - bestConstraints = constraintsClone; - } - } - } - }, - /* sortSubtypes */ true - ); - - // If we saw more than one "naked" type vars that have no - // previous constraints recorded, it's dangerous for us to - // assign a value to any of these type vars at this time. - // Typically, they will receive some constraints via some - // later argument assignment. - if (nakedTypeVarMatches > 1 && (flags & AssignTypeFlags.ArgAssignmentFirstPass) !== 0) { - bestConstraints = undefined; - } - - // If we found a winning type var mapping, copy it back to constraints. - if (constraints && bestConstraints) { - constraints.copyFromClone(bestConstraints); - } - } - } - - // If the source is a constrained TypeVar, see if we can assign all of the - // constraints to the union. - if (!foundMatch) { - if (isTypeVar(srcType) && TypeVarType.hasConstraints(srcType)) { - foundMatch = assignType( - destType, - makeTopLevelTypeVarsConcrete(srcType), - diagAddendum?.createAddendum(), - constraints, - flags, - recursionCount - ); - } - } - - if (!foundMatch) { - if (diag && diagAddendum) { - diag.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType))); - diag.addAddendum(diagAddendum); - } - return false; - } - - return true; - } - - function assignConditionalTypeToTypeVar(destType: TypeVarType, srcType: Type, recursionCount: number): boolean { - // The srcType is assignable only if all of its subtypes are assignable. - return !findSubtype(srcType, (srcSubtype) => { - if (isTypeSame(destType, srcSubtype, { ignorePseudoGeneric: true }, recursionCount)) { - return false; - } - - if (isIncompleteUnknown(srcSubtype)) { - return false; - } - - const destTypeVarName = TypeVarType.getNameWithScope(destType); - - // Determine which conditions on this type apply to this type variable. - // There might be more than one of them. - const applicableConditions = (getTypeCondition(srcSubtype) ?? []).filter( - (constraint) => constraint.typeVar.priv.nameWithScope === destTypeVarName - ); - - // If there are no applicable conditions, it's not assignable. - if (applicableConditions.length === 0) { - return true; - } - - return !applicableConditions.some((condition) => { - if (condition.typeVar.priv.nameWithScope === TypeVarType.getNameWithScope(destType)) { - if (destType.shared.boundType) { - assert( - condition.constraintIndex === 0, - 'Expected constraint for bound TypeVar to have index of 0' - ); - - return assignType( - destType.shared.boundType, - srcSubtype, - /* diag */ undefined, - /* constraints */ undefined, - AssignTypeFlags.Default, - recursionCount - ); - } - - if (TypeVarType.hasConstraints(destType)) { - assert( - condition.constraintIndex < destType.shared.constraints.length, - 'Constraint for constrained TypeVar is out of bounds' - ); - - return assignType( - destType.shared.constraints[condition.constraintIndex], - srcSubtype, - /* diag */ undefined, - /* constraints */ undefined, - AssignTypeFlags.Default, - recursionCount - ); - } - - // This is a non-bound and non-constrained type variable with a matching condition. - return true; - } - - return false; - }); - }); - } - - // If the class is a protocol and it has a `__call__` method but no other methods - // or attributes that would be incompatible with a function, this method returns - // the signature of the call implied by the `__call__` method. Otherwise it returns - // undefined. - function getCallbackProtocolType( - objType: ClassType, - recursionCount = 0 - ): FunctionType | OverloadedType | undefined { - if (!isClassInstance(objType) || !ClassType.isProtocolClass(objType)) { - return undefined; - } - - // Make sure that the protocol class doesn't define any fields that - // a normal function wouldn't be compatible with. - for (const mroClass of objType.shared.mro) { - if (isClass(mroClass) && ClassType.isProtocolClass(mroClass)) { - for (const field of ClassType.getSymbolTable(mroClass)) { - const fieldName = field[0]; - const fieldSymbol = field[1]; - - // We're expecting a __call__ method. We will also ignore a - // __slots__ definition, which is (by convention) ignored for - // protocol matching. - if (fieldName === '__call__' || fieldName === '__slots__') { - continue; - } - - if (fieldSymbol.isIgnoredForProtocolMatch()) { - continue; - } - - let fieldIsPartOfFunction = false; - - if (prefetched?.functionClass && isClass(prefetched.functionClass)) { - if (ClassType.getSymbolTable(prefetched.functionClass).has(field[0])) { - fieldIsPartOfFunction = true; - } - } - - if (!fieldIsPartOfFunction) { - return undefined; - } - } - } - } - - const callType = getBoundMagicMethod( - objType, - '__call__', - /* selfType */ undefined, - /* errorNode */ undefined, - /* diag */ undefined, - recursionCount - ); - - if (!callType) { - return undefined; - } - - return makeFunctionTypeVarsBound(callType); - } - - function assignParam( - destType: Type, - srcType: Type, - paramIndex: number | undefined, - diag: DiagnosticAddendum | undefined, - constraints: ConstraintTracker, - flags: AssignTypeFlags, - recursionCount: number - ) { - if (isTypeVarTuple(destType) && !isUnpacked(srcType)) { - return false; - } - - let specializedSrcType = srcType; - let specializedDestType = destType; - let doSpecializationStep = false; - - if ((flags & AssignTypeFlags.OverloadOverlap) === 0) { - const isFirstPass = (flags & AssignTypeFlags.ArgAssignmentFirstPass) !== 0; - - if ((flags & AssignTypeFlags.Contravariant) === 0) { - if (!isFirstPass) { - specializedDestType = solveAndApplyConstraints( - destType, - constraints, - /* applyOptions */ undefined, - { useLowerBoundOnly: true } - ); - } - doSpecializationStep = requiresSpecialization(specializedDestType); - } else { - if (!isFirstPass) { - specializedSrcType = solveAndApplyConstraints(srcType, constraints, /* applyOptions */ undefined, { - useLowerBoundOnly: true, - }); - } - doSpecializationStep = requiresSpecialization(specializedSrcType); - } - } - - // Is an additional specialization step required? - if (doSpecializationStep) { - if ( - assignType( - specializedSrcType, - specializedDestType, - /* diag */ undefined, - constraints, - (flags ^ AssignTypeFlags.Contravariant) | AssignTypeFlags.RetainLiteralsForTypeVar, - recursionCount - ) - ) { - specializedDestType = solveAndApplyConstraints(destType, constraints); - } - } - - if ( - !assignType( - specializedSrcType, - specializedDestType, - diag?.createAddendum(), - constraints, - flags, - recursionCount - ) - ) { - if (diag && paramIndex !== undefined) { - diag.addMessage( - LocAddendum.paramAssignment().format({ - index: paramIndex + 1, - sourceType: printType(destType), - destType: printType(srcType), - }) - ); - } - - return false; - } - - return true; - } - - // Determines whether we need to pack some of the source positionals - // into a tuple that matches a variadic *args parameter in the destination. - function adjustSourceParamDetailsForDestVariadic(srcDetails: ParamListDetails, destDetails: ParamListDetails) { - // If there is no *args parameter in the dest, we have nothing to do. - if (destDetails.argsIndex === undefined) { - return; - } - - // If the *args parameter isn't an unpacked TypeVarTuple or tuple, - // we have nothing to do. - if (!isUnpacked(destDetails.params[destDetails.argsIndex].type)) { - return; - } - - // If the source doesn't have enough positional parameters, we have nothing to do. - if (srcDetails.params.length < destDetails.argsIndex) { - return; - } - - let srcLastToPackIndex = srcDetails.params.findIndex((p, i) => { - assert(destDetails.argsIndex !== undefined); - return i >= destDetails.argsIndex && p.kind === ParamKind.Keyword; - }); - if (srcLastToPackIndex < 0) { - srcLastToPackIndex = srcDetails.params.length; - } - - // If both the source and dest have an *args parameter but the dest's is - // in a later position, then we can't assign the source's *args to the dest. - // Don't make any adjustment in this case. - if (srcDetails.argsIndex !== undefined && destDetails.argsIndex > srcDetails.argsIndex) { - return; - } - - const destFirstNonPositional = destDetails.firstKeywordOnlyIndex ?? destDetails.params.length; - const suffixLength = destFirstNonPositional - destDetails.argsIndex - 1; - const srcPositionalsToPack = srcDetails.params.slice(destDetails.argsIndex, srcLastToPackIndex - suffixLength); - const srcTupleTypes: TupleTypeArg[] = []; - srcPositionalsToPack.forEach((entry) => { - if (entry.param.category === ParamCategory.ArgsList) { - if (isUnpackedTypeVarTuple(entry.type)) { - srcTupleTypes.push({ type: entry.type, isUnbounded: false }); - } else if (isUnpackedClass(entry.type) && entry.type.priv.tupleTypeArgs) { - appendArray(srcTupleTypes, entry.type.priv.tupleTypeArgs); - } else { - srcTupleTypes.push({ type: entry.type, isUnbounded: true }); - } - } else { - srcTupleTypes.push({ type: entry.type, isUnbounded: false, isOptional: !!entry.defaultType }); - } - }); - - if (srcTupleTypes.length !== 1 || !isTypeVarTuple(srcTupleTypes[0].type)) { - const srcPositionalsType = makeTupleObject(evaluatorInterface, srcTupleTypes, /* isUnpacked */ true); - - // Snip out the portion of the source positionals that map to the variadic - // dest parameter and replace it with a single parameter that is typed as a - // tuple containing the individual types of the replaced parameters. - srcDetails.params = [ - ...srcDetails.params.slice(0, destDetails.argsIndex), - { - param: FunctionParam.create( - ParamCategory.ArgsList, - srcPositionalsType, - FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared, - '_arg_combined' - ), - type: srcPositionalsType, - declaredType: srcPositionalsType, - index: -1, - kind: ParamKind.Positional, - }, - ...srcDetails.params.slice( - destDetails.argsIndex + srcPositionalsToPack.length, - srcDetails.params.length - ), - ]; - - const argsIndex = srcDetails.params.findIndex((param) => param.param.category === ParamCategory.ArgsList); - srcDetails.argsIndex = argsIndex >= 0 ? argsIndex : undefined; - - const kwargsIndex = srcDetails.params.findIndex( - (param) => param.param.category === ParamCategory.KwargsDict - ); - srcDetails.kwargsIndex = kwargsIndex >= 0 ? kwargsIndex : undefined; - - const firstKeywordOnlyIndex = srcDetails.params.findIndex((param) => param.kind === ParamKind.Keyword); - srcDetails.firstKeywordOnlyIndex = firstKeywordOnlyIndex >= 0 ? firstKeywordOnlyIndex : undefined; - - srcDetails.positionOnlyParamCount = Math.max( - 0, - srcDetails.params.findIndex( - (p) => - p.kind !== ParamKind.Positional || p.param.category !== ParamCategory.Simple || !!p.defaultType - ) - ); - } - } - - function assignFunction( - destType: FunctionType, - srcType: FunctionType, - diag: DiagnosticAddendum | undefined, - constraints: ConstraintTracker, - flags: AssignTypeFlags, - recursionCount: number - ): boolean { - let canAssign = true; - const checkReturnType = (flags & AssignTypeFlags.SkipReturnTypeCheck) === 0; - const isContra = (flags & AssignTypeFlags.Contravariant) !== 0; - flags &= ~AssignTypeFlags.SkipReturnTypeCheck; - - const destParamSpec = FunctionType.getParamSpecFromArgsKwargs(destType); - if (destParamSpec) { - destType = FunctionType.cloneRemoveParamSpecArgsKwargs(destType); - } - - const srcParamSpec = FunctionType.getParamSpecFromArgsKwargs(srcType); - if (srcParamSpec) { - srcType = FunctionType.cloneRemoveParamSpecArgsKwargs(srcType); - } - - const destParamDetails = getParamListDetails(destType, { - disallowExtraKwargsForTd: (flags & AssignTypeFlags.DisallowExtraKwargsForTd) !== 0, - }); - const srcParamDetails = getParamListDetails(srcType, { - disallowExtraKwargsForTd: (flags & AssignTypeFlags.DisallowExtraKwargsForTd) !== 0, - }); - - adjustSourceParamDetailsForDestVariadic( - isContra ? destParamDetails : srcParamDetails, - isContra ? srcParamDetails : destParamDetails - ); - - const targetIncludesParamSpec = isContra ? !!srcParamSpec : !!destParamSpec; - - const destPositionalCount = destParamDetails.firstKeywordOnlyIndex ?? destParamDetails.params.length; - const srcPositionalCount = srcParamDetails.firstKeywordOnlyIndex ?? srcParamDetails.params.length; - const positionalsToMatch = Math.min(destPositionalCount, srcPositionalCount); - const skippedPosParamIndices: number[] = []; - - // Match positional parameters. - for (let paramIndex = 0; paramIndex < positionalsToMatch; paramIndex++) { - if ( - paramIndex === 0 && - destType.shared.methodClass && - (flags & AssignTypeFlags.SkipSelfClsParamCheck) !== 0 - ) { - if (FunctionType.isInstanceMethod(destType) || FunctionType.isClassMethod(destType)) { - continue; - } - } - - // Skip over the *args parameter since it's handled separately below. - if (paramIndex === destParamDetails.argsIndex) { - if (!isUnpackedTypeVarTuple(destParamDetails.params[destParamDetails.argsIndex].type)) { - skippedPosParamIndices.push(paramIndex); - } - continue; - } - - const destParam = destParamDetails.params[paramIndex]; - const srcParam = srcParamDetails.params[paramIndex]; - - // Find the original index of this source param. If we synthesized it above (for - // a variadic parameter), it may not be found. - const srcParamType = srcParam.type; - const destParamType = destParam.type; - - const destParamName = destParam.param.name ?? ''; - const srcParamName = srcParam.param.name ?? ''; - if (destParamName) { - const isDestPositionalOnly = - destParam.kind === ParamKind.Positional || destParam.kind === ParamKind.ExpandedArgs; - if ( - !isDestPositionalOnly && - destParam.param.category !== ParamCategory.ArgsList && - srcParam.param.category !== ParamCategory.ArgsList - ) { - if (srcParam.kind === ParamKind.Positional || srcParam.kind === ParamKind.ExpandedArgs) { - diag?.createAddendum().addMessage( - LocAddendum.functionParamPositionOnly().format({ - name: destParamName, - }) - ); - canAssign = false; - } else if (destParamName !== srcParamName) { - diag?.createAddendum().addMessage( - LocAddendum.functionParamName().format({ - srcName: srcParamName, - destName: destParamName, - }) - ); - canAssign = false; - } - } - } - - if (destParam.defaultType) { - if (!srcParam.defaultType && paramIndex !== srcParamDetails.argsIndex) { - diag?.createAddendum().addMessage( - LocAddendum.functionParamDefaultMissing().format({ - name: srcParamName, - }) - ); - canAssign = false; - } - - // If we're performing a partial overload match and both the source - // and dest parameters provide defaults, assume that there could - // be a match. - if ((flags & AssignTypeFlags.PartialOverloadOverlap) !== 0) { - if (srcParam.defaultType) { - continue; - } - } - } - - // Handle the special case of an overloaded __init__ method whose self - // parameter is annotated. - if ( - paramIndex === 0 && - srcType.shared.name === '__init__' && - FunctionType.isInstanceMethod(srcType) && - destType.shared.name === '__init__' && - FunctionType.isInstanceMethod(destType) && - FunctionType.isOverloaded(destType) && - FunctionParam.isTypeDeclared(destParam.param) - ) { - continue; - } - - if (isUnpacked(srcParamType)) { - canAssign = false; - } else if ( - !assignParam( - destParamType, - srcParamType, - paramIndex, - diag?.createAddendum(), - constraints, - flags, - recursionCount - ) - ) { - // Handle the special case where the source parameter is a synthesized - // TypeVar for "self" or "cls". - if ( - (flags & AssignTypeFlags.SkipSelfClsTypeCheck) === 0 || - !isTypeVar(srcParamType) || - !srcParamType.shared.isSynthesized - ) { - canAssign = false; - } - } else if ( - destParam.kind !== ParamKind.Positional && - destParam.kind !== ParamKind.ExpandedArgs && - srcParam.kind === ParamKind.Positional && - srcParamDetails.kwargsIndex === undefined && - !srcParamDetails.params.some( - (p) => - p.kind === ParamKind.Keyword && - p.param.category === ParamCategory.Simple && - p.param.name === destParam.param.name - ) - ) { - diag?.addMessage( - LocAddendum.namedParamMissingInSource().format({ - name: destParam.param.name ?? '', - }) - ); - canAssign = false; - } - } - - if ( - !FunctionType.isGradualCallableForm(destType) && - destParamDetails.firstPositionOrKeywordIndex < srcParamDetails.positionOnlyParamCount && - !targetIncludesParamSpec - ) { - diag?.createAddendum().addMessage( - LocAddendum.argsPositionOnly().format({ - expected: srcParamDetails.positionOnlyParamCount, - received: destParamDetails.firstPositionOrKeywordIndex, - }) - ); - canAssign = false; - } - - if (destPositionalCount < srcPositionalCount && !targetIncludesParamSpec) { - // Add any remaining positional parameter indices to the list that - // need to be validated. - for (let i = destPositionalCount; i < srcPositionalCount; i++) { - skippedPosParamIndices.push(i); - } - - for (const i of skippedPosParamIndices) { - // If the dest has an *args parameter, make sure it can accept the remaining - // positional arguments in the source. - if (destParamDetails.argsIndex !== undefined) { - const destArgsType = destParamDetails.params[destParamDetails.argsIndex].type; - const srcParamType = srcParamDetails.params[i].type; - if ( - !assignParam( - destArgsType, - srcParamType, - i, - diag?.createAddendum(), - constraints, - flags, - recursionCount - ) - ) { - canAssign = false; - } - - continue; - } - - // If The source parameter has a default value, it is OK for the - // corresponding dest parameter to be missing. - const srcParam = srcParamDetails.params[i]; - - if (srcParam.defaultType) { - // Assign default arg value in case it is needed for - // populating TypeVar constraints. - const paramInfo = srcParamDetails.params[i]; - const defaultArgType = paramInfo.defaultType ?? paramInfo.defaultType; - - // Enforce invariance below because the default arg value - // is constructed prior to the call, so its type is already - // fixed. - if ( - defaultArgType && - !assignType( - paramInfo.type, - defaultArgType, - diag?.createAddendum(), - constraints, - flags, - recursionCount - ) - ) { - if ((flags & AssignTypeFlags.PartialOverloadOverlap) === 0) { - canAssign = false; - } - } - - continue; - } - - // If the source parameter is also addressable by keyword, it is OK - // that there is no matching positional parameter in the dest. - if (srcParam.kind === ParamKind.Standard) { - continue; - } - - // If the source parameter is a variadic, it is OK that there is no - // matching positional parameter in the dest. - if (srcParam.param.category === ParamCategory.ArgsList) { - continue; - } - - const nonDefaultSrcParamCount = srcParamDetails.params.filter( - (p) => !!p.param.name && !p.defaultType && p.param.category === ParamCategory.Simple - ).length; - - diag?.createAddendum().addMessage( - LocAddendum.functionTooManyParams().format({ - expected: destPositionalCount, - received: nonDefaultSrcParamCount, - }) - ); - canAssign = false; - break; - } - } else if (srcPositionalCount < destPositionalCount) { - if (srcParamDetails.argsIndex !== undefined) { - // Make sure the remaining dest parameters can be assigned to the source - // *args parameter type. - const srcArgsType = srcParamDetails.params[srcParamDetails.argsIndex].type; - for (let paramIndex = srcPositionalCount; paramIndex < destPositionalCount; paramIndex++) { - if (paramIndex === srcParamDetails.argsIndex) { - continue; - } - - const destParamType = destParamDetails.params[paramIndex].type; - if (isTypeVarTuple(destParamType) && !isTypeVarTuple(srcArgsType)) { - diag?.addMessage(LocAddendum.typeVarTupleRequiresKnownLength()); - canAssign = false; - } else { - if ( - !assignParam( - destParamType, - srcArgsType, - paramIndex, - diag?.createAddendum(), - constraints, - flags, - recursionCount - ) - ) { - canAssign = false; - } - - const destParamKind = destParamDetails.params[paramIndex].kind; - if ( - destParamKind !== ParamKind.Positional && - destParamKind !== ParamKind.ExpandedArgs && - srcParamDetails.kwargsIndex === undefined - ) { - diag?.addMessage( - LocAddendum.namedParamMissingInSource().format({ - name: destParamDetails.params[paramIndex].param.name ?? '', - }) - ); - canAssign = false; - } - } - } - } else if (!srcParamDetails.paramSpec) { - // If the dest contains a *args, remove it from the positional count - // because it's OK for zero source args to match it. - let adjDestPositionalCount = destPositionalCount; - if (destParamDetails.argsIndex !== undefined && destParamDetails.argsIndex < destPositionalCount) { - adjDestPositionalCount--; - } - - // If we're doing a partial overload overlap check, ignore dest positional - // params with default values. - if ((flags & AssignTypeFlags.PartialOverloadOverlap) !== 0) { - while ( - adjDestPositionalCount > 0 && - destParamDetails.params[adjDestPositionalCount - 1].defaultType - ) { - adjDestPositionalCount--; - } - } - - if (srcPositionalCount < adjDestPositionalCount) { - diag?.addMessage( - LocAddendum.functionTooFewParams().format({ - expected: adjDestPositionalCount, - received: srcPositionalCount, - }) - ); - canAssign = false; - } - } - } - - // If both src and dest have an "*args" parameter, make sure - // their types are compatible. - if ( - srcParamDetails.argsIndex !== undefined && - destParamDetails.argsIndex !== undefined && - !FunctionType.isGradualCallableForm(destType) - ) { - let destArgsType = destParamDetails.params[destParamDetails.argsIndex].type; - let srcArgsType = srcParamDetails.params[srcParamDetails.argsIndex].type; - - if (!isUnpacked(destArgsType)) { - destArgsType = makeTupleObject( - evaluatorInterface, - [{ type: destArgsType, isUnbounded: true }], - /* isUnpacked */ true - ); - } - - if (!isUnpacked(srcArgsType)) { - srcArgsType = makeTupleObject( - evaluatorInterface, - [{ type: srcArgsType, isUnbounded: true }], - /* isUnpacked */ true - ); - } - - if ( - !assignParam( - destArgsType, - srcArgsType, - destParamDetails.params[destParamDetails.argsIndex].index, - diag?.createAddendum(), - constraints, - flags, - recursionCount - ) - ) { - canAssign = false; - } - } - - // If the dest has an "*args" but the source doesn't, report the incompatibility. - // The converse situation is OK. - if ( - !FunctionType.isGradualCallableForm(destType) && - srcParamDetails.argsIndex === undefined && - srcParamSpec === undefined && - destParamDetails.argsIndex !== undefined && - !destParamDetails.hasUnpackedTypeVarTuple - ) { - diag?.createAddendum().addMessage( - LocAddendum.argsParamMissing().format({ - paramName: destParamDetails.params[destParamDetails.argsIndex].param.name ?? '', - }) - ); - canAssign = false; - } - - // Handle matching of named (keyword) parameters. - if (!targetIncludesParamSpec) { - // Build a dictionary of named parameters in the dest. - const destParamMap = new Map(); - - if (destParamDetails.firstKeywordOnlyIndex !== undefined) { - destParamDetails.params.forEach((param, index) => { - if (index >= destParamDetails.firstKeywordOnlyIndex!) { - if ( - param.param.name && - param.param.category === ParamCategory.Simple && - param.kind !== ParamKind.Positional && - param.kind !== ParamKind.ExpandedArgs - ) { - destParamMap.set(param.param.name, param); - } - } - }); - } - - // If the dest has fewer positional arguments than the source, the remaining - // positional arguments in the source can be treated as named arguments. - let srcStartOfNamed = - srcParamDetails.firstKeywordOnlyIndex !== undefined - ? srcParamDetails.firstKeywordOnlyIndex - : srcParamDetails.params.length; - if (destPositionalCount < srcPositionalCount && destParamDetails.argsIndex === undefined) { - srcStartOfNamed = destPositionalCount; - } - - if (srcStartOfNamed >= 0) { - srcParamDetails.params.forEach((srcParamInfo, index) => { - if (index < srcStartOfNamed) { - return; - } - - if ( - !srcParamInfo.param.name || - srcParamInfo.param.category !== ParamCategory.Simple || - srcParamInfo.kind === ParamKind.Positional - ) { - return; - } - - const destParamInfo = destParamMap.get(srcParamInfo.param.name); - const paramDiag = diag?.createAddendum(); - const srcParamType = srcParamInfo.type; - - if (!destParamInfo) { - if (destParamDetails.kwargsIndex === undefined && !srcParamInfo.defaultType) { - if (paramDiag) { - paramDiag.addMessage( - LocAddendum.namedParamMissingInDest().format({ - name: srcParamInfo.param.name, - }) - ); - } - canAssign = false; - } else if (destParamDetails.kwargsIndex !== undefined) { - // Make sure we can assign the type to the Kwargs. - if ( - !assignParam( - destParamDetails.params[destParamDetails.kwargsIndex].type, - srcParamType, - destParamDetails.params[destParamDetails.kwargsIndex].index, - diag?.createAddendum(), - constraints, - flags, - recursionCount - ) - ) { - canAssign = false; - } - } else if (srcParamInfo.defaultType) { - // Assign default arg values in case they are needed for - // populating TypeVar constraints. - const defaultArgType = srcParamInfo.defaultType ?? srcParamInfo.defaultType; - - if ( - defaultArgType && - !assignType( - srcParamInfo.type, - defaultArgType, - diag?.createAddendum(), - constraints, - flags, - recursionCount - ) - ) { - if ((flags & AssignTypeFlags.PartialOverloadOverlap) === 0) { - canAssign = false; - } - } - } - return; - } - - // If we're performing a partial overload match and both the source - // and dest parameters provide defaults, assume that there could - // be a match. - if (srcParamInfo.defaultType && destParamInfo.defaultType) { - if ((flags & AssignTypeFlags.PartialOverloadOverlap) !== 0) { - destParamMap.delete(srcParamInfo.param.name); - return; - } - } - - const destParamType = destParamInfo.type; - const specializedDestParamType = constraints - ? solveAndApplyConstraints(destParamType, constraints) - : destParamType; - - if ( - !assignParam( - destParamInfo.type, - srcParamType, - /* paramIndex */ undefined, - paramDiag?.createAddendum(), - constraints, - flags, - recursionCount - ) - ) { - if (paramDiag) { - paramDiag.addMessage( - LocAddendum.namedParamTypeMismatch().format({ - name: srcParamInfo.param.name, - sourceType: printType(specializedDestParamType), - destType: printType(srcParamType), - }) - ); - } - canAssign = false; - } - - if (destParamInfo.defaultType && !srcParamInfo.defaultType) { - diag?.createAddendum().addMessage( - LocAddendum.functionParamDefaultMissing().format({ - name: srcParamInfo.param.name, - }) - ); - canAssign = false; - } - - destParamMap.delete(srcParamInfo.param.name); - }); - } - - // See if there are any unmatched named parameters. - destParamMap.forEach((destParamInfo, paramName) => { - if (srcParamDetails.kwargsIndex !== undefined && destParamInfo.param.name) { - // Make sure the src kwargs type is compatible. - if ( - !assignParam( - destParamInfo.type, - srcParamDetails.params[srcParamDetails.kwargsIndex].type, - destParamInfo.index, - diag?.createAddendum(), - constraints, - flags, - recursionCount - ) - ) { - canAssign = false; - } - destParamMap.delete(paramName); - } else { - diag?.createAddendum().addMessage( - LocAddendum.namedParamMissingInSource().format({ name: paramName }) - ); - canAssign = false; - } - }); - - // If both src and dest have a "**kwargs" parameter, make sure their types are compatible. - if (srcParamDetails.kwargsIndex !== undefined && destParamDetails.kwargsIndex !== undefined) { - if ( - !assignParam( - destParamDetails.params[destParamDetails.kwargsIndex].type, - srcParamDetails.params[srcParamDetails.kwargsIndex].type, - destParamDetails.params[destParamDetails.kwargsIndex].index, - diag?.createAddendum(), - constraints, - flags, - recursionCount - ) - ) { - canAssign = false; - } - } - - // If the dest has a "**kwargs" but the source doesn't, report the incompatibility. - // The converse situation is OK. - if ( - !FunctionType.isGradualCallableForm(destType) && - srcParamDetails.kwargsIndex === undefined && - srcParamSpec === undefined && - destParamDetails.kwargsIndex !== undefined - ) { - diag?.createAddendum().addMessage( - LocAddendum.kwargsParamMissing().format({ - paramName: destParamDetails.params[destParamDetails.kwargsIndex].param.name!, - }) - ); - canAssign = false; - } - } - - if ((flags & AssignTypeFlags.OverloadOverlap) !== 0) { - // If we're checking for full overlapping overloads and the source is - // a gradual form, the dest must also be a gradual form. - if (FunctionType.isGradualCallableForm(srcType) && !FunctionType.isGradualCallableForm(destType)) { - canAssign = false; - } - - // If the src contains a ParamSpec the dest must also. - if (srcParamSpec && !destParamSpec) { - canAssign = false; - } - } - - // If the source and the dest are using the same ParamSpec, any additional - // concatenated parameters must match. - if (targetIncludesParamSpec && srcParamSpec?.priv.nameWithScope === destParamSpec?.priv.nameWithScope) { - if (srcParamDetails.params.length !== destParamDetails.params.length) { - canAssign = false; - } - } - - // Are we assigning to a function with a ParamSpec? - if (targetIncludesParamSpec) { - const effectiveSrcType = isContra ? destType : srcType; - const effectiveDestType = isContra ? srcType : destType; - - const effectiveSrcParamSpec = isContra ? destParamSpec : srcParamSpec; - const effectiveDestParamSpec = isContra ? srcParamSpec : destParamSpec; - - if (effectiveDestParamSpec) { - const requiredMatchParamCount = effectiveDestType.shared.parameters.filter((p, i) => { - if (!p.name) { - return false; - } - - const paramType = FunctionType.getParamType(effectiveDestType, i); - if (p.category === ParamCategory.Simple && isParamSpec(paramType)) { - return false; - } - return true; - }).length; - let matchedParamCount = 0; - const remainingParams: FunctionParam[] = []; - - // If there are parameters in the source that are not matched - // to parameters in the dest, assume these are concatenated on - // to the ParamSpec. - effectiveSrcType.shared.parameters.forEach((p, index) => { - if (matchedParamCount < requiredMatchParamCount) { - if (p.name) { - matchedParamCount++; - } - - // If this is a *args parameter, assume that it provides - // the remaining positional parameters, but also assume - // that it is not exhausted and can provide additional - // parameters. - if (p.category !== ParamCategory.ArgsList) { - return; - } - } - - if (isPositionOnlySeparator(p) && remainingParams.length === 0) { - // Don't bother pushing a position-only separator if it - // is the first remaining param. - return; - } - - remainingParams.push( - FunctionParam.create( - p.category, - FunctionType.getParamType(effectiveSrcType, index), - p.flags, - p.name, - FunctionType.getParamDefaultType(effectiveSrcType, index), - p.defaultExpr - ) - ); - }); - - // If there are remaining parameters and the source and dest do not contain - // the same ParamSpec, synthesize a function for the remaining parameters. - if ( - remainingParams.length > 0 || - !effectiveSrcParamSpec || - !isTypeSame(effectiveSrcParamSpec, effectiveDestParamSpec, { ignoreTypeFlags: true }) - ) { - const effectiveSrcPosCount = isContra ? destPositionalCount : srcPositionalCount; - const effectiveDestPosCount = isContra ? srcPositionalCount : destPositionalCount; - - // If the src and dest both have ParamSpecs but the src has additional positional - // parameters that have not been matched to dest positional parameters (probably due - // to a Concatenate), don't attempt to assign the remaining parameters to the ParamSpec. - if (!effectiveSrcParamSpec || effectiveSrcPosCount >= effectiveDestPosCount) { - const remainingFunction = FunctionType.createInstance( - '', - '', - '', - effectiveSrcType.shared.flags | FunctionTypeFlags.SynthesizedMethod, - effectiveSrcType.shared.docString - ); - remainingFunction.shared.deprecatedMessage = effectiveSrcType.shared.deprecatedMessage; - remainingFunction.shared.typeVarScopeId = effectiveSrcType.shared.typeVarScopeId; - remainingFunction.priv.constructorTypeVarScopeId = - effectiveSrcType.priv.constructorTypeVarScopeId; - remainingFunction.shared.methodClass = effectiveSrcType.shared.methodClass; - remainingParams.forEach((param) => { - FunctionType.addParam(remainingFunction, param); - }); - if (effectiveSrcParamSpec) { - FunctionType.addParamSpecVariadics( - remainingFunction, - convertToInstance(effectiveSrcParamSpec) - ); - } - - if ( - !assignType( - effectiveDestParamSpec, - remainingFunction, - /* diag */ undefined, - constraints, - flags - ) - ) { - // If we couldn't assign the function to the ParamSpec, see if we can - // assign only the ParamSpec. This is possible if there were no - // remaining parameters. - if ( - remainingParams.length > 0 || - !effectiveSrcParamSpec || - !assignType( - convertToInstance(effectiveDestParamSpec), - convertToInstance(effectiveSrcParamSpec), - /* diag */ undefined, - constraints, - flags - ) - ) { - canAssign = false; - } - } - } - } - } - } - - // Match the return parameter. - if (checkReturnType) { - const destReturnType = getEffectiveReturnType(destType); - if (!isAnyOrUnknown(destReturnType)) { - const srcReturnType = solveAndApplyConstraints(getEffectiveReturnType(srcType), constraints); - const returnDiag = diag?.createAddendum(); - - let isReturnTypeCompatible = false; - - let effectiveFlags = flags; - - // If the source has a declared return type that includes a literal - // in its annotation, assume that we will want the constraint - // solver to retain literals. - if ( - srcType.shared.declaredReturnType && - containsLiteralType(srcType.shared.declaredReturnType, /* includeTypeArgs */ true) - ) { - effectiveFlags |= AssignTypeFlags.RetainLiteralsForTypeVar; - } - - if ( - assignType( - destReturnType, - srcReturnType, - returnDiag?.createAddendum(), - constraints, - effectiveFlags, - recursionCount - ) - ) { - isReturnTypeCompatible = true; - } else { - // Handle the special case where the return type is a TypeGuard[T] - // or TypeIs[T]. This should also act as a bool, since that's its - // type at runtime. - if ( - isClassInstance(srcReturnType) && - ClassType.isBuiltIn(srcReturnType, ['TypeGuard', 'TypeIs']) && - prefetched?.boolClass && - isInstantiableClass(prefetched.boolClass) - ) { - if ( - assignType( - destReturnType, - ClassType.cloneAsInstance(prefetched.boolClass), - returnDiag?.createAddendum(), - constraints, - flags, - recursionCount - ) - ) { - isReturnTypeCompatible = true; - } - } - } - - if (!isReturnTypeCompatible) { - if (returnDiag) { - returnDiag.addMessage( - LocAddendum.functionReturnTypeMismatch().format({ - sourceType: printType(srcReturnType), - destType: printType(destReturnType), - }) - ); - } - canAssign = false; - } - } - } - - return canAssign; - } - - // When a value is assigned to a variable with a declared type, - // we may be able to narrow the type based on the assignment. - function narrowTypeBasedOnAssignment(declaredType: Type, assignedTypeResult: TypeResult): TypeResult { - // TODO: The rules for narrowing types on assignment are not defined in - // the typing spec. Vipeye's current logic is currently not even internally - // consistent and probably not sound from a type theory perspective. It - // should be completely reworked once there has been a public discussion - // about the correct behavior. - - const narrowedType = mapSubtypes(assignedTypeResult.type, (assignedSubtype) => { - // Handle the special case where the assigned type is a literal type. - // Some types include very large unions of literal types, and we don't - // want to use an n^2 loop to compare them. - if (isClass(assignedSubtype) && isLiteralType(assignedSubtype)) { - if (isUnion(declaredType) && UnionType.containsType(declaredType, assignedSubtype)) { - return assignedSubtype; - } - } - - const narrowedSubtype = mapSubtypes(declaredType, (declaredSubtype) => { - if (!assignType(declaredSubtype, assignedSubtype)) { - return undefined; - } - - // Retain unknowns for code flow analysis convergence and for - // unknown type reporting in strict mode. - if (isUnknown(assignedSubtype)) { - return assignedSubtype; - } - - // If the two types are bidirectionally assignable, they are - // either equivalent (in which case it doesn't matter which - // one we choose) or one or both include gradual types (Any, etc.), - // in which case we'll want to stick with the declared subtype. - if (assignType(assignedSubtype, declaredSubtype)) { - // We need to be careful with TypedDict types that have - // narrowed fields. In this case, we want to return the - // assigned type. - if ( - isClass(assignedSubtype) && - assignedSubtype.priv.typedDictNarrowedEntries && - isTypeSame(assignedSubtype, declaredSubtype, { ignoreTypedDictNarrowEntries: true }) - ) { - return assignedSubtype; - } - - // We also need to be careful with callback protocols. - if (isClassInstance(declaredSubtype) && ClassType.isProtocolClass(declaredSubtype)) { - if (isFunctionOrOverloaded(assignedSubtype)) { - return assignedSubtype; - } - } - - return declaredSubtype; - } - - return assignedSubtype; - }); - - // If we couldn't assign the assigned subtype any of the declared - // subtypes, the types are incompatible. Return the unnarrowed form. - if (isNever(narrowedSubtype)) { - return assignedSubtype; - } - - return narrowedSubtype; - }); - - // If the result of narrowing is an Unknown that is incomplete, propagate the - // incomplete type for the benefit of code flow analysis. - // If the result of narrowing is a complete Unknown, combine the Unknown type - // with the declared type. In strict mode, this will retain the "unknown type" - // diagnostics while still providing reasonable completion suggestions. - if (isIncompleteUnknown(narrowedType)) { - return { type: narrowedType, isIncomplete: assignedTypeResult.isIncomplete }; - } else if (isUnknown(narrowedType)) { - return { type: combineTypes([narrowedType, declaredType]), isIncomplete: assignedTypeResult.isIncomplete }; - } - - return { type: narrowedType, isIncomplete: assignedTypeResult.isIncomplete }; - } - - function validateOverrideMethod( - baseMethod: Type, - overrideMethod: FunctionType | OverloadedType, - baseClass: ClassType | undefined, - diag: DiagnosticAddendum, - enforceParamNames = true - ): boolean { - // If we're overriding a non-method with a method, report it as an error. - // This occurs when a non-property overrides a property. - if (!isFunctionOrOverloaded(baseMethod)) { - diag.addMessage(LocAddendum.overrideType().format({ type: printType(baseMethod) })); - return false; - } - - if (isFunction(baseMethod)) { - // Handle the easy case - a simple function overriding another simple function. - if (isFunction(overrideMethod)) { - return validateOverrideMethodInternal(baseMethod, overrideMethod, diag, enforceParamNames); - } - - const overloadsAndImpl = [...OverloadedType.getOverloads(overrideMethod)]; - const impl = OverloadedType.getImplementation(overrideMethod); - if (impl && isFunction(impl)) { - overloadsAndImpl.push(impl); - } - - // For an overload overriding a base method, at least one overload - // or the implementation must be compatible with the base method. - if ( - overloadsAndImpl.some((overrideOverload) => { - return validateOverrideMethodInternal( - baseMethod, - overrideOverload, - /* diag */ undefined, - enforceParamNames - ); - }) - ) { - return true; - } - - diag.addMessage(LocAddendum.overrideNoOverloadMatches()); - return false; - } - - // For a non-overloaded method overriding an overloaded method, the - // override must match all of the overloads. - if (isFunction(overrideMethod)) { - return OverloadedType.getOverloads(baseMethod).every((overload) => { - // If the override isn't applicable for this base class, skip the check. - if (baseClass && !isOverrideMethodApplicable(overload, baseClass)) { - return true; - } - - return validateOverrideMethodInternal( - overload, - overrideMethod, - diag?.createAddendum(), - enforceParamNames - ); - }); - } - - // For an overloaded method overriding an overloaded method, the overrides - // must all match and be in the correct order. It is OK if the base method - // has additional overloads that are not present in the override. - - let previousMatchIndex = -1; - const baseOverloads = OverloadedType.getOverloads(baseMethod); - - for (const overrideOverload of OverloadedType.getOverloads(overrideMethod)) { - let possibleMatchIndex: number | undefined; - - let matchIndex = baseOverloads.findIndex((baseOverload, index) => { - // If the override isn't applicable for this base class, skip the check. - if (baseClass && !isOverrideMethodApplicable(baseOverload, baseClass)) { - return false; - } - - const isCompatible = validateOverrideMethodInternal( - baseOverload, - overrideOverload, - /* diag */ undefined, - enforceParamNames - ); - - // If the override is compatible but the match is one that is below the previous - // matched index, keep looking for additional matches. Record the fact that - // we found at least one match. - if (isCompatible && index <= previousMatchIndex && possibleMatchIndex === undefined) { - possibleMatchIndex = index; - return false; - } - - return isCompatible; - }); - - if (matchIndex < 0 && possibleMatchIndex !== undefined) { - matchIndex = possibleMatchIndex; - } - - if (matchIndex < 0) { - break; - } - - if (matchIndex < previousMatchIndex) { - diag.addMessage(LocAddendum.overrideOverloadOrder()); - return false; - } - - previousMatchIndex = matchIndex; - } - - if (previousMatchIndex < baseOverloads.length - 1) { - const unmatchedOverloads = baseOverloads.slice(previousMatchIndex + 1); - - // See if all of the remaining overrides are nonapplicable. - if ( - !baseClass || - unmatchedOverloads.some((overload) => { - return isOverrideMethodApplicable(overload, baseClass); - }) - ) { - // We didn't find matches for all of the base overloads. - diag.addMessage(LocAddendum.overrideOverloadNoMatch()); - return false; - } - } - - return true; - } - - // Determines whether a child class override is applicable to a parent - // class method signature. This is important in cases where the parent - // class defines an overload where some of the overload signatures supply - // explicit type annotations for the "self" or "cls" parameter and some - // of these do not apply to the child class. - function isOverrideMethodApplicable(baseMethod: FunctionType, childClass: ClassType): boolean { - if ( - !FunctionType.isInstanceMethod(baseMethod) && - !FunctionType.isClassMethod(baseMethod) && - !FunctionType.isConstructorMethod(baseMethod) - ) { - return true; - } - - const baseParamDetails = getParamListDetails(baseMethod); - if (baseParamDetails.params.length === 0) { - return true; - } - - const baseParamType = baseParamDetails.params[0].param; - - if (baseParamType.category !== ParamCategory.Simple || !FunctionParam.isTypeDeclared(baseParamType)) { - return true; - } - - // If this is a self or cls parameter, determine whether the override - // class can be assigned to the base parameter type. If not, then this - // override doesn't apply. This is important for overloads where the - // base class contains some overload signatures that are not applicable - // to the child class. - const childSelfOrClsType = FunctionType.isInstanceMethod(baseMethod) - ? ClassType.cloneAsInstance(childClass) - : childClass; - - return assignType( - baseParamDetails.params[0].type, - childSelfOrClsType, - /* diag */ undefined, - /* constraints */ undefined, - AssignTypeFlags.Default - ); - } - - // Determines whether the override method is compatible with the overridden method. - // This is used both for parent/child overrides and implicit overrides for peer - // classes in a multi-inheritance case. If enforceParamNames is true, the parameter - // names of non-positional-only parameters are enforced. - function validateOverrideMethodInternal( - baseMethod: FunctionType, - overrideMethod: FunctionType, - diag: DiagnosticAddendum | undefined, - enforceParamNames: boolean - ): boolean { - const baseParamDetails = getParamListDetails(baseMethod); - const overrideParamDetails = getParamListDetails(overrideMethod); - const constraints = new ConstraintTracker(); - - let canOverride = true; - - if (!FunctionType.isGradualCallableForm(baseMethod) && !FunctionType.isGradualCallableForm(overrideMethod)) { - // Verify that we're not overriding a static, class or instance method with - // an incompatible type. - if (FunctionType.isStaticMethod(baseMethod)) { - if (!FunctionType.isStaticMethod(overrideMethod)) { - diag?.addMessage(LocAddendum.overrideNotStaticMethod()); - canOverride = false; - } - } else if (FunctionType.isClassMethod(baseMethod)) { - if (!FunctionType.isClassMethod(overrideMethod)) { - diag?.addMessage(LocAddendum.overrideNotClassMethod()); - canOverride = false; - } - } else if (FunctionType.isInstanceMethod(baseMethod)) { - if (!FunctionType.isInstanceMethod(overrideMethod)) { - diag?.addMessage(LocAddendum.overrideNotInstanceMethod()); - canOverride = false; - } - } - - // Verify that the positional param count matches exactly or that the override - // adds only params that preserve the original signature. - let foundParamCountMismatch = false; - if (overrideParamDetails.positionParamCount < baseParamDetails.positionParamCount) { - if (overrideParamDetails.argsIndex === undefined) { - foundParamCountMismatch = true; - } else { - const overrideArgsType = overrideParamDetails.params[overrideParamDetails.argsIndex].type; - for ( - let i = overrideParamDetails.positionParamCount; - i < baseParamDetails.positionParamCount; - i++ - ) { - if ( - !assignType( - overrideArgsType, - baseParamDetails.params[i].type, - diag?.createAddendum(), - constraints, - AssignTypeFlags.Default - ) - ) { - LocAddendum.overrideParamType().format({ - index: i + 1, - baseType: printType(baseParamDetails.params[i].type), - overrideType: printType(overrideArgsType), - }); - canOverride = false; - } - } - } - } else if (overrideParamDetails.positionParamCount > baseParamDetails.positionParamCount) { - const isCallableAttrOverriddenByInstanceMethod = - baseParamDetails.positionParamCount === 0 && - overrideParamDetails.positionParamCount === 1 && - FunctionType.isInstanceMethod(overrideMethod) && - !FunctionType.isStaticMethod(baseMethod) && - !FunctionType.isClassMethod(baseMethod); - - if (!isCallableAttrOverriddenByInstanceMethod) { - // Verify that all of the override parameters that extend the - // signature are either *args, **kwargs or parameters with - // default values. - - for ( - let i = baseParamDetails.positionParamCount; - i < overrideParamDetails.positionParamCount; - i++ - ) { - const overrideParam = overrideParamDetails.params[i].param; - - if ( - overrideParam.category === ParamCategory.Simple && - overrideParam.name && - !overrideParamDetails.params[i].defaultType - ) { - foundParamCountMismatch = true; - } - } - } - } - - if (foundParamCountMismatch) { - diag?.addMessage( - LocAddendum.overridePositionalParamCount().format({ - baseCount: baseParamDetails.params.length, - overrideCount: overrideParamDetails.params.length, - }) - ); - canOverride = false; - } - - const positionalParamCount = Math.min( - baseParamDetails.positionParamCount, - overrideParamDetails.positionParamCount - ); - - for (let i = 0; i < positionalParamCount; i++) { - // If the first parameter is a "self" or "cls" parameter, skip the - // test because these are allowed to violate the Liskov substitution - // principle. - // - // Note: the checker deliberately defeats this skip for the - // "callable variable overridden by a method" case by marking both - // sides static before calling this routine (see - // `_getCallableVariableOverrideComparison` in checker.ts). Keep the - // two in sync if this skip logic changes. - if (i === 0) { - if ( - FunctionType.isInstanceMethod(overrideMethod) || - FunctionType.isClassMethod(overrideMethod) || - FunctionType.isConstructorMethod(overrideMethod) - ) { - continue; - } - } - - const baseParam = baseParamDetails.params[i].param; - const overrideParam = overrideParamDetails.params[i].param; - - if ( - i >= baseParamDetails.positionOnlyParamCount && - !isPrivateOrProtectedName(baseParam.name || '') && - baseParamDetails.params[i].kind !== ParamKind.Positional && - baseParam.category === ParamCategory.Simple && - enforceParamNames && - baseParam.name !== overrideParam.name - ) { - if (overrideParam.category === ParamCategory.Simple) { - if (!FunctionParam.isNameSynthesized(baseParam)) { - if (overrideParamDetails.params[i].kind === ParamKind.Positional) { - diag?.addMessage( - LocAddendum.overrideParamNamePositionOnly().format({ - index: i + 1, - baseName: baseParam.name || '*', - }) - ); - } else { - diag?.addMessage( - LocAddendum.overrideParamName().format({ - index: i + 1, - baseName: baseParam.name || '*', - overrideName: overrideParam.name || '*', - }) - ); - } - canOverride = false; - } - } - } else if ( - i < overrideParamDetails.positionOnlyParamCount && - i >= baseParamDetails.positionOnlyParamCount - ) { - if ( - !FunctionParam.isNameSynthesized(baseParam) && - baseParamDetails.params[i].kind !== ParamKind.Positional && - baseParamDetails.params[i].kind !== ParamKind.ExpandedArgs - ) { - diag?.addMessage( - LocAddendum.overrideParamNamePositionOnly().format({ - index: i + 1, - baseName: baseParam.name || '*', - }) - ); - canOverride = false; - } - } else { - const baseParamType = baseParamDetails.params[i].type; - const overrideParamType = overrideParamDetails.params[i].type; - - const baseIsSynthesizedTypeVar = isTypeVar(baseParamType) && baseParamType.shared.isSynthesized; - const overrideIsSynthesizedTypeVar = - isTypeVar(overrideParamType) && overrideParamType.shared.isSynthesized; - - if (!baseIsSynthesizedTypeVar && !overrideIsSynthesizedTypeVar) { - if ( - baseParam.category !== overrideParam.category || - !assignType( - overrideParamType, - baseParamType, - diag?.createAddendum(), - constraints, - AssignTypeFlags.Default - ) - ) { - diag?.addMessage( - LocAddendum.overrideParamType().format({ - index: i + 1, - baseType: printType(baseParamType), - overrideType: printType(overrideParamType), - }) - ); - canOverride = false; - } - } - - if (baseParamDetails.params[i].defaultType && !overrideParamDetails.params[i].defaultType) { - diag?.addMessage( - LocAddendum.overrideParamNoDefault().format({ - index: i + 1, - }) - ); - canOverride = false; - } - } - } - - // Check for positional (named) parameters in the base method that - // do not exist in the override. - if (enforceParamNames && overrideParamDetails.kwargsIndex === undefined) { - for (let i = positionalParamCount; i < baseParamDetails.positionParamCount; i++) { - const baseParam = baseParamDetails.params[i]; - - if (baseParam.kind === ParamKind.Standard && baseParam.param.category === ParamCategory.Simple) { - diag?.addMessage( - LocAddendum.overrideParamNamePositionOnly().format({ - index: i + 1, - baseName: baseParam.param.name || '*', - }) - ); - canOverride = false; - } - } - } - - // Check for a *args match. - if (baseParamDetails.argsIndex !== undefined) { - if (overrideParamDetails.argsIndex === undefined) { - diag?.addMessage( - LocAddendum.overrideParamNameMissing().format({ - name: baseParamDetails.params[baseParamDetails.argsIndex].param.name ?? '?', - }) - ); - canOverride = false; - } else { - const overrideParamType = overrideParamDetails.params[overrideParamDetails.argsIndex].type; - const baseParamType = baseParamDetails.params[baseParamDetails.argsIndex].type; - - if ( - !assignType( - overrideParamType, - baseParamType, - diag?.createAddendum(), - constraints, - AssignTypeFlags.Default - ) - ) { - diag?.addMessage( - LocAddendum.overrideParamKeywordType().format({ - name: overrideParamDetails.params[overrideParamDetails.argsIndex].param.name ?? '?', - baseType: printType(baseParamType), - overrideType: printType(overrideParamType), - }) - ); - canOverride = false; - } - } - } - - // Now check any keyword-only parameters. - const baseKwOnlyParams = baseParamDetails.params.filter( - (paramInfo) => paramInfo.kind === ParamKind.Keyword && paramInfo.param.category === ParamCategory.Simple - ); - const overrideKwOnlyParams = overrideParamDetails.params.filter( - (paramInfo) => paramInfo.kind === ParamKind.Keyword && paramInfo.param.category === ParamCategory.Simple - ); - - baseKwOnlyParams.forEach((paramInfo) => { - const overrideParamInfo = overrideKwOnlyParams.find((pi) => paramInfo.param.name === pi.param.name); - - if (!overrideParamInfo && overrideParamDetails.kwargsIndex === undefined) { - diag?.addMessage( - LocAddendum.overrideParamNameMissing().format({ - name: paramInfo.param.name ?? '?', - }) - ); - canOverride = false; - } else { - let targetParamType = overrideParamInfo?.type; - if (!targetParamType) { - targetParamType = overrideParamDetails.params[overrideParamDetails.kwargsIndex!].type; - } - - if ( - !assignType( - targetParamType, - paramInfo.type, - diag?.createAddendum(), - constraints, - AssignTypeFlags.Default - ) - ) { - diag?.addMessage( - LocAddendum.overrideParamKeywordType().format({ - name: paramInfo.param.name ?? '?', - baseType: printType(paramInfo.type), - overrideType: printType(targetParamType), - }) - ); - canOverride = false; - } - - if (overrideParamInfo) { - if (paramInfo.defaultType && !overrideParamInfo.defaultType) { - diag?.addMessage( - LocAddendum.overrideParamKeywordNoDefault().format({ - name: overrideParamInfo.param.name ?? '?', - }) - ); - canOverride = false; - } - } - } - }); - - // Verify that any keyword-only parameters added by the overload are compatible - // with the **kwargs in the base. - overrideKwOnlyParams.forEach((paramInfo) => { - const baseParamInfo = baseKwOnlyParams.find((pi) => paramInfo.param.name === pi.param.name); - - if (!baseParamInfo) { - if (baseParamDetails.kwargsIndex === undefined) { - if (!paramInfo.defaultType) { - diag?.addMessage( - LocAddendum.overrideParamNameExtra().format({ - name: paramInfo.param.name ?? '?', - }) - ); - canOverride = false; - } - } else { - // Base has a **kwargs; ensure the added keyword-only parameter's - // type is compatible with the base's **kwargs value type. - const baseKwargsType = baseParamDetails.params[baseParamDetails.kwargsIndex].type; - if ( - !assignType( - paramInfo.type, - baseKwargsType, - diag?.createAddendum(), - constraints, - AssignTypeFlags.Default - ) - ) { - diag?.addMessage( - LocAddendum.overrideParamKeywordType().format({ - name: paramInfo.param.name ?? '?', - baseType: printType(baseKwargsType), - overrideType: printType(paramInfo.type), - }) - ); - canOverride = false; - } - } - } - }); - - // Verify that if the base method has a **kwargs parameter, the override does too. - if (baseParamDetails.kwargsIndex !== undefined && overrideParamDetails.kwargsIndex === undefined) { - diag?.addMessage( - LocAddendum.kwargsParamMissing().format({ - paramName: baseParamDetails.params[baseParamDetails.kwargsIndex].param.name!, - }) - ); - canOverride = false; - } - } - - // Verify that one or the other method doesn't contain a ParamSpec. - if (baseParamDetails.paramSpec && !overrideParamDetails.paramSpec) { - // If the override uses an `*args: Any, **kwargs: Any` signature, we - // will allow this as an acceptable overload for a `*args: P.args, **kwargs: P.kwargs`. - const overrideHasArgsKwargs = - overrideParamDetails.argsIndex !== undefined && - isAnyOrUnknown(overrideParamDetails.params[overrideParamDetails.argsIndex].type) && - overrideParamDetails.kwargsIndex !== undefined && - isAnyOrUnknown(overrideParamDetails.params[overrideParamDetails.kwargsIndex].type); - - if (!overrideHasArgsKwargs) { - diag?.addMessage(LocAddendum.paramSpecMissingInOverride()); - canOverride = false; - } - } - - // Now check the return type. - const baseReturnType = getEffectiveReturnType(baseMethod); - const overrideReturnType = solveAndApplyConstraints(getEffectiveReturnType(overrideMethod), constraints); - - if ( - !assignType( - baseReturnType, - overrideReturnType, - diag?.createAddendum(), - constraints, - AssignTypeFlags.Default - ) - ) { - diag?.addMessage( - LocAddendum.overrideReturnType().format({ - baseType: printType(baseReturnType), - overrideType: printType(overrideReturnType), - }) - ); - - canOverride = false; - } - - return canOverride; - } - - // Validates that the specified source type matches the constraints - // of the type variable. If successful, it returns the constraint - // type that applies. If unsuccessful, it returns undefined. - function applyTypeArgToTypeVar(destType: TypeVarType, srcType: Type, diag: DiagnosticAddendum): Type | undefined { - if (isAnyOrUnknown(srcType)) { - return srcType; - } - - let effectiveSrcType: Type = transformPossibleRecursiveTypeAlias(srcType); - - if (isTypeVar(srcType)) { - if (isTypeSame(srcType, destType)) { - return srcType; - } - - effectiveSrcType = makeTopLevelTypeVarsConcrete(srcType); - } - - // If this is a partially-evaluated class, don't perform any further - // checks. Assume in this case that the type is compatible with the - // bound or constraint. - if (isClass(effectiveSrcType) && ClassType.isPartiallyEvaluated(effectiveSrcType)) { - return srcType; - } - - // If there's a bound type, make sure the source is derived from it. - if (destType.shared.boundType && !isTypeAliasPlaceholder(effectiveSrcType)) { - if ( - !assignType( - destType.shared.boundType, - effectiveSrcType, - diag.createAddendum(), - /* constraints */ undefined - ) - ) { - // Avoid adding a message that will confuse users if the TypeVar was - // synthesized for internal purposes. - if (!destType.shared.isSynthesized) { - diag.addMessage( - LocAddendum.typeBound().format({ - sourceType: printType(effectiveSrcType), - destType: printType(destType.shared.boundType), - name: TypeVarType.getReadableName(destType), - }) - ); - } - return undefined; - } - } - - if (isParamSpec(destType)) { - if (isParamSpec(srcType)) { - return srcType; - } - - if (isFunction(srcType) && FunctionType.isParamSpecValue(srcType)) { - return srcType; - } - - if (isClassInstance(srcType) && ClassType.isBuiltIn(srcType, 'Concatenate')) { - return srcType; - } - - diag.addMessage( - LocAddendum.typeParamSpec().format({ - type: printType(srcType), - name: TypeVarType.getReadableName(destType), - }) - ); - - return undefined; - } - - if (isParamSpec(srcType)) { - diag.addMessage(LocMessage.paramSpecContext()); - return undefined; - } - - // If there are no constraints, we're done. - const constraints = destType.shared.constraints; - if (constraints.length === 0) { - return srcType; - } - - if (isTypeAliasPlaceholder(srcType)) { - return srcType; - } - - if (isTypeVar(srcType) && TypeVarType.hasConstraints(srcType)) { - // Make sure all the source constraint types map to constraint types in the dest. - if ( - srcType.shared.constraints.every((sourceConstraint) => { - return constraints.some((destConstraint) => assignType(destConstraint, sourceConstraint)); - }) - ) { - return srcType; - } - } else { - let bestConstraintSoFar: Type | undefined; - - // Try to find the best (narrowest) match among the constraints. - for (const constraint of constraints) { - if (assignType(constraint, effectiveSrcType)) { - // Don't allow Never to match unless the constraint is also explicitly Never. - if (!isNever(effectiveSrcType) || isNever(constraint)) { - if (!bestConstraintSoFar || assignType(bestConstraintSoFar, constraint)) { - bestConstraintSoFar = constraint; - } - } - } - } - - if (bestConstraintSoFar) { - return bestConstraintSoFar; - } - } - - diag.addMessage( - LocAddendum.typeConstrainedTypeVar().format({ - type: printType(srcType), - name: TypeVarType.getReadableName(destType), - }) - ); - - return undefined; - } - - // Returns a list of unimplemented abstract symbols (methods or variables) for - // the specified class. - function getAbstractSymbols(classType: ClassType): AbstractSymbol[] { - const symbolTable = new Map(); - - ClassType.getReverseMro(classType).forEach((mroClass) => { - if (isInstantiableClass(mroClass)) { - // See if this class is introducing a new abstract symbol that has not been - // introduced previously or if it is overriding an abstract symbol with - // a non-abstract one. - ClassType.getSymbolTable(mroClass).forEach((symbol, symbolName) => { - const abstractSymbolInfo = getAbstractSymbolInfo(mroClass, symbolName); - - if (abstractSymbolInfo) { - symbolTable.set(symbolName, abstractSymbolInfo); - } else { - symbolTable.delete(symbolName); - } - }); - } - }); - - // Create a final list of symbols that are abstract. - const symbolList: AbstractSymbol[] = []; - symbolTable.forEach((method) => { - symbolList.push(method); - }); - - return symbolList; - } - - // If the memberType is an instance or class method, creates a new - // version of the function that has the "self" or "cls" parameter bound - // to it. If treatConstructorAsClassMethod is true, the function is - // treated like a class method even if it's not marked as such. That's - // needed to special-case the __new__ magic method when it's invoked as - // a constructor (as opposed to by name). - function bindFunctionToClassOrObject( - baseType: ClassType | undefined, - memberType: FunctionType | OverloadedType, - memberClass?: ClassType, - treatConstructorAsClassMethod = false, - selfType?: ClassType | TypeVarType, - diag?: DiagnosticAddendum, - recursionCount = 0 - ): FunctionType | OverloadedType | undefined { - return mapSignatures(memberType, (functionType) => { - // If the caller specified no base type, always strip the - // first parameter. This is used in cases like constructors. - if (!baseType) { - return FunctionType.clone(functionType, /* stripFirstParam */ true); - } - - // If the first parameter was already stripped, it has already been - // bound. Don't attempt to rebind. - if (functionType.priv.strippedFirstParamType) { - return functionType; - } - - if (FunctionType.isInstanceMethod(functionType)) { - // If the baseType is a metaclass, don't specialize the function. - if (isInstantiableMetaclass(baseType)) { - return functionType; - } - - const baseObj: ClassType = isClassInstance(baseType) - ? baseType - : ClassType.cloneAsInstance(specializeWithDefaultTypeArgs(baseType)); - - let stripFirstParam = false; - if (isClassInstance(baseType)) { - stripFirstParam = true; - } else if (memberClass && isInstantiableMetaclass(memberClass)) { - stripFirstParam = true; - } - - return partiallySpecializeBoundMethod( - baseType, - functionType, - diag, - recursionCount, - selfType ?? baseObj, - stripFirstParam - ); - } - - if ( - FunctionType.isClassMethod(functionType) || - (treatConstructorAsClassMethod && FunctionType.isConstructorMethod(functionType)) - ) { - const baseClass = isInstantiableClass(baseType) ? baseType : ClassType.cloneAsInstantiable(baseType); - const clsType = selfType ? (convertToInstantiable(selfType) as ClassType | TypeVarType) : undefined; - - return partiallySpecializeBoundMethod( - baseClass, - functionType, - diag, - recursionCount, - clsType ?? baseClass, - /* stripFirstParam */ true - ); - } - - if (FunctionType.isStaticMethod(functionType)) { - const baseClass = isInstantiableClass(baseType) ? baseType : ClassType.cloneAsInstantiable(baseType); - - return partiallySpecializeBoundMethod( - baseClass, - functionType, - diag, - recursionCount, - /* firstParamType */ undefined, - /* stripFirstParam */ false - ); - } - - return functionType; - }); - } - - // Specializes the specified function for the specified class, - // optionally stripping the first first parameter (the "self" or "cls") - // off of the specialized function in the process. The baseType - // is the type used to reference the member. - function partiallySpecializeBoundMethod( - baseType: ClassType, - memberType: FunctionType, - diag: DiagnosticAddendum | undefined, - recursionCount: number, - firstParamType: ClassType | TypeVarType | undefined, - stripFirstParam = true - ): FunctionType | undefined { - const constraints = new ConstraintTracker(); - - if (firstParamType) { - if (memberType.shared.parameters.length > 0) { - const memberTypeFirstParam = memberType.shared.parameters[0]; - const memberTypeFirstParamType = FunctionType.getParamType(memberType, 0); - - if ( - isTypeVar(memberTypeFirstParamType) && - memberTypeFirstParamType.shared.boundType && - isClassInstance(memberTypeFirstParamType.shared.boundType) && - ClassType.isProtocolClass(memberTypeFirstParamType.shared.boundType) - ) { - // Handle the protocol class specially. Some protocol classes - // contain references to themselves or their subclasses, so if - // we attempt to call assignType, we'll risk infinite recursion. - // Instead, we'll assume it's assignable. - constraints.setBounds( - memberTypeFirstParamType, - TypeBase.isInstantiable(memberTypeFirstParamType) - ? convertToInstance(firstParamType) - : firstParamType - ); - } else { - const subDiag = diag?.createAddendum(); - - // Protect against the case where a callback protocol is being - // bound to its own __call__ method but the first parameter - // is annotated with its own callable type. This can lead to - // infinite recursion. - if (isFunctionOrOverloaded(memberTypeFirstParamType)) { - if (isClassInstance(firstParamType) && ClassType.isProtocolClass(firstParamType)) { - if (subDiag) { - subDiag.addMessage( - LocMessage.bindTypeMismatch().format({ - type: printType(firstParamType), - methodName: memberType.shared.name || '', - paramName: memberTypeFirstParam.name || '__p0', - }) - ); - } - return undefined; - } - } - - if ( - !assignType( - memberTypeFirstParamType, - firstParamType, - subDiag?.createAddendum(), - constraints, - AssignTypeFlags.AllowUnspecifiedTypeArgs, - recursionCount - ) - ) { - if ( - memberTypeFirstParam.name && - !FunctionParam.isNameSynthesized(memberTypeFirstParam) && - FunctionParam.isTypeDeclared(memberTypeFirstParam) - ) { - if (subDiag) { - subDiag.addMessage( - LocMessage.bindTypeMismatch().format({ - type: printType(firstParamType), - methodName: memberType.shared.name || '', - paramName: memberTypeFirstParam.name, - }) - ); - } - return undefined; - } - } - } - } else { - const subDiag = diag?.createAddendum(); - if (subDiag) { - subDiag.addMessage( - LocMessage.bindParamMissing().format({ - methodName: memberType.shared.name || '', - }) - ); - } - return undefined; - } - } - - // Get the effective return type, which will have the side effect of lazily - // evaluating (and caching) the inferred return type if there is no defined return type. - getEffectiveReturnType(memberType); - - const specializedFunction = solveAndApplyConstraints(memberType, constraints); - if (isFunction(specializedFunction)) { - return FunctionType.clone(specializedFunction, stripFirstParam, baseType); - } - - if (isOverloaded(specializedFunction)) { - // For overloaded functions, use the first overload. This isn't - // strictly correct, but this is an extreme edge case. - return FunctionType.clone(OverloadedType.getOverloads(specializedFunction)[0], stripFirstParam, baseType); - } - - return undefined; - } - - function isFinalVariable(symbol: Symbol): boolean { - return symbol.getDeclarations().some((decl) => isFinalVariableDeclaration(decl)); - } - - function isFinalVariableDeclaration(decl: Declaration): boolean { - return decl.type === DeclarationType.Variable && !!decl.isFinal; - } - - function isExplicitTypeAliasDeclaration(decl: Declaration): boolean { - if (decl.type !== DeclarationType.Variable || !decl.typeAnnotationNode) { - return false; - } - - if ( - decl.typeAnnotationNode.nodeType !== ParseNodeType.Name && - decl.typeAnnotationNode.nodeType !== ParseNodeType.MemberAccess && - decl.typeAnnotationNode.nodeType !== ParseNodeType.StringList - ) { - return false; - } - - const fileInfo = AnalyzerNodeInfo.getFileInfo(decl.node); - - // Viper 扩展:t.CTypedef 快速路径。 - // CTypedef: TypeAlias = TypeAlias 在 vpi 中定义为变量, - // getTypeOfAnnotation 对 t.CTypedef 可能返回 Unknown 或非类实例, - // 导致后续检查失败。直接检查 typeAnnotationNode 文本,避免类型求值问题。 - // 这使得 UINT32PTR: t.CTypedef = t.CUInt32T | t.CPtr 被识别为显式类型别名。 - if ( - IsViperDialect(fileInfo.executionEnvironment.dialect) && - decl.typeAnnotationNode.nodeType === ParseNodeType.MemberAccess && - decl.typeAnnotationNode.d.member.d.value === 'CTypedef' - ) { - return true; - } - - const type = getTypeOfAnnotation(decl.typeAnnotationNode, { varTypeAnnotation: true, allowClassVar: true }); - if (!isClassInstance(type)) { - return false; - } - - // 标准类型别名注解:TypeAlias - if (ClassType.isBuiltIn(type, 'TypeAlias')) { - return true; - } - - // Viper 扩展:t.CTypedef 是 TypeAlias 的别名(CTypedef: TypeAlias = TypeAlias)。 - // 由于 t.CTypedef 来自 vipershed-fallback,没有 BuiltIn 标志, - // isBuiltIn(type, 'TypeAlias') 会返回 false。因此需要额外检查类型名称。 - // 这使得 irq_handler_t: t.CTypedef = t.Callable[[], t.CInt] 被识别为显式类型别名。 - if ( - IsViperDialect(fileInfo.executionEnvironment.dialect) && - (type.shared.name === 'CTypedef' || type.shared.fullName === 't.CTypedef') - ) { - return true; - } - - return false; - } - - function isPossibleTypeAliasDeclaration(decl: Declaration): boolean { - if (decl.type !== DeclarationType.Variable || !decl.typeAliasName || decl.typeAnnotationNode) { - return false; - } - - if (decl.node.parent?.nodeType !== ParseNodeType.Assignment) { - return false; - } - - // Perform a sanity check on the RHS expression. Some expression - // forms should never be considered legitimate for type aliases. - return isLegalTypeAliasExpressionForm(decl.node.parent.d.rightExpr, /* allowStrLiteral */ false); - } - - function isLegalTypeAliasExpressionForm(node: ExpressionNode, allowStrLiteral: boolean): boolean { - switch (node.nodeType) { - case ParseNodeType.Error: - case ParseNodeType.UnaryOperation: - case ParseNodeType.AssignmentExpression: - case ParseNodeType.TypeAnnotation: - case ParseNodeType.Await: - case ParseNodeType.Ternary: - case ParseNodeType.Unpack: - case ParseNodeType.Tuple: - case ParseNodeType.Call: - case ParseNodeType.Comprehension: - case ParseNodeType.Slice: - case ParseNodeType.Yield: - case ParseNodeType.YieldFrom: - case ParseNodeType.Lambda: - case ParseNodeType.Number: - case ParseNodeType.Dictionary: - case ParseNodeType.List: - case ParseNodeType.Set: - return false; - - case ParseNodeType.StringList: - case ParseNodeType.String: - return allowStrLiteral; - - case ParseNodeType.Constant: - return node.d.constType === KeywordType.None; - - case ParseNodeType.BinaryOperation: - return ( - node.d.operator === OperatorType.BitwiseOr && - isLegalTypeAliasExpressionForm(node.d.leftExpr, /* allowStrLiteral */ true) && - isLegalTypeAliasExpressionForm(node.d.rightExpr, /* allowStrLiteral */ true) - ); - - case ParseNodeType.Index: - return isLegalTypeAliasExpressionForm(node.d.leftExpr, allowStrLiteral); - - case ParseNodeType.MemberAccess: - return isLegalTypeAliasExpressionForm(node.d.leftExpr, allowStrLiteral); - } - - return true; - } - - function isLegalImplicitTypeAliasType(type: Type) { - // We explicitly exclude "..." and "Unknown". - if (isEllipsisType(type)) { - return false; - } - - if (isUnknown(type)) { - // If this is a union type, we'll assume that it was meant as a type - // alias even though all of the union subtypes are Unknown. - if (type.props?.specialForm && ClassType.isBuiltIn(type.props.specialForm, 'UnionType')) { - return true; - } - return false; - } - - // Look at the subtypes within the union. If any of them are not - // instantiable (other than "None" which is special-cased), it is - // not a legal type alias type. - let isLegal = true; - doForEachSubtype(type, (subtype) => { - if (!TypeBase.isInstantiable(subtype) && !isNoneInstance(subtype)) { - isLegal = false; - } - }); - - return isLegal; - } - - function isPossibleTypeAliasOrTypedDict(decl: Declaration) { - return isPossibleTypeAliasDeclaration(decl) || isPossibleTypeDictFactoryCall(decl); - } - - function isPossibleTypeDictFactoryCall(decl: Declaration) { - if ( - decl.type !== DeclarationType.Variable || - !decl.node.parent || - decl.node.parent.nodeType !== ParseNodeType.Assignment || - decl.node.parent.d.rightExpr?.nodeType !== ParseNodeType.Call - ) { - return false; - } - - const callLeftNode = decl.node.parent.d.rightExpr.d.leftExpr; - - // Use a simple heuristic to determine whether this is potentially - // a call to the TypedDict call. This avoids the expensive (and potentially - // recursive) call to getTypeOfExpression in cases where it's not needed. - if ( - (callLeftNode.nodeType === ParseNodeType.Name && callLeftNode.d.value) === 'TypedDict' || - (callLeftNode.nodeType === ParseNodeType.MemberAccess && - callLeftNode.d.member.d.value === 'TypedDict' && - callLeftNode.d.leftExpr.nodeType === ParseNodeType.Name) - ) { - // See if this is a call to TypedDict. We want to support - // recursive type references in a TypedDict call. - const callType = getTypeOfExpression(callLeftNode, EvalFlags.CallBaseDefaults).type; - - if (isInstantiableClass(callType) && ClassType.isBuiltIn(callType, 'TypedDict')) { - return true; - } - } - - return false; - } - - function printObjectTypeForClass(type: ClassType): string { - return TypePrinter.printObjectTypeForClass(type, evaluatorOptions.printTypeFlags, getEffectiveReturnType); - } - - function printFunctionParts(type: FunctionType, extraFlags?: TypePrinter.PrintTypeFlags): [string[], string] { - const flags = extraFlags ? evaluatorOptions.printTypeFlags | extraFlags : evaluatorOptions.printTypeFlags; - return TypePrinter.printFunctionParts(type, flags, getEffectiveReturnType); - } - - // Prints two types and determines whether they need to be output in - // fully-qualified form for disambiguation. - function printSrcDestTypes( - srcType: Type, - destType: Type, - options?: PrintTypeOptions - ): { sourceType: string; destType: string } { - const simpleSrcType = printType(srcType, options); - const simpleDestType = printType(destType, options); - - if (simpleSrcType !== simpleDestType) { - return { sourceType: simpleSrcType, destType: simpleDestType }; - } - - const fullSrcType = printType(srcType, { ...(options ?? {}), useFullyQualifiedNames: true }); - const fullDestType = printType(destType, { ...(options ?? {}), useFullyQualifiedNames: true }); - - if (fullSrcType !== fullDestType) { - return { sourceType: fullSrcType, destType: fullDestType }; - } - - return { sourceType: simpleSrcType, destType: simpleDestType }; - } - - function printType(type: Type, options?: PrintTypeOptions): string { - let flags = evaluatorOptions.printTypeFlags; - - if (options?.expandTypeAlias) { - flags |= TypePrinter.PrintTypeFlags.ExpandTypeAlias; - } - if (options?.enforcePythonSyntax) { - flags |= TypePrinter.PrintTypeFlags.PythonSyntax; - } - if (options?.useTypingUnpack) { - flags |= TypePrinter.PrintTypeFlags.UseTypingUnpack; - } - if (options?.printUnknownWithAny) { - flags |= TypePrinter.PrintTypeFlags.PrintUnknownWithAny; - } - if (options?.printTypeVarVariance) { - flags |= TypePrinter.PrintTypeFlags.PrintTypeVarVariance; - } - if (options?.omitTypeArgsIfUnknown) { - flags |= TypePrinter.PrintTypeFlags.OmitTypeArgsIfUnknown; - } - if (options?.useFullyQualifiedNames) { - flags |= TypePrinter.PrintTypeFlags.UseFullyQualifiedNames; - } - if (options?.disablePep604) { - flags &= ~TypePrinter.PrintTypeFlags.PEP604; - } - - return TypePrinter.printType(type, flags, getEffectiveReturnType); - } - - // Calls back into the parser to parse the contents of a string literal. - // This is unfortunately needed in some cases — specifically where the - // parser couldn't determine that the string literal would be used in - // a context where it should be treated as a forward-declared type. This - // call produces an expression tree that is not attached to the main parse - // expression tree because we don't want to mutate the latter; the - // expression tree created by this function is therefore used only temporarily. - function parseStringAsTypeAnnotation(node: StringListNode, reportErrors: boolean): ExpressionNode | undefined { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - const parser = new Parser(); - const textValue = node.d.strings[0].d.value; - - // Determine the offset within the file where the string - // literal's contents begin. - let valueOffset = node.d.strings[0].start; - if (node.d.strings[0].nodeType === ParseNodeType.String) { - valueOffset += node.d.strings[0].d.token.prefixLength + node.d.strings[0].d.token.quoteMarkLength; - } - - // Construct a temporary dummy string with the text value at the appropriate - // offset so as to mimic the original file. This will keep all of the token - // and diagnostic offsets correct. - const dummyFileContents = ' '.repeat(valueOffset) + textValue; - - const parseOptions = new ParseOptions(); - parseOptions.isStubFile = fileInfo.isStubFile; - parseOptions.pythonVersion = fileInfo.executionEnvironment.pythonVersion; - parseOptions.dialect = fileInfo.executionEnvironment.dialect; - parseOptions.reportErrorsForParsedStringContents = true; - - const parseResults = parser.parseTextExpression( - dummyFileContents, - valueOffset, - textValue.length, - parseOptions, - ParseTextMode.Expression, - /* initialParenDepth */ undefined, - fileInfo.typingSymbolAliases - ); - - if (parseResults.parseTree) { - // If there are errors but we are not reporting them, return - // undefined to indicate that the parse failed. - if (!reportErrors && parseResults.diagnostics.length > 0) { - return undefined; - } - - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - parseResults.diagnostics.forEach((diag) => { - fileInfo.diagnosticSink.addDiagnosticWithTextRange('error', diag.message, node); - }); - - parseResults.parseTree.parent = node; - - // Optionally add the new subtree to the parse tree so it can - // participate in language server operations like find and replace. - if (reportErrors) { - node.d.annotation = parseResults.parseTree; - } - - return parseResults.parseTree; - } - - return undefined; - } - - // Given a code flow node and a constrained TypeVar, determines whether that type - // var can be "narrowed" to a single one of its constraints based on isinstance - // checks within the code flow. - function narrowConstrainedTypeVar(node: ParseNode, typeVar: TypeVarType): Type | undefined { - const flowNode = AnalyzerNodeInfo.getFlowNode(node); - - if (!flowNode) { - return undefined; - } - - return codeFlowEngine.narrowConstrainedTypeVar(flowNode, typeVar); - } - - function getPrintExpressionTypesSpaces() { - return ' '.repeat(printExpressionSpaceCount); - } - - function getLineNum(node: ParseNode) { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - const range = convertOffsetsToRange(node.start, node.start + node.length, fileInfo.lines); - return (range.start.line + 1).toString(); - } - - function printControlFlowGraph( - flowNode: FlowNode, - reference: CodeFlowReferenceExpressionNode | undefined, - callName: string, - logger: ConsoleInterface - ) { - return codeFlowEngine.printControlFlowGraph(flowNode, reference, callName, logger); - } - - // Track these apis internal usages when logging is on. otherwise, it should be noop. - const getInferredReturnTypeResult = wrapWithLogger(_getInferredReturnTypeResult); - - const evaluatorInterface: TypeEvaluator = { - runWithCancellationToken, - getType, - getTypeResult, - getTypeResultForDecorator, - getCachedType, - getTypeOfExpression, - getTypeOfAnnotation, - getTypeOfClass, - createSubclass, - getTypeOfFunction, - getTypeOfExpressionExpectingType, - getExpectedType, - evaluateTypeForSubnode, - evaluateTypesForStatement, - evaluateTypesForMatchStatement, - evaluateTypesForCaseStatement, - evaluateTypeOfParam, - canBeTruthy, - canBeFalsy, - stripLiteralValue, - removeTruthinessFromType, - removeFalsinessFromType, - stripTypeGuard, - solveAndApplyConstraints, - verifyRaiseExceptionType, - verifyDeleteExpression, - validateOverloadedArgTypes, - validateInitSubclassArgs, - isNodeReachable, - isAfterNodeReachable, - getNodeReachability, - getAfterNodeReachability, - isAsymmetricAccessorAssignment, - suppressDiagnostics, - isSpecialFormClass, - getDeclInfoForStringNode, - getDeclInfoForNameNode, - getTypeForDeclaration, - resolveAliasDeclaration, - resolveAliasDeclarationWithInfo, - getTypeOfIterable, - getTypeOfIterator, - getGetterTypeFromProperty, - getTypeOfArg, - convertNodeToArg, - buildTupleTypesList, - markNamesAccessed, - expandPromotionTypes, - makeTopLevelTypeVarsConcrete, - mapSubtypesExpandTypeVars, - isTypeSubsumedByOtherType, - lookUpSymbolRecursive, - getDeclaredTypeOfSymbol, - getEffectiveTypeOfSymbol, - getEffectiveTypeOfSymbolForUsage, - getInferredTypeOfDeclaration, - getDeclaredTypeForExpression, - getDeclaredReturnType, - getInferredReturnType, - getBestOverloadForArgs, - getBuiltInType, - getTypeOfMember, - getTypeOfBoundMember, - getBoundMagicMethod, - getTypeOfMagicMethodCall, - bindFunctionToClassOrObject, - getCallbackProtocolType, - getCallSignatureInfo, - getAbstractSymbols, - narrowConstrainedTypeVar, - isTypeComparable, - assignType, - validateOverrideMethod, - validateCallArgs, - validateTypeArg, - assignTypeToExpression, - assignClassToSelf, - getTypedDictClassType, - getTupleClassType, - getDictClassType, - getStrClassType, - getObjectType, - getNoneType, - getUnionClassType, - getTypeClassType, - getBuiltInObject, - getTypingType, - getTypeCheckerInternalsType, - assignTypeArgs, - reportMissingTypeArgs, - inferReturnTypeIfNecessary, - inferVarianceForClass, - isFinalVariable, - isFinalVariableDeclaration, - isExplicitTypeAliasDeclaration, - addInformation, - addUnreachableCode, - addDeprecated, - addDiagnostic, - addDiagnosticForTextRange, - printType, - printSrcDestTypes, - printFunctionParts, - getTypeCacheEntryCount, - disposeEvaluator, - useSpeculativeMode, - isSpeculativeModeInUse, - setTypeResultForNode, - checkForCancellation, - printControlFlowGraph, - }; - - const codeFlowEngine = getCodeFlowEngine(evaluatorInterface, speculativeTypeTracker); - - return evaluatorInterface; -} + // Viper 扩展:TypeSet 类型组合赋值语义。 + // 在 Viper 中,`|` 表示类型组合(TypeSet),而非 Python 的联合类型。 + // TypeSet 中的每个子类型都是"同一值的不同视角",因此: + // - 如果 srcType 是 TypeSet(联合类型且包含 Viper 修饰类型), + // 且 destType 可以赋值给 TypeSet 中的某个子类型,则允许赋值。 + // 例如:get_block_size() -> CStatic | CUInt64T,可赋值给 CUInt64T 变量。 + if (isUnion(srcType)) { + const srcSubtypes = srcType.priv.subtypes; + const hasViperModifier = srcSubtypes.some( + (subtype) => + isClassInstance(subtype) && + (isCModifierType(subtype) || + isCPtrType(subtype) || + isCVoidType(subtype) || + isCArrayType(subtype)) + ); + if (hasViperModifier) { + // 检查 destType 是否可以赋值给 TypeSet 中的某个子类型 + for (const subtype of srcSubtypes) { + if (assignType(destType, subtype, undefined, constraints, flags, recursionCount + 1)) { + return true; + } + } + + // Viper 扩展:CPtr[T] 目标类型与包含 CPtr 的 TypeSet 的兼容。 + // 当 destType 是 CPtr[T] 且 srcType 是包含 CPtr 的 TypeSet 时, + // 允许赋值(如 volinfo | CPtr → CPtr[volinfo])。 + // 这是因为 Viper 中 T | CPtr 表示 T*(指向 T 的指针),等价于 CPtr[T]。 + if (isClassInstance(destType) && isCPtrType(destType)) { + return true; + } + } + } + + // Viper 扩展:Self 类型 → CPtr[T] 隐式取地址语义。 + // 在 Viper 中,结构体方法的 self 参数(Self@T)本身就是 CPtr[T](指针), + // 因此 Self@volinfo 可以赋值给 CPtr[volinfo]。 + // 仅对 Self 类型允许,不对普通结构体实例做隐式取地址。 + if ( + isClassInstance(destType) && isCPtrType(destType) && + destType.priv.typeArgs && destType.priv.typeArgs.length > 0 && + isTypeVar(srcType) && TypeVarType.isSelf(srcType) + ) { + return true; + } + + // Viper 扩展:类实例 → CPtr[T] 隐式取地址语义(堆对象即指针)。 + // Viper 中 `T | t.CPtr` 注解表示 T*(指向 T 的指针),构造调用返回的是 + // 堆上分配的类实例(如 `HashTable(mb)` 返回 HashTable 实例)。 + // 允许类实例赋值给 CPtr[同类](相当于 C 的 `HashTable* p = &obj`,Viper + // 中实例本身就是指针),覆盖场景: + // self._ht: hashtable.HashTable | t.CPtr = hashtable.HashTable(mb) + // 仅当 CPtr 的目标类型是源类实例的同类/基类时允许(保证指针类型安全)。 + if ( + isClassInstance(destType) && isCPtrType(destType) && + destType.priv.typeArgs && destType.priv.typeArgs.length > 0 && + isClassInstance(srcType) + ) { + const pointeeType = destType.priv.typeArgs[0]; + // 目标可以是实例或可实例化形式,统一转成可比较形式。 + let pointeeAsClass: ClassType | undefined; + if (isClassInstance(pointeeType)) { + pointeeAsClass = pointeeType; + } else if (isInstantiableClass(pointeeType)) { + const converted = convertToInstance(pointeeType); + pointeeAsClass = isClassInstance(converted) ? converted : undefined; + } + if (pointeeAsClass) { + const inheritanceChain: InheritanceChain = []; + const isDerivedFrom = ClassType.isDerivedFrom( + srcType, + pointeeAsClass, + inheritanceChain + ); + if (isDerivedFrom) { + return true; + } + } + } + + // Viper 扩展:字符串字面量 → int(CChar)转换语义。 + // C 语言中 char c = '_' 合法(字符字面量是整数)。Viper 中 CChar 是 int 别名, + // 但 '_' 是字符串字面量。允许字符串字面量赋值给 int,模拟 C 的字符赋值。 + // Python 中 str 永远不能赋值给 int,所以此扩展只在 Viper 场景生效。 + if ( + isClassInstance(destType) && ClassType.isBuiltIn(destType, 'int') && + isClassInstance(srcType) && ClassType.isBuiltIn(srcType, 'str') && + srcType.priv.literalValue !== undefined && + typeof srcType.priv.literalValue === 'string' + ) { + return true; + } + + // Viper 扩展:CEnum 子类 → int 隐式转换语义。 + // C 语言中 enum 值可以隐式转换为 int(如 int x = FR_OK)。 + // Viper 的 CEnum 子类(如 FRESULT)的成员(如 FRESULT.FR_OK) + // 可以赋值给 int 类型的变量/字段。 + if ( + isClassInstance(destType) && ClassType.isBuiltIn(destType, 'int') && + isClassInstance(srcType) && isCEnumSubclassType(srcType) + ) { + return true; + } + + // Viper 扩展:Python 内置类型 → CType 子类隐式转换。 + // Viper 中所有 C 类型(CInt, CFloat, CChar 等)都是 CType 的子类, + // 在底层等价于 Python 内置类型(int, float, str 等)。 + // 允许 `k: t.CInt = 1`(int → CInt)、`k: t.CFloat = 1.0`(float → CFloat)等赋值。 + if ( + isClassInstance(destType) && isCTypeSubclassType(destType) && + isClassInstance(srcType) && ClassType.isBuiltIn(srcType, ['int', 'float', 'str', 'bool', 'bytes']) + ) { + return true; + } + + // Viper 扩展:CType 子类 -> Python 内置类型隐式转换(反向)。 + // CInt/CFloat/CChar 等 CType 子类在底层等价于 Python 内置类型, + // 可隐式转换回 int/float/str/bool/bytes。 + // 覆盖场景:CInt 值传给 int 形参(如 def __init__(self, w: int) 接收 CInt 实参)。 + if ( + isClassInstance(destType) && ClassType.isBuiltIn(destType, ['int', 'float', 'str', 'bool', 'bytes']) && + isClassInstance(srcType) && isCTypeSubclassType(srcType) + ) { + return true; + } + + // Viper 扩展:CType 子类 -> CType 子类隐式转换。 + // 按 C 语言隐式转换规则判断(canImplicitlyConvertCType): + // - 整数/浮点:允许所有方向(高向低可安全降级,低向高无损) + // - 整数 -> 浮点:浮点位宽 >= 整数位宽时允许(无损) + // - 浮点 -> 整数:不允许(有损) + // - 同级指针(都是 CPtr 子类):允许不同类型转换 + // - 不同级(一个是指针,另一个不是):不允许 + // 覆盖场景:CUInt64T 传给 CInt 形参(高向低降级,允许)。 + if ( + isClassInstance(srcType) && isCTypeSubclassType(srcType) && + isClassInstance(destType) && isCTypeSubclassType(destType) + ) { + return canImplicitlyConvertCType(srcType, destType); + } + + // Viper 扩展:函数类型 → Callable 类实例赋值。 + // 当类型别名通过 t.CTypedef(=TypeAlias)声明时,如 + // irq_handler_t: t.CTypedef = t.Callable[[], t.CInt] + // 在某些场景下 irq_handler_t 可能被解析为 Callable 类实例而非 + // FunctionType。此时允许将函数类型赋值给 Callable 类实例。 + if ( + isClassInstance(destType) && ClassType.isBuiltIn(destType, 'Callable') && + isFunction(srcType) + ) { + return true; + } + + // Handle the case where the dest and src types are the same object. + // We can normally shortcut this and say that they are compatible, + // but if the type includes TypeVars, we need to go through + // the rest of the logic. + if (destType === srcType && !requiresSpecialization(destType)) { + return true; + } + + // If the source type is a special form, use the literal special form + // class rather than the symbolic form. + const specialForm = srcType.props?.specialForm; + if (specialForm) { + let isSpecialFormExempt = false; + + // A few special forms that are normally not compatible with type[T] + // are compatible specifically in the context of isinstance and issubclass. + if ((flags & AssignTypeFlags.AllowIsinstanceSpecialForms) !== 0) { + if (ClassType.isBuiltIn(specialForm, ['Callable', 'UnionType', 'Generic'])) { + isSpecialFormExempt = true; + } + } + + if (!isSpecialFormExempt) { + if (srcType.props?.typeForm && !specialForm.props?.typeForm) { + srcType = TypeBase.cloneWithTypeForm(specialForm, srcType.props.typeForm); + } else { + srcType = specialForm; + } + } + } + + // If the source is a class-like type created by a call to NewType, treat it + // as a FunctionClass instance rather than an instantiable class for + // purposes of assignability. This reflects its actual runtime type. + if (isInstantiableClass(srcType) && ClassType.isNewTypeClass(srcType) && !srcType.priv.includeSubclasses) { + if (prefetched?.functionClass && isInstantiableClass(prefetched?.functionClass)) { + srcType = ClassType.cloneAsInstance(prefetched.functionClass); + } + } + + if (recursionCount > maxTypeRecursionCount) { + return true; + } + recursionCount++; + + // If the source and dest refer to the recursive type aliases, handle + // the case specially to avoid recursing down both type aliases. + if ( + isTypeVar(destType) && + destType.shared.recursiveAlias && + isTypeVar(srcType) && + srcType.shared.recursiveAlias + ) { + const destAliasInfo = destType.props?.typeAliasInfo; + const srcAliasInfo = srcType.props?.typeAliasInfo; + + // Do the source and dest refer to the same recursive type alias? + if ( + destAliasInfo?.typeArgs && + srcAliasInfo?.typeArgs && + destType.shared.recursiveAlias.typeVarScopeId === srcType.shared.recursiveAlias.typeVarScopeId + ) { + return assignRecursiveTypeAliasToSelf( + destAliasInfo, + srcAliasInfo, + diag, + constraints, + flags, + recursionCount + ); + } else { + // Have we already recursed once? + if ((flags & AssignTypeFlags.SkipRecursiveTypeCheck) !== 0) { + return true; + } + + // Note that we are comparing two recursive types and do + // not recursive more than once. + flags |= AssignTypeFlags.SkipRecursiveTypeCheck; + } + } + + // If one or both of the types has an instantiable depth greater than + // zero, convert both to instances first. + if (TypeBase.isInstantiable(destType) && TypeBase.isInstantiable(srcType)) { + if (TypeBase.getInstantiableDepth(destType) > 0 || TypeBase.getInstantiableDepth(srcType) > 0) { + return assignType( + convertToInstance(destType), + convertToInstance(srcType), + diag, + constraints, + flags, + recursionCount + ); + } + } + + // Transform recursive type aliases if necessary. + const transformedDestType = transformPossibleRecursiveTypeAlias(destType); + const transformedSrcType = transformPossibleRecursiveTypeAlias(srcType); + + // Did either the source or dest include recursive type aliases? + // If so, we could be dealing with different recursive type aliases + // or a recursive type alias and a recursive protocol definition. + if ( + (transformedDestType !== destType && isUnion(transformedDestType)) || + (transformedSrcType !== srcType && isUnion(transformedSrcType)) + ) { + // Use a smaller recursive limit in this case to prevent runaway recursion. + if (recursionCount > maxRecursiveTypeAliasRecursionCount) { + // Add a special case for when the source is a str, which is itself + // a recursive type (since it derives from Sequence[str]). + if (isClassInstance(srcType) && ClassType.isBuiltIn(srcType, 'str') && isUnion(transformedDestType)) { + return transformedDestType.priv.subtypes.some( + (subtype) => isClassInstance(subtype) && ClassType.isBuiltIn(subtype, ['object', 'str']) + ); + } + return true; + } + } + + destType = transformedDestType; + srcType = transformedSrcType; + + // If the source or dest is unbound, allow the assignment. The + // error will be reported elsewhere. + if (isUnbound(destType) || isUnbound(srcType)) { + return true; + } + + if (isTypeVar(destType)) { + if (isTypeVarSame(destType, srcType)) { + return true; + } + + // If the dest is a constrained or bound type variable and all of the + // types in the source are conditioned on that same type variable + // and have compatible types, we'll consider it assignable. + if (assignConditionalTypeToTypeVar(destType, srcType, recursionCount)) { + return true; + } + + // If the source is a conditional type associated with a bound TypeVar + // and the bound TypeVar matches the condition, the types are compatible. + const destTypeVar = destType; + if ( + TypeBase.isInstantiable(destType) === TypeBase.isInstantiable(srcType) && + srcType.props?.condition && + srcType.props.condition.some((cond) => { + return ( + !TypeVarType.hasConstraints(cond.typeVar) && + cond.typeVar.priv.nameWithScope === destTypeVar.priv.nameWithScope + ); + }) + ) { + return true; + } + + if (isUnion(srcType)) { + const srcWithoutAny = removeFromUnion(srcType, (type) => isAnyOrUnknown(type)); + if (isTypeSame(destType, srcWithoutAny)) { + return true; + } + } + + // Handle the special case where both types are Self types. We'll allow + // them to be treated as equivalent to handle certain common idioms. + if ( + isTypeVar(srcType) && + TypeVarType.isSelf(srcType) && + TypeVarType.hasBound(srcType) && + TypeVarType.isSelf(destType) && + TypeVarType.hasBound(destType) && + TypeVarType.isBound(destType) === TypeVarType.isBound(srcType) && + TypeBase.isInstance(srcType) === TypeBase.isInstance(destType) + ) { + if ((flags & AssignTypeFlags.Contravariant) === 0 && constraints) { + assignTypeVar(evaluatorInterface, destType, srcType, diag, constraints, flags, recursionCount); + } + return true; + } + + // If the dest is a TypeVarTuple, and the source is a tuple + // with a single entry that is the same TypeVarTuple, it's a match. + if ( + isTypeVarTuple(destType) && + isClassInstance(srcType) && + isTupleClass(srcType) && + srcType.priv.tupleTypeArgs && + srcType.priv.tupleTypeArgs.length === 1 + ) { + if (isTypeSame(destType, srcType.priv.tupleTypeArgs[0].type, {}, recursionCount)) { + return true; + } + } + + if ((flags & AssignTypeFlags.Contravariant) === 0 || !isTypeVar(srcType)) { + if (!assignTypeVar(evaluatorInterface, destType, srcType, diag, constraints, flags, recursionCount)) { + return false; + } + + if (isAnyOrUnknown(srcType) && (flags & AssignTypeFlags.OverloadOverlap) !== 0) { + return false; + } + + return true; + } + } + + if (isTypeVar(srcType)) { + if ((flags & AssignTypeFlags.Contravariant) !== 0) { + if (TypeVarType.isBound(srcType)) { + return assignType( + makeTopLevelTypeVarsConcrete(destType), + makeTopLevelTypeVarsConcrete(srcType), + diag, + /* constraints */ undefined, + flags, + recursionCount + ); + } + + if (assignTypeVar(evaluatorInterface, srcType, destType, diag, constraints, flags, recursionCount)) { + return true; + } + + // If the dest type is a union, only one of the subtypes needs to match. + let isAssignable = false; + if (isUnion(destType)) { + doForEachSubtype(destType, (destSubtype) => { + if ( + assignTypeVar( + evaluatorInterface, + srcType as TypeVarType, + destSubtype, + diag, + constraints, + flags, + recursionCount + ) + ) { + isAssignable = true; + } + }); + } + return isAssignable; + } + + if ((flags & AssignTypeFlags.Invariant) !== 0) { + if (isAnyOrUnknown(destType)) { + return true; + } + + // If the source is a ParamSpec and the dest is a "...", this is + // effectively like an "Any" signature, so we'll treat it as though + // it's Any. + if ( + isParamSpec(srcType) && + isFunction(destType) && + FunctionType.isGradualCallableForm(destType) && + destType.shared.parameters.length <= 2 + ) { + return true; + } + + // If the source is an unpacked TypeVarTuple and the dest is a + // *tuple[Any, ...], we'll treat it as compatible. + if ( + isUnpackedTypeVarTuple(srcType) && + isClassInstance(destType) && + isUnpackedClass(destType) && + destType.priv.tupleTypeArgs && + destType.priv.tupleTypeArgs.length === 1 && + destType.priv.tupleTypeArgs[0].isUnbounded && + isAnyOrUnknown(destType.priv.tupleTypeArgs[0].type) + ) { + return true; + } + + if (!isUnion(destType)) { + diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType))); + return false; + } + } + } + + if (isAnyOrUnknown(destType)) { + return true; + } + + if (isAnyOrUnknown(srcType) && !srcType.props?.specialForm) { + if (constraints) { + // If it's an ellipsis type, convert it to a regular "Any" + // type. These are functionally equivalent, but "Any" looks + // better in the text representation. + const typeVarSubstitution = isEllipsisType(srcType) ? AnyType.create() : srcType; + setConstraintsForFreeTypeVars(destType, typeVarSubstitution, constraints); + } + if ((flags & AssignTypeFlags.OverloadOverlap) === 0) { + return true; + } + } + + if (isNever(srcType)) { + if ((flags & AssignTypeFlags.Invariant) !== 0) { + if (isNever(destType)) { + return true; + } + + diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType))); + return false; + } + + if (constraints) { + setConstraintsForFreeTypeVars(destType, UnknownType.create(), constraints); + } + return true; + } + + if (isUnion(destType)) { + // If both the source and dest are unions, use assignFromUnionType which has + // special-case logic to handle this case. + if (isUnion(srcType)) { + return assignFromUnionType(destType, srcType, diag, constraints, flags, recursionCount); + } + + const clonedConstraints = constraints?.clone(); + if (assignToUnionType(destType, srcType, /* diag */ undefined, clonedConstraints, flags, recursionCount)) { + if (constraints && clonedConstraints) { + constraints.copyFromClone(clonedConstraints); + } + return true; + } + } + + const expandedSrcType = makeTopLevelTypeVarsConcrete(srcType); + if (isUnion(expandedSrcType)) { + return assignFromUnionType(destType, expandedSrcType, diag, constraints, flags, recursionCount); + } + + if (isUnion(destType)) { + return assignToUnionType(destType, srcType, diag, constraints, flags, recursionCount); + } + + // Is the src a specialized "type" object? + if (isClassInstance(expandedSrcType) && ClassType.isBuiltIn(expandedSrcType, 'type')) { + const srcTypeArgs = expandedSrcType.priv.typeArgs; + let typeTypeArg: Type; + + if (srcTypeArgs && srcTypeArgs.length >= 1) { + typeTypeArg = srcTypeArgs[0]; + } else { + typeTypeArg = UnknownType.create(); + } + + if (isAnyOrUnknown(typeTypeArg)) { + if (isEffectivelyInstantiable(destType)) { + return true; + } + } else if (isClassInstance(typeTypeArg) || isTypeVar(typeTypeArg)) { + if ( + assignType( + destType, + convertToInstantiable(typeTypeArg), + diag?.createAddendum(), + constraints, + flags, + recursionCount + ) + ) { + return true; + } + + diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType))); + return false; + } + } + + if (isInstantiableClass(destType)) { + if (isInstantiableClass(expandedSrcType)) { + // PEP 544 says that if the dest type is a type[Proto] class, + // the source must be a "concrete" (non-protocol) class. + if (ClassType.isProtocolClass(destType)) { + if ( + (flags & AssignTypeFlags.AllowProtocolClassSource) === 0 && + ClassType.isProtocolClass(expandedSrcType) && + isInstantiableClass(srcType) && + !srcType.priv.includeSubclasses + ) { + diag?.addMessage( + LocAddendum.protocolSourceIsNotConcrete().format({ + sourceType: printType(convertToInstance(srcType)), + destType: printType(destType), + }) + ); + return false; + } + } + + if (ClassType.isBuiltIn(destType, 'type') && (srcType.props?.instantiableDepth ?? 0) > 0) { + return true; + } + + if (isSpecialFormClass(expandedSrcType, flags)) { + // Special form classes are compatible only with other special form + // classes, not with 'object' or 'type'. + const destSpecialForm = destType.props?.specialForm ?? destType; + if (isSpecialFormClass(destSpecialForm, flags)) { + return assignType(destSpecialForm, expandedSrcType, diag, constraints, flags, recursionCount); + } + } else if ( + assignClass( + destType, + expandedSrcType, + diag, + constraints, + flags, + recursionCount, + /* reportErrorsUsingObjType */ false + ) + ) { + return true; + } + + diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType))); + return false; + } + } + + if (isClassInstance(destType)) { + if (ClassType.isBuiltIn(destType, 'type')) { + if ( + isInstantiableClass(srcType) && + isSpecialFormClass(srcType, flags) && + TypeBase.getInstantiableDepth(srcType) === 0 + ) { + return false; + } + + if (isAnyOrUnknown(srcType) && (flags & AssignTypeFlags.OverloadOverlap) !== 0) { + return false; + } + + const destTypeArgs = destType.priv.typeArgs; + if (destTypeArgs && destTypeArgs.length >= 1) { + if (TypeBase.isInstance(destTypeArgs[0]) && TypeBase.isInstantiable(srcType)) { + return assignType( + destTypeArgs[0], + convertToInstance(srcType), + diag, + constraints, + flags, + recursionCount + ); + } + } + + // Is the dest a "type" object? Assume that all instantiable + // types are assignable to "type". + if (TypeBase.isInstantiable(srcType)) { + const isLiteral = isClass(srcType) && srcType.priv.literalValue !== undefined; + return !isLiteral; + } + } + + let concreteSrcType = makeTopLevelTypeVarsConcrete(srcType); + + // Handle the TypeForm special form. Add a special case for + // type[T] to be assignable to TypeForm[T]. + if (ClassType.isBuiltIn(destType, 'TypeForm')) { + const destTypeArg = + destType.priv.typeArgs && destType.priv.typeArgs.length > 0 + ? destType.priv.typeArgs[0] + : UnknownType.create(); + + let srcTypeArg: Type | undefined; + if (isClassInstance(concreteSrcType) && ClassType.isBuiltIn(concreteSrcType, 'type')) { + srcTypeArg = concreteSrcType; + } else if (isInstantiableClass(concreteSrcType)) { + srcTypeArg = convertToInstance(concreteSrcType); + } + + if (srcTypeArg) { + return assignType(destTypeArg, srcTypeArg, diag, constraints, flags, recursionCount); + } + } + + if (isClass(concreteSrcType) && TypeBase.isInstance(concreteSrcType)) { + // Handle the case where the source is an unpacked tuple. + if ( + !destType.priv.isUnpacked && + concreteSrcType.priv.isUnpacked && + concreteSrcType.priv.tupleTypeArgs + ) { + return assignType( + destType, + combineTupleTypeArgs(concreteSrcType.priv.tupleTypeArgs), + diag, + constraints, + flags, + recursionCount + ); + } + + if ( + destType.priv.literalValue !== undefined && + ClassType.isSameGenericClass(destType, concreteSrcType) + ) { + const srcLiteral = concreteSrcType.priv.literalValue; + if (srcLiteral === undefined || !ClassType.isLiteralValueSame(concreteSrcType, destType)) { + diag?.addMessage( + LocAddendum.literalAssignmentMismatch().format({ + sourceType: printType(srcType), + destType: printType(destType), + }) + ); + + return false; + } + } + + // Handle LiteralString special form. + if (ClassType.isBuiltIn(destType, 'LiteralString')) { + if ( + ClassType.isBuiltIn(concreteSrcType, 'str') && + concreteSrcType.priv.literalValue !== undefined + ) { + return (flags & AssignTypeFlags.Invariant) === 0; + } else if (ClassType.isBuiltIn(concreteSrcType, 'LiteralString')) { + return true; + } + } else if ( + ClassType.isBuiltIn(concreteSrcType, 'LiteralString') && + prefetched?.strClass && + isInstantiableClass(prefetched.strClass) && + (flags & AssignTypeFlags.Invariant) === 0 + ) { + concreteSrcType = ClassType.cloneAsInstance(prefetched.strClass); + } + + if ( + !assignClass( + ClassType.cloneAsInstantiable(destType), + ClassType.cloneAsInstantiable(concreteSrcType), + diag, + constraints, + flags, + recursionCount, + /* reportErrorsUsingObjType */ true + ) + ) { + return false; + } + + return true; + } else if (isFunctionOrOverloaded(concreteSrcType)) { + // Is the destination a callback protocol (defined in PEP 544)? + const destCallbackType = getCallbackProtocolType(destType, recursionCount); + if (destCallbackType) { + return assignType(destCallbackType, concreteSrcType, diag, constraints, flags, recursionCount); + } + + // All functions are considered instances of "types.FunctionType" or "types.MethodType". + const altClass = isMethodType(concreteSrcType) ? prefetched?.methodClass : prefetched?.functionClass; + if (altClass) { + return assignType(destType, convertToInstance(altClass), diag, constraints, flags, recursionCount); + } + } else if (isModule(concreteSrcType)) { + // Is the destination the built-in "ModuleType"? + if (ClassType.isBuiltIn(destType, 'ModuleType')) { + return true; + } + + if (ClassType.isProtocolClass(destType)) { + return assignModuleToProtocol( + evaluatorInterface, + ClassType.cloneAsInstantiable(destType), + concreteSrcType, + diag, + constraints, + flags, + recursionCount + ); + } + } else if (isInstantiableClass(concreteSrcType)) { + // See if the destType is an instantiation of a Protocol + // class that is effectively a function. + const callbackType = getCallbackProtocolType(destType, recursionCount); + if (callbackType) { + return assignType(callbackType, concreteSrcType, diag, constraints, flags, recursionCount); + } + + // If the destType is an instantiation of a Protocol, + // see if the class type itself satisfies the protocol. + if (ClassType.isProtocolClass(destType)) { + return assignClassToProtocol( + evaluatorInterface, + ClassType.cloneAsInstantiable(destType), + concreteSrcType, + diag, + constraints, + flags, + recursionCount + ); + } + + // Determine if the metaclass can be assigned to the object. + const metaclass = concreteSrcType.shared.effectiveMetaclass; + if (metaclass) { + if (!isAnyOrUnknown(metaclass)) { + if ( + assignClass( + ClassType.cloneAsInstantiable(destType), + metaclass, + /* diag */ undefined, + constraints, + flags, + recursionCount, + /* reportErrorsUsingObjType */ true + ) + ) { + return true; + } + } + } + } else if (isAnyOrUnknown(concreteSrcType) && !concreteSrcType.props?.specialForm) { + return (flags & AssignTypeFlags.OverloadOverlap) === 0; + } else if (isUnion(concreteSrcType)) { + return assignType(destType, concreteSrcType, diag, constraints, flags, recursionCount); + } + } + + if (isFunction(destType)) { + let concreteSrcType = makeTopLevelTypeVarsConcrete(srcType); + + if (isClassInstance(concreteSrcType)) { + const boundMethod = getBoundMagicMethod( + concreteSrcType, + '__call__', + /* selfType */ undefined, + /* errorNode */ undefined, + /* diag */ undefined, + recursionCount + ); + if (boundMethod) { + concreteSrcType = boundMethod; + } + } + + // If it's a class, use the constructor for type compatibility checking. + if (isInstantiableClass(concreteSrcType) && concreteSrcType.priv.literalValue === undefined) { + const constructor = createFunctionFromConstructor( + evaluatorInterface, + concreteSrcType, + isTypeVar(srcType) ? convertToInstance(srcType) : undefined, + recursionCount + ); + if (constructor) { + concreteSrcType = constructor; + + // The constructor conversion may result in a union of the + // __init__ and __new__ callables. + if (isUnion(concreteSrcType)) { + return assignType(destType, concreteSrcType, diag, constraints, flags, recursionCount); + } + } + } + + if (isAnyOrUnknown(concreteSrcType)) { + return (flags & AssignTypeFlags.OverloadOverlap) === 0; + } + + if (isOverloaded(concreteSrcType)) { + // If this is the first pass of an argument assignment, skip + // all attempts to assign an overloaded function to a function + // because we probably don't have enough information to properly + // filter the overloads at this time. We will do this work on + // subsequent passes. + if ((flags & AssignTypeFlags.ArgAssignmentFirstPass) !== 0) { + return true; + } + + // Find all of the overloaded functions that match the parameters. + const overloads = OverloadedType.getOverloads(concreteSrcType); + const filteredOverloads: FunctionType[] = []; + const typeVarSignatures: ConstraintSet[] = []; + + overloads.forEach((overload) => { + const overloadScopeId = getTypeVarScopeId(overload) ?? ''; + const constraintsClone = constraints?.cloneWithSignature(overloadScopeId); + + if (assignType(destType, overload, /* diag */ undefined, constraintsClone, flags, recursionCount)) { + filteredOverloads.push(overload); + + if (constraintsClone) { + appendArray(typeVarSignatures, constraintsClone.getConstraintSets()); + } + } + }); + + if (filteredOverloads.length === 0) { + diag?.addMessage(LocAddendum.noOverloadAssignable().format({ type: printType(destType) })); + return false; + } + + if (filteredOverloads.length === 1 || (flags & AssignTypeFlags.ArgAssignmentFirstPass) === 0) { + if (constraints) { + constraints.addConstraintSets(typeVarSignatures); + } + } + + return true; + } + + if (isFunction(concreteSrcType)) { + if ( + assignFunction( + destType, + concreteSrcType, + diag?.createAddendum(), + constraints ?? new ConstraintTracker(), + flags, + recursionCount + ) + ) { + return true; + } + } + } + + if (isOverloaded(destType)) { + const overloadDiag = diag?.createAddendum(); + + // All overloads in the dest must be assignable. + const destOverloads = OverloadedType.getOverloads(destType); + + // If the source is also an overload with the same number of overloads, + // there's a good chance that there's a one-to-one mapping. Try this + // first before using an n^2 algorithm. + if (isOverloaded(srcType)) { + const srcOverloads = OverloadedType.getOverloads(srcType); + if (destOverloads.length === srcOverloads.length) { + if ( + destOverloads.every((destOverload, index) => { + const srcOverload = srcOverloads[index]; + return assignType( + destOverload, + srcOverload, + /* diag */ undefined, + constraints, + flags, + recursionCount + ); + }) + ) { + return true; + } + } + } + + const isAssignable = destOverloads.every((destOverload) => { + const result = assignType( + destOverload, + srcType, + overloadDiag?.createAddendum(), + constraints, + flags, + recursionCount + ); + return result; + }); + + if (!isAssignable) { + const overloads = OverloadedType.getOverloads(destType); + + if (overloadDiag && overloads.length > 0) { + overloadDiag.addMessage( + LocAddendum.overloadNotAssignable().format({ + name: overloads[0].shared.name, + }) + ); + } + return false; + } + + return true; + } + + if (isClass(destType) && ClassType.isBuiltIn(destType, 'object')) { + if ((isInstantiableClass(destType) && TypeBase.isInstantiable(srcType)) || isClassInstance(destType)) { + if ((flags & AssignTypeFlags.Invariant) === 0) { + // All types (including None, Module, Overloaded) derive from object. + return true; + } + } + } + + // Are we trying to assign None to a protocol? + if (isNoneInstance(srcType) && isClassInstance(destType) && ClassType.isProtocolClass(destType)) { + if (prefetched?.noneTypeClass && isInstantiableClass(prefetched.noneTypeClass)) { + return assignClassToProtocol( + evaluatorInterface, + ClassType.cloneAsInstantiable(destType), + ClassType.cloneAsInstance(prefetched.noneTypeClass), + diag, + constraints, + flags, + recursionCount + ); + } + } + + if (isNoneInstance(destType)) { + diag?.addMessage(LocAddendum.assignToNone()); + return false; + } + + diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType))); + + return false; + } + + // Determines whether a recursive type alias can be assigned to itself + // given the source and dest type args and computed variance for its + // type params. + function assignRecursiveTypeAliasToSelf( + destAliasInfo: TypeAliasInfo, + srcAliasInfo: TypeAliasInfo, + diag?: DiagnosticAddendum, + constraints?: ConstraintTracker, + flags = AssignTypeFlags.Default, + recursionCount = 0 + ) { + assert(destAliasInfo.typeArgs !== undefined); + assert(srcAliasInfo.typeArgs !== undefined); + + let isAssignable = true; + const srcTypeArgs = srcAliasInfo.typeArgs; + const variances = destAliasInfo.shared.computedVariance; + + destAliasInfo.typeArgs.forEach((destTypeArg, index) => { + const srcTypeArg = index < srcTypeArgs.length ? srcTypeArgs[index] : UnknownType.create(); + + let adjFlags = flags; + const variance = variances && index < variances.length ? variances[index] : Variance.Covariant; + + if (variance === Variance.Invariant) { + adjFlags |= AssignTypeFlags.Invariant; + } else if (variance === Variance.Contravariant) { + adjFlags ^= AssignTypeFlags.Contravariant; + } + + if (!assignType(destTypeArg, srcTypeArg, diag, constraints, adjFlags, recursionCount)) { + isAssignable = false; + } + }); + + return isAssignable; + } + + function expectedTypeWantsTypeForm(expectedType: Type): boolean { + return someSubtypes( + expectedType, + (subtype) => isClassInstance(subtype) && ClassType.isBuiltIn(subtype, 'TypeForm') + ); + } + + // If the expected type is an explicit TypeForm type, see if the source + // type has an implicit TypeForm type that can be assigned to it. If so, + // convert to an explicit TypeForm type. + function convertToTypeFormType(expectedType: Type, srcType: Type): Type { + // Is the source is a TypeForm type? + if (!srcType.props?.typeForm) { + return srcType; + } + + let srcTypeFormType: Type | undefined; + + // Is the source is a TypeForm type? + if (srcType.props?.typeForm) { + srcTypeFormType = srcType.props.typeForm; + } else if (isClass(srcType)) { + if (TypeBase.isInstantiable(srcType)) { + if (!ClassType.isSpecialBuiltIn(srcType)) { + srcTypeFormType = ClassType.cloneAsInstance(srcType); + } + } else if (ClassType.isBuiltIn(srcType, 'type')) { + srcTypeFormType = + srcType.priv.typeArgs?.length && srcType.priv.typeArgs.length > 0 + ? srcType.priv.typeArgs[0] + : UnknownType.create(); + } + } else if (isTypeVar(srcType) && TypeBase.isInstantiable(srcType)) { + if (!isTypeVarTuple(srcType) || !srcType.priv.isInUnion) { + srcTypeFormType = convertToInstance(srcType); + } + } + + if (!srcTypeFormType) { + return srcType; + } + + let resultType: Type | undefined; + + doForEachSubtype(expectedType, (subtype) => { + if (resultType || !isClassInstance(subtype) || !ClassType.isBuiltIn(subtype, 'TypeForm')) { + return; + } + + const destTypeFormType = + subtype.priv.typeArgs && subtype.priv.typeArgs.length > 0 + ? subtype.priv.typeArgs[0] + : UnknownType.create(); + + if (assignType(destTypeFormType, srcTypeFormType)) { + resultType = ClassType.specialize(subtype, [srcTypeFormType]); + } + }); + + return resultType ?? srcType; + } + + function assignFromUnionType( + destType: Type, + srcType: UnionType, + diag: DiagnosticAddendum | undefined, + constraints: ConstraintTracker | undefined, + flags: AssignTypeFlags, + recursionCount: number + ): boolean { + // Start by checking for an exact match. This is needed to handle unions + // that contain recursive type aliases. + if (isTypeSame(srcType, destType, {}, recursionCount)) { + return true; + } + + if ( + (flags & AssignTypeFlags.OverloadOverlap) !== 0 && + srcType.priv.subtypes.some((subtype) => isAnyOrUnknown(subtype)) + ) { + return false; + } + + // Sort the subtypes so we have a deterministic order for unions. + let sortedSrcTypes: Type[] = sortTypes(srcType.priv.subtypes); + let matchedSomeSubtypes = false; + + // Handle the case where the source and dest are both unions. Try + // to eliminate as many exact type matches between the src and dest. + if (isUnion(destType)) { + // Handle the special case where the dest is a union of Any and + // a type variable. This occurs, for example, with the return type of + // the getattr function. + const nonAnySubtypes = destType.priv.subtypes.filter((t) => !isAnyOrUnknown(t)); + if (nonAnySubtypes.length === 1 && isTypeVar(nonAnySubtypes[0])) { + assignType(nonAnySubtypes[0], srcType, /* diag */ undefined, constraints, flags, recursionCount); + + // This always succeeds because the destination contains Any. + return true; + } + + const remainingDestSubtypes: Type[] = []; + let remainingSrcSubtypes: Type[] = sortedSrcTypes; + let canUseFastPath = true; + + // First attempt to match all of the non-generic types in the dest + // to non-generic types in the source. + sortTypes(destType.priv.subtypes).forEach((destSubtype) => { + if (requiresSpecialization(destSubtype)) { + remainingDestSubtypes.push(destSubtype); + } else { + const srcTypeIndex = remainingSrcSubtypes.findIndex((srcSubtype) => + isTypeSame(srcSubtype, destSubtype, {}, recursionCount) + ); + + if (srcTypeIndex >= 0) { + remainingSrcSubtypes.splice(srcTypeIndex, 1); + matchedSomeSubtypes = true; + } else { + remainingDestSubtypes.push(destSubtype); + } + } + }); + + // For all remaining source subtypes, attempt to find a dest subtype + // whose primary type matches. + remainingSrcSubtypes.forEach((srcSubtype) => { + const destTypeIndex = remainingDestSubtypes.findIndex((destSubtype) => { + if (isTypeSame(destSubtype, srcSubtype)) { + return true; + } + + if ( + isClass(srcSubtype) && + isClass(destSubtype) && + TypeBase.isInstance(srcSubtype) === TypeBase.isInstance(destSubtype) + ) { + if (ClassType.isSameGenericClass(srcSubtype, destSubtype)) { + return true; + } + + // Are they equivalent TypedDicts? + if (ClassType.isTypedDictClass(srcSubtype) && ClassType.isTypedDictClass(destSubtype)) { + if ( + assignType( + srcSubtype, + destSubtype, + /* diag */ undefined, + /* constraints */ undefined, + flags, + recursionCount + ) + ) { + return true; + } + } + } + + if (isFunctionOrOverloaded(srcSubtype) && isFunctionOrOverloaded(destSubtype)) { + return true; + } + + return false; + }); + + if (destTypeIndex >= 0) { + if ( + assignType( + remainingDestSubtypes[destTypeIndex], + srcSubtype, + /* diag */ undefined, + constraints, + flags, + recursionCount + ) + ) { + // Note that we have matched at least one subtype indicating + // there is at least some overlap. + matchedSomeSubtypes = true; + } else { + canUseFastPath = false; + } + + remainingDestSubtypes.splice(destTypeIndex, 1); + remainingSrcSubtypes = remainingSrcSubtypes.filter((t) => t !== srcSubtype); + } + }); + + // If there is are remaining dest subtypes and they're all type variables, + // attempt to assign the remaining source subtypes to them. + if (canUseFastPath && (remainingDestSubtypes.length !== 0 || remainingSrcSubtypes.length !== 0)) { + if ((flags & AssignTypeFlags.Invariant) !== 0) { + // If we have no src subtypes remaining but not all dest types have been subsumed + // by other dest types, then the types are not compatible if we're enforcing invariance. + if (remainingSrcSubtypes.length === 0) { + return remainingDestSubtypes.every((destSubtype) => + isTypeSubsumedByOtherType( + destSubtype, + destType, + /* allowAnyToSubsume */ true, + recursionCount + ) + ); + } + } + + const isContra = (flags & AssignTypeFlags.Contravariant) !== 0; + const effectiveDestSubtypes = isContra ? remainingSrcSubtypes : remainingDestSubtypes; + + if (effectiveDestSubtypes.length === 0 || effectiveDestSubtypes.some((t) => !isTypeVar(t))) { + canUseFastPath = false; + + // We can avoid checking the source subtypes that have already been checked. + sortedSrcTypes = remainingSrcSubtypes; + } else if (remainingDestSubtypes.length === remainingSrcSubtypes.length) { + // If the number of remaining source subtypes is the same as the number + // of dest TypeVars, try to assign each source subtype to its own dest TypeVar. + const reorderedDestSubtypes = [...remainingDestSubtypes]; + + for (let srcIndex = 0; srcIndex < remainingSrcSubtypes.length; srcIndex++) { + let foundMatchForSrc = false; + + for (let destIndex = 0; destIndex < reorderedDestSubtypes.length; destIndex++) { + if ( + assignType( + reorderedDestSubtypes[destIndex], + remainingSrcSubtypes[srcIndex], + diag?.createAddendum(), + constraints, + flags, + recursionCount + ) + ) { + foundMatchForSrc = true; + // Move the matched dest TypeVar to the end of the list so the other + // dest TypeVars have a better chance of being assigned to. + reorderedDestSubtypes.push(...reorderedDestSubtypes.splice(destIndex, 1)); + break; + } + } + + if (!foundMatchForSrc) { + canUseFastPath = false; + break; + } + } + + // We can avoid checking the source subtypes that have already been checked. + sortedSrcTypes = remainingSrcSubtypes; + } else if (remainingSrcSubtypes.length === 0) { + if ((flags & AssignTypeFlags.PopulateExpectedType) !== 0) { + // If we're populating an expected type, try not to leave + // any TypeVars unsolved. Assign the full type to the remaining + // dest TypeVars. + remainingDestSubtypes.forEach((destSubtype) => { + assignType(destSubtype, srcType, /* diag */ undefined, constraints, flags, recursionCount); + }); + } + + // If we've assigned all of the source subtypes but one or more dest + // TypeVars have gone unmatched, treat this as success. + } else { + // Try to assign a union of the remaining source types to + // the first destination TypeVar. If this is a contravariant + // context, use the full dest type rather than the remaining + // dest subtypes to keep the lower bound as wide as possible. + if ( + !assignType( + isContra ? destType : remainingDestSubtypes[0], + isContra ? remainingSrcSubtypes[0] : combineTypes(remainingSrcSubtypes), + diag?.createAddendum(), + constraints, + flags, + recursionCount + ) + ) { + canUseFastPath = false; + } + } + } + + if (canUseFastPath) { + return true; + } + + // If we're looking for type overlaps and at least one type was matched, + // consider it as assignable. + if ((flags & AssignTypeFlags.PartialOverloadOverlap) !== 0 && matchedSomeSubtypes) { + return true; + } + } + + let isIncompatible = false; + + sortedSrcTypes.forEach((subtype) => { + if (isIncompatible) { + return; + } + + if (!assignType(destType, subtype, /* diag */ undefined, constraints, flags, recursionCount)) { + // Determine if the current subtype is subsumed by another subtype + // in the same union. If so, we can ignore this. + const isSubtypeSubsumed = isTypeSubsumedByOtherType( + subtype, + srcType, + /* allowAnyToSubsume */ false, + recursionCount + ); + + // Try again with a concrete version of the subtype. + if ( + !isSubtypeSubsumed && + !assignType(destType, subtype, diag?.createAddendum(), constraints, flags, recursionCount) + ) { + isIncompatible = true; + } + } else { + matchedSomeSubtypes = true; + } + }, /* sortSubtypes */ true); + + if (isIncompatible) { + // If we're looking for type overlaps and at least one type was matched, + // consider it as assignable. + if ((flags & AssignTypeFlags.PartialOverloadOverlap) !== 0 && matchedSomeSubtypes) { + return true; + } + + diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType))); + return false; + } + + return true; + } + + function isSpecialFormClass(classType: ClassType, flags: AssignTypeFlags): boolean { + if ((flags & AssignTypeFlags.AllowIsinstanceSpecialForms) !== 0) { + return false; + } + + return ClassType.isSpecialFormClass(classType); + } + + // Finds unsolved type variables in the destType and establishes constraints + // in the constraint tracker for them based on the srcType. + function setConstraintsForFreeTypeVars( + destType: Type, + srcType: UnknownType | AnyType, + constraints: ConstraintTracker + ) { + const typeVars = getTypeVarArgsRecursive(destType); + typeVars.forEach((typeVar) => { + if (!TypeVarType.isBound(typeVar) && !constraints.getMainConstraintSet().getTypeVar(typeVar)) { + // Don't set ParamSpecs or TypeVarTuples. + if (!isParamSpec(srcType) && !isTypeVarTuple(srcType)) { + constraints.setBounds(typeVar, srcType); + } + } + }); + } + + // Determines whether a type is "subsumed by" (i.e. is a proper subtype of) another type. + function isTypeSubsumedByOtherType(type: Type, otherType: Type, allowAnyToSubsume: boolean, recursionCount = 0) { + const concreteType = makeTopLevelTypeVarsConcrete(type); + const otherSubtypes = isUnion(otherType) ? otherType.priv.subtypes : [otherType]; + + for (const otherSubtype of otherSubtypes) { + if (isTypeSame(otherSubtype, type)) { + continue; + } + + if (isAnyOrUnknown(otherSubtype)) { + if (allowAnyToSubsume) { + return true; + } + } else if (isProperSubtype(otherSubtype, concreteType, recursionCount)) { + return true; + } + } + + return false; + } + + // Determines whether the srcType is a subtype of destType but the converse + // is not true. It's important that we check both directions to avoid + // matches for types like `tuple[Any]` and `tuple[int]` from being considered + // proper subtypes of each other. + function isProperSubtype(destType: Type, srcType: Type, recursionCount: number) { + // If the destType has a condition, don't consider the srcType a proper subtype. + if (destType.props?.condition) { + return false; + } + + // Shortcut the check if either type is Any or Unknown. + if (isAnyOrUnknown(destType) || isAnyOrUnknown(srcType)) { + return true; + } + + // Shortcut the check if either type is a class whose hierarchy contains an unknown type. + if (isClass(destType) && destType.shared.mro.some((mro) => isAnyOrUnknown(mro))) { + return true; + } + + if (isClass(srcType) && srcType.shared.mro.some((mro) => isAnyOrUnknown(mro))) { + return true; + } + + return ( + assignType( + destType, + srcType, + /* diag */ undefined, + /* constraints */ undefined, + AssignTypeFlags.Default, + recursionCount + ) && + !assignType( + srcType, + destType, + /* diag */ undefined, + /* constraints */ undefined, + AssignTypeFlags.Default, + recursionCount + ) + ); + } + + // Determines whether the two types are potentially comparable -- i.e. + // their types overlap in such a way that it makes sense for them to + // be compared with an == or != operator. The functional also supports + // a special variant that can be used for the "is" and "is not" operator. + // This variant can be less conservative in some cases. + function isTypeComparable(leftType: Type, rightType: Type, assumeIsOperator = false) { + if (isAnyOrUnknown(leftType) || isAnyOrUnknown(rightType)) { + return true; + } + + // Viper 扩展:t.CDefine 类型可与任何类型比较(任意转参转换语义) + if (isCDefineType(leftType) || isCDefineType(rightType)) { + return true; + } + + if (isNever(leftType) || isNever(rightType)) { + return false; + } + + if (isModule(leftType) || isModule(rightType)) { + return isTypeSame(leftType, rightType, { ignoreConditions: true }); + } + + const isLeftCallable = isFunctionOrOverloaded(leftType); + const isRightCallable = isFunctionOrOverloaded(rightType); + + // If either type is a function, assume that it may be comparable. The other + // operand might be a callable object, an 'object' instance, etc. We could + // make this more precise for specific cases (e.g. if the other operand is + // None or a literal or an instance of a nominal class that doesn't override + // __call__ and is marked final, etc.), but coming up with a comprehensive + // list is probably not feasible. + if (isLeftCallable || isRightCallable) { + return true; + } + + if (isInstantiableClass(leftType) || (isClassInstance(leftType) && ClassType.isBuiltIn(leftType, 'type'))) { + if ( + isInstantiableClass(rightType) || + (isClassInstance(rightType) && ClassType.isBuiltIn(rightType, 'type')) + ) { + const genericLeftType = ClassType.specialize(leftType, /* typeArgs */ undefined); + const genericRightType = ClassType.specialize(rightType, /* typeArgs */ undefined); + + if (assignType(genericLeftType, genericRightType) || assignType(genericRightType, genericLeftType)) { + return true; + } + } + + // Does the class have an operator overload for eq? + const metaclass = leftType.shared.effectiveMetaclass; + if (metaclass && isClass(metaclass)) { + if (lookUpClassMember(metaclass, '__eq__', MemberAccessFlags.SkipObjectBaseClass)) { + return true; + } + } + + return false; + } + + if (isClassInstance(leftType)) { + if (isClass(rightType)) { + const genericLeftType = ClassType.specialize(leftType, /* typeArgs */ undefined); + const genericRightType = ClassType.specialize(rightType, /* typeArgs */ undefined); + + if (assignType(genericLeftType, genericRightType) || assignType(genericRightType, genericLeftType)) { + return true; + } + + // Check for the "is None" or "is not None" case. + if (assumeIsOperator && isNoneInstance(rightType)) { + if (isNoneInstance(leftType)) { + return true; + } + + // The LHS could be a protocol or 'object', in which case None is + // potentially comparable to it. In other cases, None is not comparable + // because the types are disjoint. + return assignType(leftType, rightType); + } + + // Assume that if the types are disjoint and built-in classes that they + // will never be comparable. + if (ClassType.isBuiltIn(leftType) && ClassType.isBuiltIn(rightType) && TypeBase.isInstance(rightType)) { + // We need to be careful with bool and int literals because + // they are comparable under certain circumstances. + let boolType: ClassType | undefined; + let intType: ClassType | undefined; + if (ClassType.isBuiltIn(leftType, 'bool') && ClassType.isBuiltIn(rightType, 'int')) { + boolType = leftType; + intType = rightType; + } else if (ClassType.isBuiltIn(rightType, 'bool') && ClassType.isBuiltIn(leftType, 'int')) { + boolType = rightType; + intType = leftType; + } + + if (boolType && intType) { + const intVal = intType.priv?.literalValue as number | BigInt | undefined; + if (intVal === undefined) { + return true; + } + if (intVal !== 0 && intVal !== 1) { + return false; + } + + const boolVal = boolType.priv?.literalValue as boolean | undefined; + if (boolVal === undefined) { + return true; + } + + return boolVal === (intVal === 1); + } + + return false; + } + } + + // Does the class have an operator overload for eq? + const eqMethod = lookUpClassMember( + ClassType.cloneAsInstantiable(leftType), + '__eq__', + MemberAccessFlags.SkipObjectBaseClass + ); + + if (eqMethod) { + // If this is a synthesized method for a dataclass, we can assume + // that other dataclass types will not be comparable. + if (ClassType.isDataClass(leftType) && eqMethod.symbol.getSynthesizedType()) { + return false; + } + + return true; + } + + return false; + } + + return true; + } + + function assignToUnionType( + destType: UnionType, + srcType: Type, + diag: DiagnosticAddendum | undefined, + constraints: ConstraintTracker | undefined, + flags: AssignTypeFlags, + recursionCount: number + ): boolean { + // If we need to enforce invariance, the source needs to be compatible + // with all subtypes in the dest, unless those subtypes are subclasses + // of other subtypes. + if (flags & AssignTypeFlags.Invariant) { + let isIncompatible = false; + + doForEachSubtype(destType, (subtype, index) => { + if ( + !isIncompatible && + !assignType(subtype, srcType, diag?.createAddendum(), constraints, flags, recursionCount) + ) { + // Determine whether this subtype is subsumed by some other + // subtype in the union. If so, we can ignore the incompatibility. + let skipSubtype = false; + if (!isAnyOrUnknown(subtype)) { + const adjSubtype = makeTypeVarsBound(subtype, /* scopeIds */ undefined); + + doForEachSubtype(destType, (otherSubtype, otherIndex) => { + if (index !== otherIndex && !skipSubtype) { + const adjOtherSubtype = makeTypeVarsBound(otherSubtype, /* scopeIds */ undefined); + + if ( + assignType( + adjOtherSubtype, + adjSubtype, + /* diag */ undefined, + /* constraints */ undefined, + AssignTypeFlags.Default, + recursionCount + ) + ) { + skipSubtype = true; + } + } + }); + } + if (!skipSubtype) { + isIncompatible = true; + } + } + }); + + if (isIncompatible) { + diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType))); + return false; + } + + return true; + } + + // For union destinations, we just need to match one of the types. + const diagAddendum = diag ? new DiagnosticAddendum() : undefined; + + let foundMatch = false; + + // Does the union contain any type variables that need to be solved? + // If so, we need to use a slower path. + if (!requiresSpecialization(destType)) { + for (const subtype of destType.priv.subtypes) { + if (assignType(subtype, srcType, diagAddendum?.createAddendum(), constraints, flags, recursionCount)) { + foundMatch = true; + break; + } + } + } else { + // Run through all subtypes in the union. Don't stop at the first + // match we find because we may need to match TypeVars in other + // subtypes. We special-case "None" so we can handle Optional[T] + // without matching the None to the type var. + if (isNoneInstance(srcType) && isOptionalType(destType)) { + foundMatch = true; + } else { + let bestConstraints: ConstraintTracker | undefined; + let bestConstraintsScore: number | undefined; + let nakedTypeVarMatches = 0; + + // If the srcType is a literal, try to use the fast-path lookup + // in case the destType is a union with hundreds of literals. + if ( + isClassInstance(srcType) && + isLiteralType(srcType) && + UnionType.containsType( + destType, + srcType, + /* options */ undefined, + /* exclusionSet */ undefined, + recursionCount + ) + ) { + return true; + } + + doForEachSubtype( + destType, + (subtype) => { + // Make a temporary clone of the constraints. We don't want to modify + // the original constraints until we find the "optimal" typeVar mapping. + const constraintsClone = constraints?.clone(); + if ( + assignType( + subtype, + srcType, + diagAddendum?.createAddendum(), + constraintsClone, + flags, + recursionCount + ) + ) { + foundMatch = true; + if (constraintsClone) { + // Ask the constraints to compute a "score" for the current + // contents of the table. + let constraintsScore = constraintsClone.getScore(); + + if (isTypeVar(subtype)) { + if (!constraints?.getMainConstraintSet().getTypeVar(subtype)) { + nakedTypeVarMatches++; + + // Handicap the solution slightly so another type var with + // existing constraints will be preferred. + constraintsScore += 0.001; + } + } + + // If the type matches exactly, prefer it over other types. + if (isTypeSame(subtype, stripLiteralValue(srcType))) { + constraintsScore = Number.POSITIVE_INFINITY; + } + + if (bestConstraintsScore === undefined || bestConstraintsScore <= constraintsScore) { + // We found a typeVar mapping with a higher score than before. + bestConstraintsScore = constraintsScore; + bestConstraints = constraintsClone; + } + } + } + }, + /* sortSubtypes */ true + ); + + // If we saw more than one "naked" type vars that have no + // previous constraints recorded, it's dangerous for us to + // assign a value to any of these type vars at this time. + // Typically, they will receive some constraints via some + // later argument assignment. + if (nakedTypeVarMatches > 1 && (flags & AssignTypeFlags.ArgAssignmentFirstPass) !== 0) { + bestConstraints = undefined; + } + + // If we found a winning type var mapping, copy it back to constraints. + if (constraints && bestConstraints) { + constraints.copyFromClone(bestConstraints); + } + } + } + + // If the source is a constrained TypeVar, see if we can assign all of the + // constraints to the union. + if (!foundMatch) { + if (isTypeVar(srcType) && TypeVarType.hasConstraints(srcType)) { + foundMatch = assignType( + destType, + makeTopLevelTypeVarsConcrete(srcType), + diagAddendum?.createAddendum(), + constraints, + flags, + recursionCount + ); + } + } + + if (!foundMatch) { + if (diag && diagAddendum) { + diag.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType))); + diag.addAddendum(diagAddendum); + } + return false; + } + + return true; + } + + function assignConditionalTypeToTypeVar(destType: TypeVarType, srcType: Type, recursionCount: number): boolean { + // The srcType is assignable only if all of its subtypes are assignable. + return !findSubtype(srcType, (srcSubtype) => { + if (isTypeSame(destType, srcSubtype, { ignorePseudoGeneric: true }, recursionCount)) { + return false; + } + + if (isIncompleteUnknown(srcSubtype)) { + return false; + } + + const destTypeVarName = TypeVarType.getNameWithScope(destType); + + // Determine which conditions on this type apply to this type variable. + // There might be more than one of them. + const applicableConditions = (getTypeCondition(srcSubtype) ?? []).filter( + (constraint) => constraint.typeVar.priv.nameWithScope === destTypeVarName + ); + + // If there are no applicable conditions, it's not assignable. + if (applicableConditions.length === 0) { + return true; + } + + return !applicableConditions.some((condition) => { + if (condition.typeVar.priv.nameWithScope === TypeVarType.getNameWithScope(destType)) { + if (destType.shared.boundType) { + assert( + condition.constraintIndex === 0, + 'Expected constraint for bound TypeVar to have index of 0' + ); + + return assignType( + destType.shared.boundType, + srcSubtype, + /* diag */ undefined, + /* constraints */ undefined, + AssignTypeFlags.Default, + recursionCount + ); + } + + if (TypeVarType.hasConstraints(destType)) { + assert( + condition.constraintIndex < destType.shared.constraints.length, + 'Constraint for constrained TypeVar is out of bounds' + ); + + return assignType( + destType.shared.constraints[condition.constraintIndex], + srcSubtype, + /* diag */ undefined, + /* constraints */ undefined, + AssignTypeFlags.Default, + recursionCount + ); + } + + // This is a non-bound and non-constrained type variable with a matching condition. + return true; + } + + return false; + }); + }); + } + + // If the class is a protocol and it has a `__call__` method but no other methods + // or attributes that would be incompatible with a function, this method returns + // the signature of the call implied by the `__call__` method. Otherwise it returns + // undefined. + function getCallbackProtocolType( + objType: ClassType, + recursionCount = 0 + ): FunctionType | OverloadedType | undefined { + if (!isClassInstance(objType) || !ClassType.isProtocolClass(objType)) { + return undefined; + } + + // Make sure that the protocol class doesn't define any fields that + // a normal function wouldn't be compatible with. + for (const mroClass of objType.shared.mro) { + if (isClass(mroClass) && ClassType.isProtocolClass(mroClass)) { + for (const field of ClassType.getSymbolTable(mroClass)) { + const fieldName = field[0]; + const fieldSymbol = field[1]; + + // We're expecting a __call__ method. We will also ignore a + // __slots__ definition, which is (by convention) ignored for + // protocol matching. + if (fieldName === '__call__' || fieldName === '__slots__') { + continue; + } + + if (fieldSymbol.isIgnoredForProtocolMatch()) { + continue; + } + + let fieldIsPartOfFunction = false; + + if (prefetched?.functionClass && isClass(prefetched.functionClass)) { + if (ClassType.getSymbolTable(prefetched.functionClass).has(field[0])) { + fieldIsPartOfFunction = true; + } + } + + if (!fieldIsPartOfFunction) { + return undefined; + } + } + } + } + + const callType = getBoundMagicMethod( + objType, + '__call__', + /* selfType */ undefined, + /* errorNode */ undefined, + /* diag */ undefined, + recursionCount + ); + + if (!callType) { + return undefined; + } + + return makeFunctionTypeVarsBound(callType); + } + + function assignParam( + destType: Type, + srcType: Type, + paramIndex: number | undefined, + diag: DiagnosticAddendum | undefined, + constraints: ConstraintTracker, + flags: AssignTypeFlags, + recursionCount: number + ) { + if (isTypeVarTuple(destType) && !isUnpacked(srcType)) { + return false; + } + + let specializedSrcType = srcType; + let specializedDestType = destType; + let doSpecializationStep = false; + + if ((flags & AssignTypeFlags.OverloadOverlap) === 0) { + const isFirstPass = (flags & AssignTypeFlags.ArgAssignmentFirstPass) !== 0; + + if ((flags & AssignTypeFlags.Contravariant) === 0) { + if (!isFirstPass) { + specializedDestType = solveAndApplyConstraints( + destType, + constraints, + /* applyOptions */ undefined, + { useLowerBoundOnly: true } + ); + } + doSpecializationStep = requiresSpecialization(specializedDestType); + } else { + if (!isFirstPass) { + specializedSrcType = solveAndApplyConstraints(srcType, constraints, /* applyOptions */ undefined, { + useLowerBoundOnly: true, + }); + } + doSpecializationStep = requiresSpecialization(specializedSrcType); + } + } + + // Is an additional specialization step required? + if (doSpecializationStep) { + if ( + assignType( + specializedSrcType, + specializedDestType, + /* diag */ undefined, + constraints, + (flags ^ AssignTypeFlags.Contravariant) | AssignTypeFlags.RetainLiteralsForTypeVar, + recursionCount + ) + ) { + specializedDestType = solveAndApplyConstraints(destType, constraints); + } + } + + if ( + !assignType( + specializedSrcType, + specializedDestType, + diag?.createAddendum(), + constraints, + flags, + recursionCount + ) + ) { + if (diag && paramIndex !== undefined) { + diag.addMessage( + LocAddendum.paramAssignment().format({ + index: paramIndex + 1, + sourceType: printType(destType), + destType: printType(srcType), + }) + ); + } + + return false; + } + + return true; + } + + // Determines whether we need to pack some of the source positionals + // into a tuple that matches a variadic *args parameter in the destination. + function adjustSourceParamDetailsForDestVariadic(srcDetails: ParamListDetails, destDetails: ParamListDetails) { + // If there is no *args parameter in the dest, we have nothing to do. + if (destDetails.argsIndex === undefined) { + return; + } + + // If the *args parameter isn't an unpacked TypeVarTuple or tuple, + // we have nothing to do. + if (!isUnpacked(destDetails.params[destDetails.argsIndex].type)) { + return; + } + + // If the source doesn't have enough positional parameters, we have nothing to do. + if (srcDetails.params.length < destDetails.argsIndex) { + return; + } + + let srcLastToPackIndex = srcDetails.params.findIndex((p, i) => { + assert(destDetails.argsIndex !== undefined); + return i >= destDetails.argsIndex && p.kind === ParamKind.Keyword; + }); + if (srcLastToPackIndex < 0) { + srcLastToPackIndex = srcDetails.params.length; + } + + // If both the source and dest have an *args parameter but the dest's is + // in a later position, then we can't assign the source's *args to the dest. + // Don't make any adjustment in this case. + if (srcDetails.argsIndex !== undefined && destDetails.argsIndex > srcDetails.argsIndex) { + return; + } + + const destFirstNonPositional = destDetails.firstKeywordOnlyIndex ?? destDetails.params.length; + const suffixLength = destFirstNonPositional - destDetails.argsIndex - 1; + const srcPositionalsToPack = srcDetails.params.slice(destDetails.argsIndex, srcLastToPackIndex - suffixLength); + const srcTupleTypes: TupleTypeArg[] = []; + srcPositionalsToPack.forEach((entry) => { + if (entry.param.category === ParamCategory.ArgsList) { + if (isUnpackedTypeVarTuple(entry.type)) { + srcTupleTypes.push({ type: entry.type, isUnbounded: false }); + } else if (isUnpackedClass(entry.type) && entry.type.priv.tupleTypeArgs) { + appendArray(srcTupleTypes, entry.type.priv.tupleTypeArgs); + } else { + srcTupleTypes.push({ type: entry.type, isUnbounded: true }); + } + } else { + srcTupleTypes.push({ type: entry.type, isUnbounded: false, isOptional: !!entry.defaultType }); + } + }); + + if (srcTupleTypes.length !== 1 || !isTypeVarTuple(srcTupleTypes[0].type)) { + const srcPositionalsType = makeTupleObject(evaluatorInterface, srcTupleTypes, /* isUnpacked */ true); + + // Snip out the portion of the source positionals that map to the variadic + // dest parameter and replace it with a single parameter that is typed as a + // tuple containing the individual types of the replaced parameters. + srcDetails.params = [ + ...srcDetails.params.slice(0, destDetails.argsIndex), + { + param: FunctionParam.create( + ParamCategory.ArgsList, + srcPositionalsType, + FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared, + '_arg_combined' + ), + type: srcPositionalsType, + declaredType: srcPositionalsType, + index: -1, + kind: ParamKind.Positional, + }, + ...srcDetails.params.slice( + destDetails.argsIndex + srcPositionalsToPack.length, + srcDetails.params.length + ), + ]; + + const argsIndex = srcDetails.params.findIndex((param) => param.param.category === ParamCategory.ArgsList); + srcDetails.argsIndex = argsIndex >= 0 ? argsIndex : undefined; + + const kwargsIndex = srcDetails.params.findIndex( + (param) => param.param.category === ParamCategory.KwargsDict + ); + srcDetails.kwargsIndex = kwargsIndex >= 0 ? kwargsIndex : undefined; + + const firstKeywordOnlyIndex = srcDetails.params.findIndex((param) => param.kind === ParamKind.Keyword); + srcDetails.firstKeywordOnlyIndex = firstKeywordOnlyIndex >= 0 ? firstKeywordOnlyIndex : undefined; + + srcDetails.positionOnlyParamCount = Math.max( + 0, + srcDetails.params.findIndex( + (p) => + p.kind !== ParamKind.Positional || p.param.category !== ParamCategory.Simple || !!p.defaultType + ) + ); + } + } + + function assignFunction( + destType: FunctionType, + srcType: FunctionType, + diag: DiagnosticAddendum | undefined, + constraints: ConstraintTracker, + flags: AssignTypeFlags, + recursionCount: number + ): boolean { + let canAssign = true; + const checkReturnType = (flags & AssignTypeFlags.SkipReturnTypeCheck) === 0; + const isContra = (flags & AssignTypeFlags.Contravariant) !== 0; + flags &= ~AssignTypeFlags.SkipReturnTypeCheck; + + const destParamSpec = FunctionType.getParamSpecFromArgsKwargs(destType); + if (destParamSpec) { + destType = FunctionType.cloneRemoveParamSpecArgsKwargs(destType); + } + + const srcParamSpec = FunctionType.getParamSpecFromArgsKwargs(srcType); + if (srcParamSpec) { + srcType = FunctionType.cloneRemoveParamSpecArgsKwargs(srcType); + } + + const destParamDetails = getParamListDetails(destType, { + disallowExtraKwargsForTd: (flags & AssignTypeFlags.DisallowExtraKwargsForTd) !== 0, + }); + const srcParamDetails = getParamListDetails(srcType, { + disallowExtraKwargsForTd: (flags & AssignTypeFlags.DisallowExtraKwargsForTd) !== 0, + }); + + adjustSourceParamDetailsForDestVariadic( + isContra ? destParamDetails : srcParamDetails, + isContra ? srcParamDetails : destParamDetails + ); + + const targetIncludesParamSpec = isContra ? !!srcParamSpec : !!destParamSpec; + + const destPositionalCount = destParamDetails.firstKeywordOnlyIndex ?? destParamDetails.params.length; + const srcPositionalCount = srcParamDetails.firstKeywordOnlyIndex ?? srcParamDetails.params.length; + const positionalsToMatch = Math.min(destPositionalCount, srcPositionalCount); + const skippedPosParamIndices: number[] = []; + + // Match positional parameters. + for (let paramIndex = 0; paramIndex < positionalsToMatch; paramIndex++) { + if ( + paramIndex === 0 && + destType.shared.methodClass && + (flags & AssignTypeFlags.SkipSelfClsParamCheck) !== 0 + ) { + if (FunctionType.isInstanceMethod(destType) || FunctionType.isClassMethod(destType)) { + continue; + } + } + + // Skip over the *args parameter since it's handled separately below. + if (paramIndex === destParamDetails.argsIndex) { + if (!isUnpackedTypeVarTuple(destParamDetails.params[destParamDetails.argsIndex].type)) { + skippedPosParamIndices.push(paramIndex); + } + continue; + } + + const destParam = destParamDetails.params[paramIndex]; + const srcParam = srcParamDetails.params[paramIndex]; + + // Find the original index of this source param. If we synthesized it above (for + // a variadic parameter), it may not be found. + const srcParamType = srcParam.type; + const destParamType = destParam.type; + + const destParamName = destParam.param.name ?? ''; + const srcParamName = srcParam.param.name ?? ''; + if (destParamName) { + const isDestPositionalOnly = + destParam.kind === ParamKind.Positional || destParam.kind === ParamKind.ExpandedArgs; + if ( + !isDestPositionalOnly && + destParam.param.category !== ParamCategory.ArgsList && + srcParam.param.category !== ParamCategory.ArgsList + ) { + if (srcParam.kind === ParamKind.Positional || srcParam.kind === ParamKind.ExpandedArgs) { + diag?.createAddendum().addMessage( + LocAddendum.functionParamPositionOnly().format({ + name: destParamName, + }) + ); + canAssign = false; + } else if (destParamName !== srcParamName) { + diag?.createAddendum().addMessage( + LocAddendum.functionParamName().format({ + srcName: srcParamName, + destName: destParamName, + }) + ); + canAssign = false; + } + } + } + + if (destParam.defaultType) { + if (!srcParam.defaultType && paramIndex !== srcParamDetails.argsIndex) { + diag?.createAddendum().addMessage( + LocAddendum.functionParamDefaultMissing().format({ + name: srcParamName, + }) + ); + canAssign = false; + } + + // If we're performing a partial overload match and both the source + // and dest parameters provide defaults, assume that there could + // be a match. + if ((flags & AssignTypeFlags.PartialOverloadOverlap) !== 0) { + if (srcParam.defaultType) { + continue; + } + } + } + + // Handle the special case of an overloaded __init__ method whose self + // parameter is annotated. + if ( + paramIndex === 0 && + srcType.shared.name === '__init__' && + FunctionType.isInstanceMethod(srcType) && + destType.shared.name === '__init__' && + FunctionType.isInstanceMethod(destType) && + FunctionType.isOverloaded(destType) && + FunctionParam.isTypeDeclared(destParam.param) + ) { + continue; + } + + if (isUnpacked(srcParamType)) { + canAssign = false; + } else if ( + !assignParam( + destParamType, + srcParamType, + paramIndex, + diag?.createAddendum(), + constraints, + flags, + recursionCount + ) + ) { + // Handle the special case where the source parameter is a synthesized + // TypeVar for "self" or "cls". + if ( + (flags & AssignTypeFlags.SkipSelfClsTypeCheck) === 0 || + !isTypeVar(srcParamType) || + !srcParamType.shared.isSynthesized + ) { + canAssign = false; + } + } else if ( + destParam.kind !== ParamKind.Positional && + destParam.kind !== ParamKind.ExpandedArgs && + srcParam.kind === ParamKind.Positional && + srcParamDetails.kwargsIndex === undefined && + !srcParamDetails.params.some( + (p) => + p.kind === ParamKind.Keyword && + p.param.category === ParamCategory.Simple && + p.param.name === destParam.param.name + ) + ) { + diag?.addMessage( + LocAddendum.namedParamMissingInSource().format({ + name: destParam.param.name ?? '', + }) + ); + canAssign = false; + } + } + + if ( + !FunctionType.isGradualCallableForm(destType) && + destParamDetails.firstPositionOrKeywordIndex < srcParamDetails.positionOnlyParamCount && + !targetIncludesParamSpec + ) { + diag?.createAddendum().addMessage( + LocAddendum.argsPositionOnly().format({ + expected: srcParamDetails.positionOnlyParamCount, + received: destParamDetails.firstPositionOrKeywordIndex, + }) + ); + canAssign = false; + } + + if (destPositionalCount < srcPositionalCount && !targetIncludesParamSpec) { + // Add any remaining positional parameter indices to the list that + // need to be validated. + for (let i = destPositionalCount; i < srcPositionalCount; i++) { + skippedPosParamIndices.push(i); + } + + for (const i of skippedPosParamIndices) { + // If the dest has an *args parameter, make sure it can accept the remaining + // positional arguments in the source. + if (destParamDetails.argsIndex !== undefined) { + const destArgsType = destParamDetails.params[destParamDetails.argsIndex].type; + const srcParamType = srcParamDetails.params[i].type; + if ( + !assignParam( + destArgsType, + srcParamType, + i, + diag?.createAddendum(), + constraints, + flags, + recursionCount + ) + ) { + canAssign = false; + } + + continue; + } + + // If The source parameter has a default value, it is OK for the + // corresponding dest parameter to be missing. + const srcParam = srcParamDetails.params[i]; + + if (srcParam.defaultType) { + // Assign default arg value in case it is needed for + // populating TypeVar constraints. + const paramInfo = srcParamDetails.params[i]; + const defaultArgType = paramInfo.defaultType ?? paramInfo.defaultType; + + // Enforce invariance below because the default arg value + // is constructed prior to the call, so its type is already + // fixed. + if ( + defaultArgType && + !assignType( + paramInfo.type, + defaultArgType, + diag?.createAddendum(), + constraints, + flags, + recursionCount + ) + ) { + if ((flags & AssignTypeFlags.PartialOverloadOverlap) === 0) { + canAssign = false; + } + } + + continue; + } + + // If the source parameter is also addressable by keyword, it is OK + // that there is no matching positional parameter in the dest. + if (srcParam.kind === ParamKind.Standard) { + continue; + } + + // If the source parameter is a variadic, it is OK that there is no + // matching positional parameter in the dest. + if (srcParam.param.category === ParamCategory.ArgsList) { + continue; + } + + const nonDefaultSrcParamCount = srcParamDetails.params.filter( + (p) => !!p.param.name && !p.defaultType && p.param.category === ParamCategory.Simple + ).length; + + diag?.createAddendum().addMessage( + LocAddendum.functionTooManyParams().format({ + expected: destPositionalCount, + received: nonDefaultSrcParamCount, + }) + ); + canAssign = false; + break; + } + } else if (srcPositionalCount < destPositionalCount) { + if (srcParamDetails.argsIndex !== undefined) { + // Make sure the remaining dest parameters can be assigned to the source + // *args parameter type. + const srcArgsType = srcParamDetails.params[srcParamDetails.argsIndex].type; + for (let paramIndex = srcPositionalCount; paramIndex < destPositionalCount; paramIndex++) { + if (paramIndex === srcParamDetails.argsIndex) { + continue; + } + + const destParamType = destParamDetails.params[paramIndex].type; + if (isTypeVarTuple(destParamType) && !isTypeVarTuple(srcArgsType)) { + diag?.addMessage(LocAddendum.typeVarTupleRequiresKnownLength()); + canAssign = false; + } else { + if ( + !assignParam( + destParamType, + srcArgsType, + paramIndex, + diag?.createAddendum(), + constraints, + flags, + recursionCount + ) + ) { + canAssign = false; + } + + const destParamKind = destParamDetails.params[paramIndex].kind; + if ( + destParamKind !== ParamKind.Positional && + destParamKind !== ParamKind.ExpandedArgs && + srcParamDetails.kwargsIndex === undefined + ) { + diag?.addMessage( + LocAddendum.namedParamMissingInSource().format({ + name: destParamDetails.params[paramIndex].param.name ?? '', + }) + ); + canAssign = false; + } + } + } + } else if (!srcParamDetails.paramSpec) { + // If the dest contains a *args, remove it from the positional count + // because it's OK for zero source args to match it. + let adjDestPositionalCount = destPositionalCount; + if (destParamDetails.argsIndex !== undefined && destParamDetails.argsIndex < destPositionalCount) { + adjDestPositionalCount--; + } + + // If we're doing a partial overload overlap check, ignore dest positional + // params with default values. + if ((flags & AssignTypeFlags.PartialOverloadOverlap) !== 0) { + while ( + adjDestPositionalCount > 0 && + destParamDetails.params[adjDestPositionalCount - 1].defaultType + ) { + adjDestPositionalCount--; + } + } + + if (srcPositionalCount < adjDestPositionalCount) { + diag?.addMessage( + LocAddendum.functionTooFewParams().format({ + expected: adjDestPositionalCount, + received: srcPositionalCount, + }) + ); + canAssign = false; + } + } + } + + // If both src and dest have an "*args" parameter, make sure + // their types are compatible. + if ( + srcParamDetails.argsIndex !== undefined && + destParamDetails.argsIndex !== undefined && + !FunctionType.isGradualCallableForm(destType) + ) { + let destArgsType = destParamDetails.params[destParamDetails.argsIndex].type; + let srcArgsType = srcParamDetails.params[srcParamDetails.argsIndex].type; + + if (!isUnpacked(destArgsType)) { + destArgsType = makeTupleObject( + evaluatorInterface, + [{ type: destArgsType, isUnbounded: true }], + /* isUnpacked */ true + ); + } + + if (!isUnpacked(srcArgsType)) { + srcArgsType = makeTupleObject( + evaluatorInterface, + [{ type: srcArgsType, isUnbounded: true }], + /* isUnpacked */ true + ); + } + + if ( + !assignParam( + destArgsType, + srcArgsType, + destParamDetails.params[destParamDetails.argsIndex].index, + diag?.createAddendum(), + constraints, + flags, + recursionCount + ) + ) { + canAssign = false; + } + } + + // If the dest has an "*args" but the source doesn't, report the incompatibility. + // The converse situation is OK. + if ( + !FunctionType.isGradualCallableForm(destType) && + srcParamDetails.argsIndex === undefined && + srcParamSpec === undefined && + destParamDetails.argsIndex !== undefined && + !destParamDetails.hasUnpackedTypeVarTuple + ) { + diag?.createAddendum().addMessage( + LocAddendum.argsParamMissing().format({ + paramName: destParamDetails.params[destParamDetails.argsIndex].param.name ?? '', + }) + ); + canAssign = false; + } + + // Handle matching of named (keyword) parameters. + if (!targetIncludesParamSpec) { + // Build a dictionary of named parameters in the dest. + const destParamMap = new Map(); + + if (destParamDetails.firstKeywordOnlyIndex !== undefined) { + destParamDetails.params.forEach((param, index) => { + if (index >= destParamDetails.firstKeywordOnlyIndex!) { + if ( + param.param.name && + param.param.category === ParamCategory.Simple && + param.kind !== ParamKind.Positional && + param.kind !== ParamKind.ExpandedArgs + ) { + destParamMap.set(param.param.name, param); + } + } + }); + } + + // If the dest has fewer positional arguments than the source, the remaining + // positional arguments in the source can be treated as named arguments. + let srcStartOfNamed = + srcParamDetails.firstKeywordOnlyIndex !== undefined + ? srcParamDetails.firstKeywordOnlyIndex + : srcParamDetails.params.length; + if (destPositionalCount < srcPositionalCount && destParamDetails.argsIndex === undefined) { + srcStartOfNamed = destPositionalCount; + } + + if (srcStartOfNamed >= 0) { + srcParamDetails.params.forEach((srcParamInfo, index) => { + if (index < srcStartOfNamed) { + return; + } + + if ( + !srcParamInfo.param.name || + srcParamInfo.param.category !== ParamCategory.Simple || + srcParamInfo.kind === ParamKind.Positional + ) { + return; + } + + const destParamInfo = destParamMap.get(srcParamInfo.param.name); + const paramDiag = diag?.createAddendum(); + const srcParamType = srcParamInfo.type; + + if (!destParamInfo) { + if (destParamDetails.kwargsIndex === undefined && !srcParamInfo.defaultType) { + if (paramDiag) { + paramDiag.addMessage( + LocAddendum.namedParamMissingInDest().format({ + name: srcParamInfo.param.name, + }) + ); + } + canAssign = false; + } else if (destParamDetails.kwargsIndex !== undefined) { + // Make sure we can assign the type to the Kwargs. + if ( + !assignParam( + destParamDetails.params[destParamDetails.kwargsIndex].type, + srcParamType, + destParamDetails.params[destParamDetails.kwargsIndex].index, + diag?.createAddendum(), + constraints, + flags, + recursionCount + ) + ) { + canAssign = false; + } + } else if (srcParamInfo.defaultType) { + // Assign default arg values in case they are needed for + // populating TypeVar constraints. + const defaultArgType = srcParamInfo.defaultType ?? srcParamInfo.defaultType; + + if ( + defaultArgType && + !assignType( + srcParamInfo.type, + defaultArgType, + diag?.createAddendum(), + constraints, + flags, + recursionCount + ) + ) { + if ((flags & AssignTypeFlags.PartialOverloadOverlap) === 0) { + canAssign = false; + } + } + } + return; + } + + // If we're performing a partial overload match and both the source + // and dest parameters provide defaults, assume that there could + // be a match. + if (srcParamInfo.defaultType && destParamInfo.defaultType) { + if ((flags & AssignTypeFlags.PartialOverloadOverlap) !== 0) { + destParamMap.delete(srcParamInfo.param.name); + return; + } + } + + const destParamType = destParamInfo.type; + const specializedDestParamType = constraints + ? solveAndApplyConstraints(destParamType, constraints) + : destParamType; + + if ( + !assignParam( + destParamInfo.type, + srcParamType, + /* paramIndex */ undefined, + paramDiag?.createAddendum(), + constraints, + flags, + recursionCount + ) + ) { + if (paramDiag) { + paramDiag.addMessage( + LocAddendum.namedParamTypeMismatch().format({ + name: srcParamInfo.param.name, + sourceType: printType(specializedDestParamType), + destType: printType(srcParamType), + }) + ); + } + canAssign = false; + } + + if (destParamInfo.defaultType && !srcParamInfo.defaultType) { + diag?.createAddendum().addMessage( + LocAddendum.functionParamDefaultMissing().format({ + name: srcParamInfo.param.name, + }) + ); + canAssign = false; + } + + destParamMap.delete(srcParamInfo.param.name); + }); + } + + // See if there are any unmatched named parameters. + destParamMap.forEach((destParamInfo, paramName) => { + if (srcParamDetails.kwargsIndex !== undefined && destParamInfo.param.name) { + // Make sure the src kwargs type is compatible. + if ( + !assignParam( + destParamInfo.type, + srcParamDetails.params[srcParamDetails.kwargsIndex].type, + destParamInfo.index, + diag?.createAddendum(), + constraints, + flags, + recursionCount + ) + ) { + canAssign = false; + } + destParamMap.delete(paramName); + } else { + diag?.createAddendum().addMessage( + LocAddendum.namedParamMissingInSource().format({ name: paramName }) + ); + canAssign = false; + } + }); + + // If both src and dest have a "**kwargs" parameter, make sure their types are compatible. + if (srcParamDetails.kwargsIndex !== undefined && destParamDetails.kwargsIndex !== undefined) { + if ( + !assignParam( + destParamDetails.params[destParamDetails.kwargsIndex].type, + srcParamDetails.params[srcParamDetails.kwargsIndex].type, + destParamDetails.params[destParamDetails.kwargsIndex].index, + diag?.createAddendum(), + constraints, + flags, + recursionCount + ) + ) { + canAssign = false; + } + } + + // If the dest has a "**kwargs" but the source doesn't, report the incompatibility. + // The converse situation is OK. + if ( + !FunctionType.isGradualCallableForm(destType) && + srcParamDetails.kwargsIndex === undefined && + srcParamSpec === undefined && + destParamDetails.kwargsIndex !== undefined + ) { + diag?.createAddendum().addMessage( + LocAddendum.kwargsParamMissing().format({ + paramName: destParamDetails.params[destParamDetails.kwargsIndex].param.name!, + }) + ); + canAssign = false; + } + } + + if ((flags & AssignTypeFlags.OverloadOverlap) !== 0) { + // If we're checking for full overlapping overloads and the source is + // a gradual form, the dest must also be a gradual form. + if (FunctionType.isGradualCallableForm(srcType) && !FunctionType.isGradualCallableForm(destType)) { + canAssign = false; + } + + // If the src contains a ParamSpec the dest must also. + if (srcParamSpec && !destParamSpec) { + canAssign = false; + } + } + + // If the source and the dest are using the same ParamSpec, any additional + // concatenated parameters must match. + if (targetIncludesParamSpec && srcParamSpec?.priv.nameWithScope === destParamSpec?.priv.nameWithScope) { + if (srcParamDetails.params.length !== destParamDetails.params.length) { + canAssign = false; + } + } + + // Are we assigning to a function with a ParamSpec? + if (targetIncludesParamSpec) { + const effectiveSrcType = isContra ? destType : srcType; + const effectiveDestType = isContra ? srcType : destType; + + const effectiveSrcParamSpec = isContra ? destParamSpec : srcParamSpec; + const effectiveDestParamSpec = isContra ? srcParamSpec : destParamSpec; + + if (effectiveDestParamSpec) { + const requiredMatchParamCount = effectiveDestType.shared.parameters.filter((p, i) => { + if (!p.name) { + return false; + } + + const paramType = FunctionType.getParamType(effectiveDestType, i); + if (p.category === ParamCategory.Simple && isParamSpec(paramType)) { + return false; + } + return true; + }).length; + let matchedParamCount = 0; + const remainingParams: FunctionParam[] = []; + + // If there are parameters in the source that are not matched + // to parameters in the dest, assume these are concatenated on + // to the ParamSpec. + effectiveSrcType.shared.parameters.forEach((p, index) => { + if (matchedParamCount < requiredMatchParamCount) { + if (p.name) { + matchedParamCount++; + } + + // If this is a *args parameter, assume that it provides + // the remaining positional parameters, but also assume + // that it is not exhausted and can provide additional + // parameters. + if (p.category !== ParamCategory.ArgsList) { + return; + } + } + + if (isPositionOnlySeparator(p) && remainingParams.length === 0) { + // Don't bother pushing a position-only separator if it + // is the first remaining param. + return; + } + + remainingParams.push( + FunctionParam.create( + p.category, + FunctionType.getParamType(effectiveSrcType, index), + p.flags, + p.name, + FunctionType.getParamDefaultType(effectiveSrcType, index), + p.defaultExpr + ) + ); + }); + + // If there are remaining parameters and the source and dest do not contain + // the same ParamSpec, synthesize a function for the remaining parameters. + if ( + remainingParams.length > 0 || + !effectiveSrcParamSpec || + !isTypeSame(effectiveSrcParamSpec, effectiveDestParamSpec, { ignoreTypeFlags: true }) + ) { + const effectiveSrcPosCount = isContra ? destPositionalCount : srcPositionalCount; + const effectiveDestPosCount = isContra ? srcPositionalCount : destPositionalCount; + + // If the src and dest both have ParamSpecs but the src has additional positional + // parameters that have not been matched to dest positional parameters (probably due + // to a Concatenate), don't attempt to assign the remaining parameters to the ParamSpec. + if (!effectiveSrcParamSpec || effectiveSrcPosCount >= effectiveDestPosCount) { + const remainingFunction = FunctionType.createInstance( + '', + '', + '', + effectiveSrcType.shared.flags | FunctionTypeFlags.SynthesizedMethod, + effectiveSrcType.shared.docString + ); + remainingFunction.shared.deprecatedMessage = effectiveSrcType.shared.deprecatedMessage; + remainingFunction.shared.typeVarScopeId = effectiveSrcType.shared.typeVarScopeId; + remainingFunction.priv.constructorTypeVarScopeId = + effectiveSrcType.priv.constructorTypeVarScopeId; + remainingFunction.shared.methodClass = effectiveSrcType.shared.methodClass; + remainingParams.forEach((param) => { + FunctionType.addParam(remainingFunction, param); + }); + if (effectiveSrcParamSpec) { + FunctionType.addParamSpecVariadics( + remainingFunction, + convertToInstance(effectiveSrcParamSpec) + ); + } + + if ( + !assignType( + effectiveDestParamSpec, + remainingFunction, + /* diag */ undefined, + constraints, + flags + ) + ) { + // If we couldn't assign the function to the ParamSpec, see if we can + // assign only the ParamSpec. This is possible if there were no + // remaining parameters. + if ( + remainingParams.length > 0 || + !effectiveSrcParamSpec || + !assignType( + convertToInstance(effectiveDestParamSpec), + convertToInstance(effectiveSrcParamSpec), + /* diag */ undefined, + constraints, + flags + ) + ) { + canAssign = false; + } + } + } + } + } + } + + // Match the return parameter. + if (checkReturnType) { + const destReturnType = getEffectiveReturnType(destType); + if (!isAnyOrUnknown(destReturnType)) { + const srcReturnType = solveAndApplyConstraints(getEffectiveReturnType(srcType), constraints); + const returnDiag = diag?.createAddendum(); + + let isReturnTypeCompatible = false; + + let effectiveFlags = flags; + + // If the source has a declared return type that includes a literal + // in its annotation, assume that we will want the constraint + // solver to retain literals. + if ( + srcType.shared.declaredReturnType && + containsLiteralType(srcType.shared.declaredReturnType, /* includeTypeArgs */ true) + ) { + effectiveFlags |= AssignTypeFlags.RetainLiteralsForTypeVar; + } + + if ( + assignType( + destReturnType, + srcReturnType, + returnDiag?.createAddendum(), + constraints, + effectiveFlags, + recursionCount + ) + ) { + isReturnTypeCompatible = true; + } else { + // Handle the special case where the return type is a TypeGuard[T] + // or TypeIs[T]. This should also act as a bool, since that's its + // type at runtime. + if ( + isClassInstance(srcReturnType) && + ClassType.isBuiltIn(srcReturnType, ['TypeGuard', 'TypeIs']) && + prefetched?.boolClass && + isInstantiableClass(prefetched.boolClass) + ) { + if ( + assignType( + destReturnType, + ClassType.cloneAsInstance(prefetched.boolClass), + returnDiag?.createAddendum(), + constraints, + flags, + recursionCount + ) + ) { + isReturnTypeCompatible = true; + } + } + } + + if (!isReturnTypeCompatible) { + if (returnDiag) { + returnDiag.addMessage( + LocAddendum.functionReturnTypeMismatch().format({ + sourceType: printType(srcReturnType), + destType: printType(destReturnType), + }) + ); + } + canAssign = false; + } + } + } + + return canAssign; + } + + // When a value is assigned to a variable with a declared type, + // we may be able to narrow the type based on the assignment. + function narrowTypeBasedOnAssignment(declaredType: Type, assignedTypeResult: TypeResult): TypeResult { + // TODO: The rules for narrowing types on assignment are not defined in + // the typing spec. Vipeye's current logic is currently not even internally + // consistent and probably not sound from a type theory perspective. It + // should be completely reworked once there has been a public discussion + // about the correct behavior. + + const narrowedType = mapSubtypes(assignedTypeResult.type, (assignedSubtype) => { + // Handle the special case where the assigned type is a literal type. + // Some types include very large unions of literal types, and we don't + // want to use an n^2 loop to compare them. + if (isClass(assignedSubtype) && isLiteralType(assignedSubtype)) { + if (isUnion(declaredType) && UnionType.containsType(declaredType, assignedSubtype)) { + return assignedSubtype; + } + } + + const narrowedSubtype = mapSubtypes(declaredType, (declaredSubtype) => { + if (!assignType(declaredSubtype, assignedSubtype)) { + return undefined; + } + + // Retain unknowns for code flow analysis convergence and for + // unknown type reporting in strict mode. + if (isUnknown(assignedSubtype)) { + return assignedSubtype; + } + + // If the two types are bidirectionally assignable, they are + // either equivalent (in which case it doesn't matter which + // one we choose) or one or both include gradual types (Any, etc.), + // in which case we'll want to stick with the declared subtype. + if (assignType(assignedSubtype, declaredSubtype)) { + // We need to be careful with TypedDict types that have + // narrowed fields. In this case, we want to return the + // assigned type. + if ( + isClass(assignedSubtype) && + assignedSubtype.priv.typedDictNarrowedEntries && + isTypeSame(assignedSubtype, declaredSubtype, { ignoreTypedDictNarrowEntries: true }) + ) { + return assignedSubtype; + } + + // We also need to be careful with callback protocols. + if (isClassInstance(declaredSubtype) && ClassType.isProtocolClass(declaredSubtype)) { + if (isFunctionOrOverloaded(assignedSubtype)) { + return assignedSubtype; + } + } + + return declaredSubtype; + } + + return assignedSubtype; + }); + + // If we couldn't assign the assigned subtype any of the declared + // subtypes, the types are incompatible. Return the unnarrowed form. + if (isNever(narrowedSubtype)) { + return assignedSubtype; + } + + return narrowedSubtype; + }); + + // If the result of narrowing is an Unknown that is incomplete, propagate the + // incomplete type for the benefit of code flow analysis. + // If the result of narrowing is a complete Unknown, combine the Unknown type + // with the declared type. In strict mode, this will retain the "unknown type" + // diagnostics while still providing reasonable completion suggestions. + if (isIncompleteUnknown(narrowedType)) { + return { type: narrowedType, isIncomplete: assignedTypeResult.isIncomplete }; + } else if (isUnknown(narrowedType)) { + return { type: combineTypes([narrowedType, declaredType]), isIncomplete: assignedTypeResult.isIncomplete }; + } + + return { type: narrowedType, isIncomplete: assignedTypeResult.isIncomplete }; + } + + function validateOverrideMethod( + baseMethod: Type, + overrideMethod: FunctionType | OverloadedType, + baseClass: ClassType | undefined, + diag: DiagnosticAddendum, + enforceParamNames = true + ): boolean { + // If we're overriding a non-method with a method, report it as an error. + // This occurs when a non-property overrides a property. + if (!isFunctionOrOverloaded(baseMethod)) { + diag.addMessage(LocAddendum.overrideType().format({ type: printType(baseMethod) })); + return false; + } + + if (isFunction(baseMethod)) { + // Handle the easy case - a simple function overriding another simple function. + if (isFunction(overrideMethod)) { + return validateOverrideMethodInternal(baseMethod, overrideMethod, diag, enforceParamNames); + } + + const overloadsAndImpl = [...OverloadedType.getOverloads(overrideMethod)]; + const impl = OverloadedType.getImplementation(overrideMethod); + if (impl && isFunction(impl)) { + overloadsAndImpl.push(impl); + } + + // For an overload overriding a base method, at least one overload + // or the implementation must be compatible with the base method. + if ( + overloadsAndImpl.some((overrideOverload) => { + return validateOverrideMethodInternal( + baseMethod, + overrideOverload, + /* diag */ undefined, + enforceParamNames + ); + }) + ) { + return true; + } + + diag.addMessage(LocAddendum.overrideNoOverloadMatches()); + return false; + } + + // For a non-overloaded method overriding an overloaded method, the + // override must match all of the overloads. + if (isFunction(overrideMethod)) { + return OverloadedType.getOverloads(baseMethod).every((overload) => { + // If the override isn't applicable for this base class, skip the check. + if (baseClass && !isOverrideMethodApplicable(overload, baseClass)) { + return true; + } + + return validateOverrideMethodInternal( + overload, + overrideMethod, + diag?.createAddendum(), + enforceParamNames + ); + }); + } + + // For an overloaded method overriding an overloaded method, the overrides + // must all match and be in the correct order. It is OK if the base method + // has additional overloads that are not present in the override. + + let previousMatchIndex = -1; + const baseOverloads = OverloadedType.getOverloads(baseMethod); + + for (const overrideOverload of OverloadedType.getOverloads(overrideMethod)) { + let possibleMatchIndex: number | undefined; + + let matchIndex = baseOverloads.findIndex((baseOverload, index) => { + // If the override isn't applicable for this base class, skip the check. + if (baseClass && !isOverrideMethodApplicable(baseOverload, baseClass)) { + return false; + } + + const isCompatible = validateOverrideMethodInternal( + baseOverload, + overrideOverload, + /* diag */ undefined, + enforceParamNames + ); + + // If the override is compatible but the match is one that is below the previous + // matched index, keep looking for additional matches. Record the fact that + // we found at least one match. + if (isCompatible && index <= previousMatchIndex && possibleMatchIndex === undefined) { + possibleMatchIndex = index; + return false; + } + + return isCompatible; + }); + + if (matchIndex < 0 && possibleMatchIndex !== undefined) { + matchIndex = possibleMatchIndex; + } + + if (matchIndex < 0) { + break; + } + + if (matchIndex < previousMatchIndex) { + diag.addMessage(LocAddendum.overrideOverloadOrder()); + return false; + } + + previousMatchIndex = matchIndex; + } + + if (previousMatchIndex < baseOverloads.length - 1) { + const unmatchedOverloads = baseOverloads.slice(previousMatchIndex + 1); + + // See if all of the remaining overrides are nonapplicable. + if ( + !baseClass || + unmatchedOverloads.some((overload) => { + return isOverrideMethodApplicable(overload, baseClass); + }) + ) { + // We didn't find matches for all of the base overloads. + diag.addMessage(LocAddendum.overrideOverloadNoMatch()); + return false; + } + } + + return true; + } + + // Determines whether a child class override is applicable to a parent + // class method signature. This is important in cases where the parent + // class defines an overload where some of the overload signatures supply + // explicit type annotations for the "self" or "cls" parameter and some + // of these do not apply to the child class. + function isOverrideMethodApplicable(baseMethod: FunctionType, childClass: ClassType): boolean { + if ( + !FunctionType.isInstanceMethod(baseMethod) && + !FunctionType.isClassMethod(baseMethod) && + !FunctionType.isConstructorMethod(baseMethod) + ) { + return true; + } + + const baseParamDetails = getParamListDetails(baseMethod); + if (baseParamDetails.params.length === 0) { + return true; + } + + const baseParamType = baseParamDetails.params[0].param; + + if (baseParamType.category !== ParamCategory.Simple || !FunctionParam.isTypeDeclared(baseParamType)) { + return true; + } + + // If this is a self or cls parameter, determine whether the override + // class can be assigned to the base parameter type. If not, then this + // override doesn't apply. This is important for overloads where the + // base class contains some overload signatures that are not applicable + // to the child class. + const childSelfOrClsType = FunctionType.isInstanceMethod(baseMethod) + ? ClassType.cloneAsInstance(childClass) + : childClass; + + return assignType( + baseParamDetails.params[0].type, + childSelfOrClsType, + /* diag */ undefined, + /* constraints */ undefined, + AssignTypeFlags.Default + ); + } + + // Determines whether the override method is compatible with the overridden method. + // This is used both for parent/child overrides and implicit overrides for peer + // classes in a multi-inheritance case. If enforceParamNames is true, the parameter + // names of non-positional-only parameters are enforced. + function validateOverrideMethodInternal( + baseMethod: FunctionType, + overrideMethod: FunctionType, + diag: DiagnosticAddendum | undefined, + enforceParamNames: boolean + ): boolean { + const baseParamDetails = getParamListDetails(baseMethod); + const overrideParamDetails = getParamListDetails(overrideMethod); + const constraints = new ConstraintTracker(); + + let canOverride = true; + + if (!FunctionType.isGradualCallableForm(baseMethod) && !FunctionType.isGradualCallableForm(overrideMethod)) { + // Verify that we're not overriding a static, class or instance method with + // an incompatible type. + if (FunctionType.isStaticMethod(baseMethod)) { + if (!FunctionType.isStaticMethod(overrideMethod)) { + diag?.addMessage(LocAddendum.overrideNotStaticMethod()); + canOverride = false; + } + } else if (FunctionType.isClassMethod(baseMethod)) { + if (!FunctionType.isClassMethod(overrideMethod)) { + diag?.addMessage(LocAddendum.overrideNotClassMethod()); + canOverride = false; + } + } else if (FunctionType.isInstanceMethod(baseMethod)) { + if (!FunctionType.isInstanceMethod(overrideMethod)) { + diag?.addMessage(LocAddendum.overrideNotInstanceMethod()); + canOverride = false; + } + } + + // Verify that the positional param count matches exactly or that the override + // adds only params that preserve the original signature. + let foundParamCountMismatch = false; + if (overrideParamDetails.positionParamCount < baseParamDetails.positionParamCount) { + if (overrideParamDetails.argsIndex === undefined) { + foundParamCountMismatch = true; + } else { + const overrideArgsType = overrideParamDetails.params[overrideParamDetails.argsIndex].type; + for ( + let i = overrideParamDetails.positionParamCount; + i < baseParamDetails.positionParamCount; + i++ + ) { + if ( + !assignType( + overrideArgsType, + baseParamDetails.params[i].type, + diag?.createAddendum(), + constraints, + AssignTypeFlags.Default + ) + ) { + LocAddendum.overrideParamType().format({ + index: i + 1, + baseType: printType(baseParamDetails.params[i].type), + overrideType: printType(overrideArgsType), + }); + canOverride = false; + } + } + } + } else if (overrideParamDetails.positionParamCount > baseParamDetails.positionParamCount) { + const isCallableAttrOverriddenByInstanceMethod = + baseParamDetails.positionParamCount === 0 && + overrideParamDetails.positionParamCount === 1 && + FunctionType.isInstanceMethod(overrideMethod) && + !FunctionType.isStaticMethod(baseMethod) && + !FunctionType.isClassMethod(baseMethod); + + if (!isCallableAttrOverriddenByInstanceMethod) { + // Verify that all of the override parameters that extend the + // signature are either *args, **kwargs or parameters with + // default values. + + for ( + let i = baseParamDetails.positionParamCount; + i < overrideParamDetails.positionParamCount; + i++ + ) { + const overrideParam = overrideParamDetails.params[i].param; + + if ( + overrideParam.category === ParamCategory.Simple && + overrideParam.name && + !overrideParamDetails.params[i].defaultType + ) { + foundParamCountMismatch = true; + } + } + } + } + + if (foundParamCountMismatch) { + diag?.addMessage( + LocAddendum.overridePositionalParamCount().format({ + baseCount: baseParamDetails.params.length, + overrideCount: overrideParamDetails.params.length, + }) + ); + canOverride = false; + } + + const positionalParamCount = Math.min( + baseParamDetails.positionParamCount, + overrideParamDetails.positionParamCount + ); + + for (let i = 0; i < positionalParamCount; i++) { + // If the first parameter is a "self" or "cls" parameter, skip the + // test because these are allowed to violate the Liskov substitution + // principle. + // + // Note: the checker deliberately defeats this skip for the + // "callable variable overridden by a method" case by marking both + // sides static before calling this routine (see + // `_getCallableVariableOverrideComparison` in checker.ts). Keep the + // two in sync if this skip logic changes. + if (i === 0) { + if ( + FunctionType.isInstanceMethod(overrideMethod) || + FunctionType.isClassMethod(overrideMethod) || + FunctionType.isConstructorMethod(overrideMethod) + ) { + continue; + } + } + + const baseParam = baseParamDetails.params[i].param; + const overrideParam = overrideParamDetails.params[i].param; + + if ( + i >= baseParamDetails.positionOnlyParamCount && + !isPrivateOrProtectedName(baseParam.name || '') && + baseParamDetails.params[i].kind !== ParamKind.Positional && + baseParam.category === ParamCategory.Simple && + enforceParamNames && + baseParam.name !== overrideParam.name + ) { + if (overrideParam.category === ParamCategory.Simple) { + if (!FunctionParam.isNameSynthesized(baseParam)) { + if (overrideParamDetails.params[i].kind === ParamKind.Positional) { + diag?.addMessage( + LocAddendum.overrideParamNamePositionOnly().format({ + index: i + 1, + baseName: baseParam.name || '*', + }) + ); + } else { + diag?.addMessage( + LocAddendum.overrideParamName().format({ + index: i + 1, + baseName: baseParam.name || '*', + overrideName: overrideParam.name || '*', + }) + ); + } + canOverride = false; + } + } + } else if ( + i < overrideParamDetails.positionOnlyParamCount && + i >= baseParamDetails.positionOnlyParamCount + ) { + if ( + !FunctionParam.isNameSynthesized(baseParam) && + baseParamDetails.params[i].kind !== ParamKind.Positional && + baseParamDetails.params[i].kind !== ParamKind.ExpandedArgs + ) { + diag?.addMessage( + LocAddendum.overrideParamNamePositionOnly().format({ + index: i + 1, + baseName: baseParam.name || '*', + }) + ); + canOverride = false; + } + } else { + const baseParamType = baseParamDetails.params[i].type; + const overrideParamType = overrideParamDetails.params[i].type; + + const baseIsSynthesizedTypeVar = isTypeVar(baseParamType) && baseParamType.shared.isSynthesized; + const overrideIsSynthesizedTypeVar = + isTypeVar(overrideParamType) && overrideParamType.shared.isSynthesized; + + if (!baseIsSynthesizedTypeVar && !overrideIsSynthesizedTypeVar) { + if ( + baseParam.category !== overrideParam.category || + !assignType( + overrideParamType, + baseParamType, + diag?.createAddendum(), + constraints, + AssignTypeFlags.Default + ) + ) { + diag?.addMessage( + LocAddendum.overrideParamType().format({ + index: i + 1, + baseType: printType(baseParamType), + overrideType: printType(overrideParamType), + }) + ); + canOverride = false; + } + } + + if (baseParamDetails.params[i].defaultType && !overrideParamDetails.params[i].defaultType) { + diag?.addMessage( + LocAddendum.overrideParamNoDefault().format({ + index: i + 1, + }) + ); + canOverride = false; + } + } + } + + // Check for positional (named) parameters in the base method that + // do not exist in the override. + if (enforceParamNames && overrideParamDetails.kwargsIndex === undefined) { + for (let i = positionalParamCount; i < baseParamDetails.positionParamCount; i++) { + const baseParam = baseParamDetails.params[i]; + + if (baseParam.kind === ParamKind.Standard && baseParam.param.category === ParamCategory.Simple) { + diag?.addMessage( + LocAddendum.overrideParamNamePositionOnly().format({ + index: i + 1, + baseName: baseParam.param.name || '*', + }) + ); + canOverride = false; + } + } + } + + // Check for a *args match. + if (baseParamDetails.argsIndex !== undefined) { + if (overrideParamDetails.argsIndex === undefined) { + diag?.addMessage( + LocAddendum.overrideParamNameMissing().format({ + name: baseParamDetails.params[baseParamDetails.argsIndex].param.name ?? '?', + }) + ); + canOverride = false; + } else { + const overrideParamType = overrideParamDetails.params[overrideParamDetails.argsIndex].type; + const baseParamType = baseParamDetails.params[baseParamDetails.argsIndex].type; + + if ( + !assignType( + overrideParamType, + baseParamType, + diag?.createAddendum(), + constraints, + AssignTypeFlags.Default + ) + ) { + diag?.addMessage( + LocAddendum.overrideParamKeywordType().format({ + name: overrideParamDetails.params[overrideParamDetails.argsIndex].param.name ?? '?', + baseType: printType(baseParamType), + overrideType: printType(overrideParamType), + }) + ); + canOverride = false; + } + } + } + + // Now check any keyword-only parameters. + const baseKwOnlyParams = baseParamDetails.params.filter( + (paramInfo) => paramInfo.kind === ParamKind.Keyword && paramInfo.param.category === ParamCategory.Simple + ); + const overrideKwOnlyParams = overrideParamDetails.params.filter( + (paramInfo) => paramInfo.kind === ParamKind.Keyword && paramInfo.param.category === ParamCategory.Simple + ); + + baseKwOnlyParams.forEach((paramInfo) => { + const overrideParamInfo = overrideKwOnlyParams.find((pi) => paramInfo.param.name === pi.param.name); + + if (!overrideParamInfo && overrideParamDetails.kwargsIndex === undefined) { + diag?.addMessage( + LocAddendum.overrideParamNameMissing().format({ + name: paramInfo.param.name ?? '?', + }) + ); + canOverride = false; + } else { + let targetParamType = overrideParamInfo?.type; + if (!targetParamType) { + targetParamType = overrideParamDetails.params[overrideParamDetails.kwargsIndex!].type; + } + + if ( + !assignType( + targetParamType, + paramInfo.type, + diag?.createAddendum(), + constraints, + AssignTypeFlags.Default + ) + ) { + diag?.addMessage( + LocAddendum.overrideParamKeywordType().format({ + name: paramInfo.param.name ?? '?', + baseType: printType(paramInfo.type), + overrideType: printType(targetParamType), + }) + ); + canOverride = false; + } + + if (overrideParamInfo) { + if (paramInfo.defaultType && !overrideParamInfo.defaultType) { + diag?.addMessage( + LocAddendum.overrideParamKeywordNoDefault().format({ + name: overrideParamInfo.param.name ?? '?', + }) + ); + canOverride = false; + } + } + } + }); + + // Verify that any keyword-only parameters added by the overload are compatible + // with the **kwargs in the base. + overrideKwOnlyParams.forEach((paramInfo) => { + const baseParamInfo = baseKwOnlyParams.find((pi) => paramInfo.param.name === pi.param.name); + + if (!baseParamInfo) { + if (baseParamDetails.kwargsIndex === undefined) { + if (!paramInfo.defaultType) { + diag?.addMessage( + LocAddendum.overrideParamNameExtra().format({ + name: paramInfo.param.name ?? '?', + }) + ); + canOverride = false; + } + } else { + // Base has a **kwargs; ensure the added keyword-only parameter's + // type is compatible with the base's **kwargs value type. + const baseKwargsType = baseParamDetails.params[baseParamDetails.kwargsIndex].type; + if ( + !assignType( + paramInfo.type, + baseKwargsType, + diag?.createAddendum(), + constraints, + AssignTypeFlags.Default + ) + ) { + diag?.addMessage( + LocAddendum.overrideParamKeywordType().format({ + name: paramInfo.param.name ?? '?', + baseType: printType(baseKwargsType), + overrideType: printType(paramInfo.type), + }) + ); + canOverride = false; + } + } + } + }); + + // Verify that if the base method has a **kwargs parameter, the override does too. + if (baseParamDetails.kwargsIndex !== undefined && overrideParamDetails.kwargsIndex === undefined) { + diag?.addMessage( + LocAddendum.kwargsParamMissing().format({ + paramName: baseParamDetails.params[baseParamDetails.kwargsIndex].param.name!, + }) + ); + canOverride = false; + } + } + + // Verify that one or the other method doesn't contain a ParamSpec. + if (baseParamDetails.paramSpec && !overrideParamDetails.paramSpec) { + // If the override uses an `*args: Any, **kwargs: Any` signature, we + // will allow this as an acceptable overload for a `*args: P.args, **kwargs: P.kwargs`. + const overrideHasArgsKwargs = + overrideParamDetails.argsIndex !== undefined && + isAnyOrUnknown(overrideParamDetails.params[overrideParamDetails.argsIndex].type) && + overrideParamDetails.kwargsIndex !== undefined && + isAnyOrUnknown(overrideParamDetails.params[overrideParamDetails.kwargsIndex].type); + + if (!overrideHasArgsKwargs) { + diag?.addMessage(LocAddendum.paramSpecMissingInOverride()); + canOverride = false; + } + } + + // Now check the return type. + const baseReturnType = getEffectiveReturnType(baseMethod); + const overrideReturnType = solveAndApplyConstraints(getEffectiveReturnType(overrideMethod), constraints); + + if ( + !assignType( + baseReturnType, + overrideReturnType, + diag?.createAddendum(), + constraints, + AssignTypeFlags.Default + ) + ) { + diag?.addMessage( + LocAddendum.overrideReturnType().format({ + baseType: printType(baseReturnType), + overrideType: printType(overrideReturnType), + }) + ); + + canOverride = false; + } + + return canOverride; + } + + // Validates that the specified source type matches the constraints + // of the type variable. If successful, it returns the constraint + // type that applies. If unsuccessful, it returns undefined. + function applyTypeArgToTypeVar(destType: TypeVarType, srcType: Type, diag: DiagnosticAddendum): Type | undefined { + if (isAnyOrUnknown(srcType)) { + return srcType; + } + + let effectiveSrcType: Type = transformPossibleRecursiveTypeAlias(srcType); + + if (isTypeVar(srcType)) { + if (isTypeSame(srcType, destType)) { + return srcType; + } + + effectiveSrcType = makeTopLevelTypeVarsConcrete(srcType); + } + + // If this is a partially-evaluated class, don't perform any further + // checks. Assume in this case that the type is compatible with the + // bound or constraint. + if (isClass(effectiveSrcType) && ClassType.isPartiallyEvaluated(effectiveSrcType)) { + return srcType; + } + + // If there's a bound type, make sure the source is derived from it. + if (destType.shared.boundType && !isTypeAliasPlaceholder(effectiveSrcType)) { + if ( + !assignType( + destType.shared.boundType, + effectiveSrcType, + diag.createAddendum(), + /* constraints */ undefined + ) + ) { + // Avoid adding a message that will confuse users if the TypeVar was + // synthesized for internal purposes. + if (!destType.shared.isSynthesized) { + diag.addMessage( + LocAddendum.typeBound().format({ + sourceType: printType(effectiveSrcType), + destType: printType(destType.shared.boundType), + name: TypeVarType.getReadableName(destType), + }) + ); + } + return undefined; + } + } + + if (isParamSpec(destType)) { + if (isParamSpec(srcType)) { + return srcType; + } + + if (isFunction(srcType) && FunctionType.isParamSpecValue(srcType)) { + return srcType; + } + + if (isClassInstance(srcType) && ClassType.isBuiltIn(srcType, 'Concatenate')) { + return srcType; + } + + diag.addMessage( + LocAddendum.typeParamSpec().format({ + type: printType(srcType), + name: TypeVarType.getReadableName(destType), + }) + ); + + return undefined; + } + + if (isParamSpec(srcType)) { + diag.addMessage(LocMessage.paramSpecContext()); + return undefined; + } + + // If there are no constraints, we're done. + const constraints = destType.shared.constraints; + if (constraints.length === 0) { + return srcType; + } + + if (isTypeAliasPlaceholder(srcType)) { + return srcType; + } + + if (isTypeVar(srcType) && TypeVarType.hasConstraints(srcType)) { + // Make sure all the source constraint types map to constraint types in the dest. + if ( + srcType.shared.constraints.every((sourceConstraint) => { + return constraints.some((destConstraint) => assignType(destConstraint, sourceConstraint)); + }) + ) { + return srcType; + } + } else { + let bestConstraintSoFar: Type | undefined; + + // Try to find the best (narrowest) match among the constraints. + for (const constraint of constraints) { + if (assignType(constraint, effectiveSrcType)) { + // Don't allow Never to match unless the constraint is also explicitly Never. + if (!isNever(effectiveSrcType) || isNever(constraint)) { + if (!bestConstraintSoFar || assignType(bestConstraintSoFar, constraint)) { + bestConstraintSoFar = constraint; + } + } + } + } + + if (bestConstraintSoFar) { + return bestConstraintSoFar; + } + } + + diag.addMessage( + LocAddendum.typeConstrainedTypeVar().format({ + type: printType(srcType), + name: TypeVarType.getReadableName(destType), + }) + ); + + return undefined; + } + + // Returns a list of unimplemented abstract symbols (methods or variables) for + // the specified class. + function getAbstractSymbols(classType: ClassType): AbstractSymbol[] { + const symbolTable = new Map(); + + ClassType.getReverseMro(classType).forEach((mroClass) => { + if (isInstantiableClass(mroClass)) { + // See if this class is introducing a new abstract symbol that has not been + // introduced previously or if it is overriding an abstract symbol with + // a non-abstract one. + ClassType.getSymbolTable(mroClass).forEach((symbol, symbolName) => { + const abstractSymbolInfo = getAbstractSymbolInfo(mroClass, symbolName); + + if (abstractSymbolInfo) { + symbolTable.set(symbolName, abstractSymbolInfo); + } else { + symbolTable.delete(symbolName); + } + }); + } + }); + + // Create a final list of symbols that are abstract. + const symbolList: AbstractSymbol[] = []; + symbolTable.forEach((method) => { + symbolList.push(method); + }); + + return symbolList; + } + + // If the memberType is an instance or class method, creates a new + // version of the function that has the "self" or "cls" parameter bound + // to it. If treatConstructorAsClassMethod is true, the function is + // treated like a class method even if it's not marked as such. That's + // needed to special-case the __new__ magic method when it's invoked as + // a constructor (as opposed to by name). + function bindFunctionToClassOrObject( + baseType: ClassType | undefined, + memberType: FunctionType | OverloadedType, + memberClass?: ClassType, + treatConstructorAsClassMethod = false, + selfType?: ClassType | TypeVarType, + diag?: DiagnosticAddendum, + recursionCount = 0 + ): FunctionType | OverloadedType | undefined { + return mapSignatures(memberType, (functionType) => { + // If the caller specified no base type, always strip the + // first parameter. This is used in cases like constructors. + if (!baseType) { + return FunctionType.clone(functionType, /* stripFirstParam */ true); + } + + // If the first parameter was already stripped, it has already been + // bound. Don't attempt to rebind. + if (functionType.priv.strippedFirstParamType) { + return functionType; + } + + if (FunctionType.isInstanceMethod(functionType)) { + // If the baseType is a metaclass, don't specialize the function. + if (isInstantiableMetaclass(baseType)) { + return functionType; + } + + const baseObj: ClassType = isClassInstance(baseType) + ? baseType + : ClassType.cloneAsInstance(specializeWithDefaultTypeArgs(baseType)); + + let stripFirstParam = false; + if (isClassInstance(baseType)) { + stripFirstParam = true; + } else if (memberClass && isInstantiableMetaclass(memberClass)) { + stripFirstParam = true; + } + + return partiallySpecializeBoundMethod( + baseType, + functionType, + diag, + recursionCount, + selfType ?? baseObj, + stripFirstParam + ); + } + + if ( + FunctionType.isClassMethod(functionType) || + (treatConstructorAsClassMethod && FunctionType.isConstructorMethod(functionType)) + ) { + const baseClass = isInstantiableClass(baseType) ? baseType : ClassType.cloneAsInstantiable(baseType); + const clsType = selfType ? (convertToInstantiable(selfType) as ClassType | TypeVarType) : undefined; + + return partiallySpecializeBoundMethod( + baseClass, + functionType, + diag, + recursionCount, + clsType ?? baseClass, + /* stripFirstParam */ true + ); + } + + if (FunctionType.isStaticMethod(functionType)) { + const baseClass = isInstantiableClass(baseType) ? baseType : ClassType.cloneAsInstantiable(baseType); + + return partiallySpecializeBoundMethod( + baseClass, + functionType, + diag, + recursionCount, + /* firstParamType */ undefined, + /* stripFirstParam */ false + ); + } + + return functionType; + }); + } + + // Specializes the specified function for the specified class, + // optionally stripping the first first parameter (the "self" or "cls") + // off of the specialized function in the process. The baseType + // is the type used to reference the member. + function partiallySpecializeBoundMethod( + baseType: ClassType, + memberType: FunctionType, + diag: DiagnosticAddendum | undefined, + recursionCount: number, + firstParamType: ClassType | TypeVarType | undefined, + stripFirstParam = true + ): FunctionType | undefined { + const constraints = new ConstraintTracker(); + + if (firstParamType) { + if (memberType.shared.parameters.length > 0) { + const memberTypeFirstParam = memberType.shared.parameters[0]; + const memberTypeFirstParamType = FunctionType.getParamType(memberType, 0); + + if ( + isTypeVar(memberTypeFirstParamType) && + memberTypeFirstParamType.shared.boundType && + isClassInstance(memberTypeFirstParamType.shared.boundType) && + ClassType.isProtocolClass(memberTypeFirstParamType.shared.boundType) + ) { + // Handle the protocol class specially. Some protocol classes + // contain references to themselves or their subclasses, so if + // we attempt to call assignType, we'll risk infinite recursion. + // Instead, we'll assume it's assignable. + constraints.setBounds( + memberTypeFirstParamType, + TypeBase.isInstantiable(memberTypeFirstParamType) + ? convertToInstance(firstParamType) + : firstParamType + ); + } else { + const subDiag = diag?.createAddendum(); + + // Protect against the case where a callback protocol is being + // bound to its own __call__ method but the first parameter + // is annotated with its own callable type. This can lead to + // infinite recursion. + if (isFunctionOrOverloaded(memberTypeFirstParamType)) { + if (isClassInstance(firstParamType) && ClassType.isProtocolClass(firstParamType)) { + if (subDiag) { + subDiag.addMessage( + LocMessage.bindTypeMismatch().format({ + type: printType(firstParamType), + methodName: memberType.shared.name || '', + paramName: memberTypeFirstParam.name || '__p0', + }) + ); + } + return undefined; + } + } + + if ( + !assignType( + memberTypeFirstParamType, + firstParamType, + subDiag?.createAddendum(), + constraints, + AssignTypeFlags.AllowUnspecifiedTypeArgs, + recursionCount + ) + ) { + if ( + memberTypeFirstParam.name && + !FunctionParam.isNameSynthesized(memberTypeFirstParam) && + FunctionParam.isTypeDeclared(memberTypeFirstParam) + ) { + if (subDiag) { + subDiag.addMessage( + LocMessage.bindTypeMismatch().format({ + type: printType(firstParamType), + methodName: memberType.shared.name || '', + paramName: memberTypeFirstParam.name, + }) + ); + } + return undefined; + } + } + } + } else { + const subDiag = diag?.createAddendum(); + if (subDiag) { + subDiag.addMessage( + LocMessage.bindParamMissing().format({ + methodName: memberType.shared.name || '', + }) + ); + } + return undefined; + } + } + + // Get the effective return type, which will have the side effect of lazily + // evaluating (and caching) the inferred return type if there is no defined return type. + getEffectiveReturnType(memberType); + + const specializedFunction = solveAndApplyConstraints(memberType, constraints); + if (isFunction(specializedFunction)) { + return FunctionType.clone(specializedFunction, stripFirstParam, baseType); + } + + if (isOverloaded(specializedFunction)) { + // For overloaded functions, use the first overload. This isn't + // strictly correct, but this is an extreme edge case. + return FunctionType.clone(OverloadedType.getOverloads(specializedFunction)[0], stripFirstParam, baseType); + } + + return undefined; + } + + function isFinalVariable(symbol: Symbol): boolean { + return symbol.getDeclarations().some((decl) => isFinalVariableDeclaration(decl)); + } + + function isFinalVariableDeclaration(decl: Declaration): boolean { + return decl.type === DeclarationType.Variable && !!decl.isFinal; + } + + function isExplicitTypeAliasDeclaration(decl: Declaration): boolean { + if (decl.type !== DeclarationType.Variable || !decl.typeAnnotationNode) { + return false; + } + + if ( + decl.typeAnnotationNode.nodeType !== ParseNodeType.Name && + decl.typeAnnotationNode.nodeType !== ParseNodeType.MemberAccess && + decl.typeAnnotationNode.nodeType !== ParseNodeType.StringList + ) { + return false; + } + + const fileInfo = AnalyzerNodeInfo.getFileInfo(decl.node); + + // Viper 扩展:t.CTypedef 快速路径。 + // CTypedef: TypeAlias = TypeAlias 在 vpi 中定义为变量, + // getTypeOfAnnotation 对 t.CTypedef 可能返回 Unknown 或非类实例, + // 导致后续检查失败。直接检查 typeAnnotationNode 文本,避免类型求值问题。 + // 这使得 UINT32PTR: t.CTypedef = t.CUInt32T | t.CPtr 被识别为显式类型别名。 + if ( + IsViperDialect(fileInfo.executionEnvironment.dialect) && + decl.typeAnnotationNode.nodeType === ParseNodeType.MemberAccess && + decl.typeAnnotationNode.d.member.d.value === 'CTypedef' + ) { + return true; + } + + const type = getTypeOfAnnotation(decl.typeAnnotationNode, { varTypeAnnotation: true, allowClassVar: true }); + if (!isClassInstance(type)) { + return false; + } + + // 标准类型别名注解:TypeAlias + if (ClassType.isBuiltIn(type, 'TypeAlias')) { + return true; + } + + // Viper 扩展:t.CTypedef 是 TypeAlias 的别名(CTypedef: TypeAlias = TypeAlias)。 + // 由于 t.CTypedef 来自 vipershed-fallback,没有 BuiltIn 标志, + // isBuiltIn(type, 'TypeAlias') 会返回 false。因此需要额外检查类型名称。 + // 这使得 irq_handler_t: t.CTypedef = t.Callable[[], t.CInt] 被识别为显式类型别名。 + if ( + IsViperDialect(fileInfo.executionEnvironment.dialect) && + (type.shared.name === 'CTypedef' || type.shared.fullName === 't.CTypedef') + ) { + return true; + } + + return false; + } + + function isPossibleTypeAliasDeclaration(decl: Declaration): boolean { + if (decl.type !== DeclarationType.Variable || !decl.typeAliasName || decl.typeAnnotationNode) { + return false; + } + + if (decl.node.parent?.nodeType !== ParseNodeType.Assignment) { + return false; + } + + // Perform a sanity check on the RHS expression. Some expression + // forms should never be considered legitimate for type aliases. + return isLegalTypeAliasExpressionForm(decl.node.parent.d.rightExpr, /* allowStrLiteral */ false); + } + + function isLegalTypeAliasExpressionForm(node: ExpressionNode, allowStrLiteral: boolean): boolean { + switch (node.nodeType) { + case ParseNodeType.Error: + case ParseNodeType.UnaryOperation: + case ParseNodeType.AssignmentExpression: + case ParseNodeType.TypeAnnotation: + case ParseNodeType.Await: + case ParseNodeType.Ternary: + case ParseNodeType.Unpack: + case ParseNodeType.Tuple: + case ParseNodeType.Call: + case ParseNodeType.Comprehension: + case ParseNodeType.Slice: + case ParseNodeType.Yield: + case ParseNodeType.YieldFrom: + case ParseNodeType.Lambda: + case ParseNodeType.Number: + case ParseNodeType.Dictionary: + case ParseNodeType.List: + case ParseNodeType.Set: + return false; + + case ParseNodeType.StringList: + case ParseNodeType.String: + return allowStrLiteral; + + case ParseNodeType.Constant: + return node.d.constType === KeywordType.None; + + case ParseNodeType.BinaryOperation: + return ( + node.d.operator === OperatorType.BitwiseOr && + isLegalTypeAliasExpressionForm(node.d.leftExpr, /* allowStrLiteral */ true) && + isLegalTypeAliasExpressionForm(node.d.rightExpr, /* allowStrLiteral */ true) + ); + + case ParseNodeType.Index: + return isLegalTypeAliasExpressionForm(node.d.leftExpr, allowStrLiteral); + + case ParseNodeType.MemberAccess: + return isLegalTypeAliasExpressionForm(node.d.leftExpr, allowStrLiteral); + } + + return true; + } + + function isLegalImplicitTypeAliasType(type: Type) { + // We explicitly exclude "..." and "Unknown". + if (isEllipsisType(type)) { + return false; + } + + if (isUnknown(type)) { + // If this is a union type, we'll assume that it was meant as a type + // alias even though all of the union subtypes are Unknown. + if (type.props?.specialForm && ClassType.isBuiltIn(type.props.specialForm, 'UnionType')) { + return true; + } + return false; + } + + // Look at the subtypes within the union. If any of them are not + // instantiable (other than "None" which is special-cased), it is + // not a legal type alias type. + let isLegal = true; + doForEachSubtype(type, (subtype) => { + if (!TypeBase.isInstantiable(subtype) && !isNoneInstance(subtype)) { + isLegal = false; + } + }); + + return isLegal; + } + + function isPossibleTypeAliasOrTypedDict(decl: Declaration) { + return isPossibleTypeAliasDeclaration(decl) || isPossibleTypeDictFactoryCall(decl); + } + + function isPossibleTypeDictFactoryCall(decl: Declaration) { + if ( + decl.type !== DeclarationType.Variable || + !decl.node.parent || + decl.node.parent.nodeType !== ParseNodeType.Assignment || + decl.node.parent.d.rightExpr?.nodeType !== ParseNodeType.Call + ) { + return false; + } + + const callLeftNode = decl.node.parent.d.rightExpr.d.leftExpr; + + // Use a simple heuristic to determine whether this is potentially + // a call to the TypedDict call. This avoids the expensive (and potentially + // recursive) call to getTypeOfExpression in cases where it's not needed. + if ( + (callLeftNode.nodeType === ParseNodeType.Name && callLeftNode.d.value) === 'TypedDict' || + (callLeftNode.nodeType === ParseNodeType.MemberAccess && + callLeftNode.d.member.d.value === 'TypedDict' && + callLeftNode.d.leftExpr.nodeType === ParseNodeType.Name) + ) { + // See if this is a call to TypedDict. We want to support + // recursive type references in a TypedDict call. + const callType = getTypeOfExpression(callLeftNode, EvalFlags.CallBaseDefaults).type; + + if (isInstantiableClass(callType) && ClassType.isBuiltIn(callType, 'TypedDict')) { + return true; + } + } + + return false; + } + + function printObjectTypeForClass(type: ClassType): string { + return TypePrinter.printObjectTypeForClass(type, evaluatorOptions.printTypeFlags, getEffectiveReturnType); + } + + function printFunctionParts(type: FunctionType, extraFlags?: TypePrinter.PrintTypeFlags): [string[], string] { + const flags = extraFlags ? evaluatorOptions.printTypeFlags | extraFlags : evaluatorOptions.printTypeFlags; + return TypePrinter.printFunctionParts(type, flags, getEffectiveReturnType); + } + + // Prints two types and determines whether they need to be output in + // fully-qualified form for disambiguation. + function printSrcDestTypes( + srcType: Type, + destType: Type, + options?: PrintTypeOptions + ): { sourceType: string; destType: string } { + const simpleSrcType = printType(srcType, options); + const simpleDestType = printType(destType, options); + + if (simpleSrcType !== simpleDestType) { + return { sourceType: simpleSrcType, destType: simpleDestType }; + } + + const fullSrcType = printType(srcType, { ...(options ?? {}), useFullyQualifiedNames: true }); + const fullDestType = printType(destType, { ...(options ?? {}), useFullyQualifiedNames: true }); + + if (fullSrcType !== fullDestType) { + return { sourceType: fullSrcType, destType: fullDestType }; + } + + return { sourceType: simpleSrcType, destType: simpleDestType }; + } + + function printType(type: Type, options?: PrintTypeOptions): string { + let flags = evaluatorOptions.printTypeFlags; + + if (options?.expandTypeAlias) { + flags |= TypePrinter.PrintTypeFlags.ExpandTypeAlias; + } + if (options?.enforcePythonSyntax) { + flags |= TypePrinter.PrintTypeFlags.PythonSyntax; + } + if (options?.useTypingUnpack) { + flags |= TypePrinter.PrintTypeFlags.UseTypingUnpack; + } + if (options?.printUnknownWithAny) { + flags |= TypePrinter.PrintTypeFlags.PrintUnknownWithAny; + } + if (options?.printTypeVarVariance) { + flags |= TypePrinter.PrintTypeFlags.PrintTypeVarVariance; + } + if (options?.omitTypeArgsIfUnknown) { + flags |= TypePrinter.PrintTypeFlags.OmitTypeArgsIfUnknown; + } + if (options?.useFullyQualifiedNames) { + flags |= TypePrinter.PrintTypeFlags.UseFullyQualifiedNames; + } + if (options?.disablePep604) { + flags &= ~TypePrinter.PrintTypeFlags.PEP604; + } + + return TypePrinter.printType(type, flags, getEffectiveReturnType); + } + + // Calls back into the parser to parse the contents of a string literal. + // This is unfortunately needed in some cases — specifically where the + // parser couldn't determine that the string literal would be used in + // a context where it should be treated as a forward-declared type. This + // call produces an expression tree that is not attached to the main parse + // expression tree because we don't want to mutate the latter; the + // expression tree created by this function is therefore used only temporarily. + function parseStringAsTypeAnnotation(node: StringListNode, reportErrors: boolean): ExpressionNode | undefined { + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + const parser = new Parser(); + const textValue = node.d.strings[0].d.value; + + // Determine the offset within the file where the string + // literal's contents begin. + let valueOffset = node.d.strings[0].start; + if (node.d.strings[0].nodeType === ParseNodeType.String) { + valueOffset += node.d.strings[0].d.token.prefixLength + node.d.strings[0].d.token.quoteMarkLength; + } + + // Construct a temporary dummy string with the text value at the appropriate + // offset so as to mimic the original file. This will keep all of the token + // and diagnostic offsets correct. + const dummyFileContents = ' '.repeat(valueOffset) + textValue; + + const parseOptions = new ParseOptions(); + parseOptions.isStubFile = fileInfo.isStubFile; + parseOptions.pythonVersion = fileInfo.executionEnvironment.pythonVersion; + parseOptions.dialect = fileInfo.executionEnvironment.dialect; + parseOptions.reportErrorsForParsedStringContents = true; + + const parseResults = parser.parseTextExpression( + dummyFileContents, + valueOffset, + textValue.length, + parseOptions, + ParseTextMode.Expression, + /* initialParenDepth */ undefined, + fileInfo.typingSymbolAliases + ); + + if (parseResults.parseTree) { + // If there are errors but we are not reporting them, return + // undefined to indicate that the parse failed. + if (!reportErrors && parseResults.diagnostics.length > 0) { + return undefined; + } + + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + parseResults.diagnostics.forEach((diag) => { + fileInfo.diagnosticSink.addDiagnosticWithTextRange('error', diag.message, node); + }); + + parseResults.parseTree.parent = node; + + // Optionally add the new subtree to the parse tree so it can + // participate in language server operations like find and replace. + if (reportErrors) { + node.d.annotation = parseResults.parseTree; + } + + return parseResults.parseTree; + } + + return undefined; + } + + // Given a code flow node and a constrained TypeVar, determines whether that type + // var can be "narrowed" to a single one of its constraints based on isinstance + // checks within the code flow. + function narrowConstrainedTypeVar(node: ParseNode, typeVar: TypeVarType): Type | undefined { + const flowNode = AnalyzerNodeInfo.getFlowNode(node); + + if (!flowNode) { + return undefined; + } + + return codeFlowEngine.narrowConstrainedTypeVar(flowNode, typeVar); + } + + function getPrintExpressionTypesSpaces() { + return ' '.repeat(printExpressionSpaceCount); + } + + function getLineNum(node: ParseNode) { + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + const range = convertOffsetsToRange(node.start, node.start + node.length, fileInfo.lines); + return (range.start.line + 1).toString(); + } + + function printControlFlowGraph( + flowNode: FlowNode, + reference: CodeFlowReferenceExpressionNode | undefined, + callName: string, + logger: ConsoleInterface + ) { + return codeFlowEngine.printControlFlowGraph(flowNode, reference, callName, logger); + } + + // Track these apis internal usages when logging is on. otherwise, it should be noop. + const getInferredReturnTypeResult = wrapWithLogger(_getInferredReturnTypeResult); + + const evaluatorInterface: TypeEvaluator = { + runWithCancellationToken, + getType, + getTypeResult, + getTypeResultForDecorator, + getCachedType, + getTypeOfExpression, + getTypeOfAnnotation, + getTypeOfClass, + createSubclass, + getTypeOfFunction, + getTypeOfExpressionExpectingType, + getExpectedType, + evaluateTypeForSubnode, + evaluateTypesForStatement, + evaluateTypesForMatchStatement, + evaluateTypesForCaseStatement, + evaluateTypeOfParam, + canBeTruthy, + canBeFalsy, + stripLiteralValue, + removeTruthinessFromType, + removeFalsinessFromType, + stripTypeGuard, + solveAndApplyConstraints, + verifyRaiseExceptionType, + verifyDeleteExpression, + validateOverloadedArgTypes, + validateInitSubclassArgs, + isNodeReachable, + isAfterNodeReachable, + getNodeReachability, + getAfterNodeReachability, + isAsymmetricAccessorAssignment, + suppressDiagnostics, + isSpecialFormClass, + getDeclInfoForStringNode, + getDeclInfoForNameNode, + getTypeForDeclaration, + resolveAliasDeclaration, + resolveAliasDeclarationWithInfo, + getTypeOfIterable, + getTypeOfIterator, + getGetterTypeFromProperty, + getTypeOfArg, + convertNodeToArg, + buildTupleTypesList, + markNamesAccessed, + expandPromotionTypes, + makeTopLevelTypeVarsConcrete, + mapSubtypesExpandTypeVars, + isTypeSubsumedByOtherType, + lookUpSymbolRecursive, + getDeclaredTypeOfSymbol, + getEffectiveTypeOfSymbol, + getEffectiveTypeOfSymbolForUsage, + getInferredTypeOfDeclaration, + getDeclaredTypeForExpression, + getDeclaredReturnType, + getInferredReturnType, + getBestOverloadForArgs, + getBuiltInType, + getTypeOfMember, + getTypeOfBoundMember, + getBoundMagicMethod, + getTypeOfMagicMethodCall, + bindFunctionToClassOrObject, + getCallbackProtocolType, + getCallSignatureInfo, + getAbstractSymbols, + narrowConstrainedTypeVar, + isTypeComparable, + assignType, + validateOverrideMethod, + validateCallArgs, + validateTypeArg, + assignTypeToExpression, + assignClassToSelf, + getTypedDictClassType, + getTupleClassType, + getDictClassType, + getStrClassType, + getObjectType, + getNoneType, + getUnionClassType, + getTypeClassType, + getBuiltInObject, + getTypingType, + getTypeCheckerInternalsType, + assignTypeArgs, + reportMissingTypeArgs, + inferReturnTypeIfNecessary, + inferVarianceForClass, + isFinalVariable, + isFinalVariableDeclaration, + isExplicitTypeAliasDeclaration, + addInformation, + addUnreachableCode, + addDeprecated, + addDiagnostic, + addDiagnosticForTextRange, + printType, + printSrcDestTypes, + printFunctionParts, + getTypeCacheEntryCount, + disposeEvaluator, + useSpeculativeMode, + isSpeculativeModeInUse, + setTypeResultForNode, + checkForCancellation, + printControlFlowGraph, + }; + + const codeFlowEngine = getCodeFlowEngine(evaluatorInterface, speculativeTypeTracker); + + return evaluatorInterface; +} diff --git a/packages/vipeye-internal/src/tests/viperGenericTypeVarCall.test.ts b/packages/vipeye-internal/src/tests/viperGenericTypeVarCall.test.ts new file mode 100644 index 00000000..a5dbdc36 --- /dev/null +++ b/packages/vipeye-internal/src/tests/viperGenericTypeVarCall.test.ts @@ -0,0 +1,162 @@ +/* + * viperGenericTypeVarCall.test.ts + * + * 验证 Viper 泛型类方法中 `T(v)`(用类型变量做 C 风格强转)不应报 + * "Object of type TypeVar is not callable" / "Attribute __call__ is unknown"。 + * + * 场景:TransPyC/Test/GenericTest/App/main.vp + * class Box[T](): + * value: T + * def __init__(self, v: T): + * self.value = T(v) # ← T(v) 类型变量调用 + * + * 运行:npx jest src/tests/viperGenericTypeVarCall.test.ts --runInBand + */ + +import assert from 'assert'; +import * as path from 'path'; + +import { ImportResolver } from '../analyzer/importResolver'; +import { Program } from '../analyzer/program'; +import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions'; +import { NullConsole } from '../common/console'; +import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem'; +import { createServiceProvider } from '../common/serviceProviderExtensions'; +import { UriEx } from '../common/uri/uriUtils'; +import { FullAccessHost } from '../common/fullAccessHost'; + +// 允许 vipershed-fallback(t.vpi/c.vpi 存根)被定位。 +(global as any).__rootDirectory = path.resolve(); + +const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes'; +const GENERIC_TEST = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\Test\\GenericTest\\App\\main.vp'; + +function buildProgram(targetPath: string) { + const tempFile = new RealTempFile(); + const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile); + const projectRoot = UriEx.file(path.dirname(targetPath)); + const configOptions = new ConfigOptions(projectRoot); + configOptions.internalTestMode = true; + configOptions.initializeTypeCheckingMode('standard'); + + const execEnv = new ExecutionEnvironment( + 'python', + projectRoot, + configOptions.diagnosticRuleSet, + configOptions.defaultPythonVersion, + configOptions.defaultPythonPlatform, + [projectRoot], + /* skipNativeLibraries */ false, + /* defaultDialect */ 'viper' + ); + configOptions.executionEnvironments = [execEnv]; + + const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider)); + const program = new Program(importResolver, configOptions, serviceProvider); + + const uri = UriEx.file(targetPath); + program.setTrackedFiles([uri]); + while (program.analyze()) { + // 循环直到分析完成 + } + + return { program, serviceProvider, execEnv, uri, configOptions }; +} + +function getDiagnostics(program: Program, configOptions: ConfigOptions, uri: any): string[] { + const sourceFile = program.getSourceFile(uri); + assert(sourceFile, 'source file not found'); + return (sourceFile.getDiagnostics(configOptions) || []).map( + (diag) => `[category=${diag.category}] ${diag.message}` + ); +} + +test('GenericTest 泛型类 Box 中 T(v) 类型变量调用不报 not callable', () => { + const { program, serviceProvider, uri, configOptions } = buildProgram(GENERIC_TEST); + + const diagnostics = getDiagnostics(program, configOptions, uri); + console.log('=== GenericTest not-callable diagnostics ==='); + const notCallable = diagnostics.filter( + (d) => d.includes('not callable') || d.includes('not callable') || d.includes('不可调用') + ); + notCallable.forEach((d) => console.log(` ${d}`)); + + assert.strictEqual( + notCallable.length, + 0, + `unexpected not-callable errors: ${JSON.stringify(notCallable)}` + ); + + program.dispose(); + serviceProvider.dispose(); +}); + +test('最小复现:泛型类方法中 T(v) 类型变量调用不报 not callable', () => { + const tempFile = new RealTempFile(); + const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile); + const projectRoot = UriEx.file(TRANSPYC_INCLUDES); + const configOptions = new ConfigOptions(projectRoot); + configOptions.internalTestMode = true; + configOptions.initializeTypeCheckingMode('standard'); + + const execEnv = new ExecutionEnvironment( + 'python', + projectRoot, + configOptions.diagnosticRuleSet, + configOptions.defaultPythonVersion, + configOptions.defaultPythonPlatform, + [projectRoot], + /* skipNativeLibraries */ false, + /* defaultDialect */ 'viper' + ); + configOptions.executionEnvironments = [execEnv]; + + const code = `import t + +class Box[T](): + value: T + + def __init__(self, v: T): + self.value = T(v) + + def get(self) -> T: + return self.value + +def make_box(): + b = Box(42) + return b.get() +`; + const testDir = path.join(TRANSPYC_INCLUDES, '__typevar_call_test__'); + const fs = require('fs'); + fs.mkdirSync(testDir, { recursive: true }); + const testFile = path.join(testDir, 'typevar.vp'); + fs.writeFileSync(testFile, code, 'utf8'); + + try { + const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider)); + const program = new Program(importResolver, configOptions, serviceProvider); + const uri = UriEx.file(testFile); + program.setTrackedFiles([uri]); + while (program.analyze()) { + // 循环 + } + + const diagnostics = getDiagnostics(program, configOptions, uri); + console.log('=== typevar.vp diagnostics ==='); + diagnostics.forEach((d) => console.log(` ${d}`)); + + const notCallable = diagnostics.filter( + (d) => d.includes('not callable') || d.includes('不可调用') || d.includes('__call__') + ); + assert.strictEqual( + notCallable.length, + 0, + `unexpected not-callable errors: ${JSON.stringify(diagnostics)}` + ); + + program.dispose(); + } finally { + fs.rmSync(testDir, { recursive: true, force: true }); + } + serviceProvider.dispose(); +});