From a5d18411266937496da4852bf8bcbc04c07b4a65 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sun, 2 Aug 2026 03:29:41 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E8=B7=A8=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=20typedef=20=E7=AD=89=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _edit_float.py | 29 + .../vipeye-internal/src/analyzer/binder.ts | 21 + .../vipeye-internal/src/analyzer/checker.ts | 21 +- .../vipeye-internal/src/analyzer/enums.ts | 17 +- .../src/analyzer/importResolver.ts | 50 +- .../src/analyzer/operations.ts | 3034 +- .../src/analyzer/parseTreeUtils.ts | 108 +- .../vipeye-internal/src/analyzer/service.ts | 9 + .../src/analyzer/serviceUtils.ts | 26 + .../src/analyzer/typeEvaluator.ts | 59945 ++++++++-------- .../src/analyzer/typePrinter.ts | 11 +- .../vipeye-internal/src/analyzer/types.ts | 8651 +-- .../src/languageService/hoverProvider.ts | 1483 +- .../languageService/semanticTokenProvider.ts | 1454 +- .../src/localization/localize.ts | 16 + .../src/localization/package.nls.en-us.json | 4 + .../src/localization/package.nls.zh-cn.json | 4 + .../stdlib/_typeshed/__init__.vpi | 3 + .../vipershed-fallback/stdlib/argparse.vpi | 206 - .../vipershed-fallback/stdlib/c.vpi | 7 +- .../vipershed-fallback/stdlib/hashtable.vpi | 140 - .../vipershed-fallback/stdlib/linkedlist.vpi | 210 - .../vipershed-fallback/stdlib/memhub.vpi | 243 - .../vipershed-fallback/stdlib/stdint.vpi | 138 - .../vipershed-fallback/stdlib/stdio.vpi | 113 - .../vipershed-fallback/stdlib/stdlib.vpi | 121 - .../vipershed-fallback/stdlib/string.vpi | 218 - .../vipershed-fallback/stdlib/sys.vpi | 101 - .../vipershed-fallback/stdlib/t.vpi | 343 +- .../vipershed-fallback/stdlib/viperlib.vpi | 93 - packages/vscode-vipeye/package.json | 62 + .../syntaxes/viper.tmLanguage.json | 849 +- 32 files changed, 38618 insertions(+), 39112 deletions(-) create mode 100644 _edit_float.py delete mode 100644 packages/vipeye-internal/vipershed-fallback/stdlib/argparse.vpi delete mode 100644 packages/vipeye-internal/vipershed-fallback/stdlib/hashtable.vpi delete mode 100644 packages/vipeye-internal/vipershed-fallback/stdlib/linkedlist.vpi delete mode 100644 packages/vipeye-internal/vipershed-fallback/stdlib/memhub.vpi delete mode 100644 packages/vipeye-internal/vipershed-fallback/stdlib/stdint.vpi delete mode 100644 packages/vipeye-internal/vipershed-fallback/stdlib/stdio.vpi delete mode 100644 packages/vipeye-internal/vipershed-fallback/stdlib/stdlib.vpi delete mode 100644 packages/vipeye-internal/vipershed-fallback/stdlib/string.vpi delete mode 100644 packages/vipeye-internal/vipershed-fallback/stdlib/sys.vpi delete mode 100644 packages/vipeye-internal/vipershed-fallback/stdlib/viperlib.vpi diff --git a/_edit_float.py b/_edit_float.py new file mode 100644 index 00000000..08926bb4 --- /dev/null +++ b/_edit_float.py @@ -0,0 +1,29 @@ +path = r'd:\Users\TermiNexus\Desktop\Viper_VSCode\packages\vipeye-internal\src\analyzer\types.ts' +with open(path, 'r', encoding='utf-8') as f: + text = f.read() + +old1 = ( + "// Viper \u6269\u5c55\uff1aViper C \u6d6e\u70b9\u7c7b\u578b\u540d\u79f0\u767d\u540d\u5355\u3002\n" + "const _viperCFloatTypeNames = new Set([\n" + " 'CFloat', 'CDouble', 'CFloat8T', 'CFloat16T', 'CFloat32T', 'CFloat64T', 'CFloat128T',\n" + "]);\n" +) + +new1 = old1 + ( + "\n" + "// Viper \u6269\u5c55\uff1a\u5224\u65ad\u7c7b\u578b\u540d\u662f\u5426\u4e3a Viper C \u6d6e\u70b9\u7c7b\u578b\u3002\n" + "// \u7528\u4e8e\u534f\u8bae\u5339\u914d\u7b49\u573a\u666f\uff0c\u8ba9 CType \u6d6e\u70b9\u5b50\u7c7b\u6ee1\u8db3 SupportsFloat \u7b49\u6570\u503c\u534f\u8bae\u3002\n" + "export function isViperCFloatTypeName(name: string): boolean {\n" + " return _viperCFloatTypeNames.has(name);\n" + "}\n" +) + +c1 = text.count(old1) +print('Edit1 match count:', c1) +assert c1 == 1, f'Edit1 match count: {c1}' +text = text.replace(old1, new1) + +with open(path, 'w', encoding='utf-8') as f: + f.write(text) + +print('isViperCFloatTypeName function added.') diff --git a/packages/vipeye-internal/src/analyzer/binder.ts b/packages/vipeye-internal/src/analyzer/binder.ts index c596c983..79fff4d2 100644 --- a/packages/vipeye-internal/src/analyzer/binder.ts +++ b/packages/vipeye-internal/src/analyzer/binder.ts @@ -21,6 +21,7 @@ import { DiagnosticLevel } from '../common/configOptions'; import { assert, assertNever, fail } from '../common/debug'; import { CreateTypeStubFileAction, Diagnostic } from '../common/diagnostic'; import { DiagnosticRule } from '../common/diagnosticRules'; +import { IsViperDialect } from '../common/dialect'; import { stripFileExtension } from '../common/pathUtils'; import { convertTextRangeToRange } from '../common/positionUtils'; import { TextRange, getEmptyRange } from '../common/textRange'; @@ -426,6 +427,26 @@ export class Binder extends ParseTreeWalker { return true; } + // Viper 扩展:命名空间包(目录无 __init__.vp)标黄警告"疑似未完成"。 + // Viper 是 AOT 静态语言,__init__.vp 缺失可能意味着模块未编写完整。 + // 仅当导入的最终目标是目录(而非具体文件)时才标黄。 + const lastResolvedUri = importResult.resolvedUris[importResult.resolvedUris.length - 1]; + const isFinalTargetDirectory = !lastResolvedUri || lastResolvedUri.isEmpty(); + if ( + importResult.isImportFound && + importResult.isNamespacePackage && + isFinalTargetDirectory && + IsViperDialect(this._fileInfo.executionEnvironment.dialect) + ) { + this._fileInfo.diagnosticSink.addDiagnosticWithTextRange( + 'warning', + LocMessage.viperNamespacePackageIncomplete().format({ + importName: importResult.importName, + }), + node + ); + } + // See if a source file was found but it's not part of a py.typed // library and no type stub is found. let reportStubMissing = false; diff --git a/packages/vipeye-internal/src/analyzer/checker.ts b/packages/vipeye-internal/src/analyzer/checker.ts index 41df8f65..8d47701f 100644 --- a/packages/vipeye-internal/src/analyzer/checker.ts +++ b/packages/vipeye-internal/src/analyzer/checker.ts @@ -164,6 +164,7 @@ import { isOverloaded, isParamSpec, isPossiblyUnbound, + isPureCModifierType, isTypeSame, isTypeVar, isTypeVarTuple, @@ -4789,7 +4790,14 @@ export class Checker extends ParseTreeWalker { // The types of all return statement expressions were already checked // against the declared type, but we need to verify the implicit None // at the end of the function. - if (declaredReturnType && !functionNeverReturns && implicitlyReturnsNone) { + // Viper 扩展:返回类型包含 C 声明标记(t.State 等,等价 CExport | CExtern) + // 视为声明性函数(C extern 声明,如 w32 API 的 BOOL | t.State: pass),不要求返回值。 + const isPureModifierReturn = + IsViperDialect(this._fileInfo.executionEnvironment.dialect) && + declaredReturnType !== undefined && + isPureCModifierType(declaredReturnType); + + if (declaredReturnType && !functionNeverReturns && implicitlyReturnsNone && !isPureModifierReturn) { if (isNever(declaredReturnType)) { // If the function consists entirely of "...", assume that it's // an abstract method or a protocol method and don't require that @@ -4980,9 +4988,15 @@ export class Checker extends ParseTreeWalker { } // Enum members should not have type annotations. + // Viper 扩展:CEnum 子类允许类型注解成员(如 FR_OK: t.State = 0), + // 跳过此错误报告。 const typedDecls = symbol.getTypedDeclarations(); if (typedDecls.length > 0) { - if (typedDecls[0].type === DeclarationType.Variable && typedDecls[0].inferredTypeSource) { + if ( + !IsViperDialect(this._fileInfo.executionEnvironment.dialect) && + typedDecls[0].type === DeclarationType.Variable && + typedDecls[0].inferredTypeSource + ) { this._evaluator.addDiagnostic( DiagnosticRule.reportGeneralTypeIssues, LocMessage.enumMemberTypeAnnotation(), @@ -5009,7 +5023,8 @@ export class Checker extends ParseTreeWalker { } // Look for an enum attribute annotated with "Final". - if (decls[0].isFinal) { + // Viper 扩展:CEnum 子类允许类型注解成员,跳过 Final 注解错误。 + if (decls[0].isFinal && !IsViperDialect(this._fileInfo.executionEnvironment.dialect)) { this._evaluator.addDiagnostic( DiagnosticRule.reportGeneralTypeIssues, LocMessage.enumMemberTypeAnnotation(), diff --git a/packages/vipeye-internal/src/analyzer/enums.ts b/packages/vipeye-internal/src/analyzer/enums.ts index 16bab708..7d649422 100644 --- a/packages/vipeye-internal/src/analyzer/enums.ts +++ b/packages/vipeye-internal/src/analyzer/enums.ts @@ -8,6 +8,7 @@ */ import { assert } from '../common/debug'; +import { IsViperDialect } from '../common/dialect'; import { PythonVersion, pythonVersion3_13 } from '../common/pythonVersion'; import { ArgCategory, ExpressionNode, NameNode, ParseNode, ParseNodeType } from '../parser/parseNodes'; import { getFileInfo } from './analyzerNodeInfo'; @@ -380,10 +381,24 @@ export function transformTypeForEnumMember( nameNode.parent?.nodeType === ParseNodeType.TypeAnnotation && nameNode.parent.d.valueExpr === nameNode ) { - if (ignoreAnnotation) { + // Viper 扩展:CEnum 子类的类型注解成员始终视为枚举成员。 + // Python 的 Enum 要求成员有赋值(如 RED = 1),但 Viper 的 CEnum + // 允许只用类型注解声明成员(如 RES_PARERR: State)。 + // 在 Viper 方言下,无论 ignoreAnnotation 如何,都设置 isMemberOfEnumeration = true。 + if (ignoreAnnotation || IsViperDialect(getFileInfo(nameNode).executionEnvironment.dialect)) { isMemberOfEnumeration = true; } declaredTypeNode = nameNode.parent.d.annotation; + + // Viper 扩展:处理"类型注解 + 赋值"组合(如 FR_OK: t.State = 0)。 + // 当 TypeAnnotation 的 parent 是 Assignment 时,提取赋值右侧表达式, + // 使 assignedType 能正确评估(如 0 → int)。declaredType(t.State)仍优先。 + if ( + nameNode.parent.parent?.nodeType === ParseNodeType.Assignment && + nameNode.parent.parent.d.leftExpr === nameNode.parent + ) { + valueTypeExprNode = nameNode.parent.parent.d.rightExpr; + } } // The spec specifically excludes names that start and end with a single underscore. diff --git a/packages/vipeye-internal/src/analyzer/importResolver.ts b/packages/vipeye-internal/src/analyzer/importResolver.ts index 64a88773..84e67cf7 100644 --- a/packages/vipeye-internal/src/analyzer/importResolver.ts +++ b/packages/vipeye-internal/src/analyzer/importResolver.ts @@ -357,9 +357,19 @@ export class ImportResolver { getImportRoots(execEnv: ExecutionEnvironment, forLogging = false) { const roots = []; - // Viper 模式优先搜索 vipershed-fallback/stdlib(Viper 特有模块:t/c/stdio 等), - // 再搜索 typeshed-fallback/stdlib(Python 通用模块:builtins/typing 等)。 - // typeshed root 已改为 typeshed-fallback,确保 builtins 可被隐式加载。 + // Viper 模式:项目 includes(.vpj 中配置的真实 .vp 源文件路径)必须最优先, + // 让 LSP 直接解析真实源码生成注解,而非依赖预写的 .vpi 存根。 + // The "default" workspace has a root-less execution environment; ignore it. + if (IsViperDialect(this._configOptions.dialect)) { + if (execEnv.root) { + roots.push(execEnv.root); + } + appendArray(roots, execEnv.extraPaths); + } + + // vipershed-fallback/stdlib 作为回退:仅提供 t.vpi/c.vpi 等无真实 .vp 源码 + // 的核心模块存根(t.py/c.py 是编译器实现,依赖内部模块无法被 LSP 解析)。 + // 排在项目 includes 之后,确保有真实 .vp 的模块优先用源码。 if (IsViperDialect(this._configOptions.dialect)) { const vipershed = PythonPathUtils.getVipershedFallbackPath(this.fileSystem); if (vipershed && !vipershed.isEmpty()) { @@ -370,6 +380,8 @@ export class ImportResolver { } } + // typeshed-fallback/stdlib 提供 Python 通用模块(builtins/typing 等)。 + // typeshed root 已改为 typeshed-fallback,确保 builtins 可被隐式加载。 const stdTypeshed = this._getStdlibTypeshedPath( this._configOptions.typeshedPath, execEnv.pythonVersion, @@ -379,13 +391,14 @@ export class ImportResolver { roots.push(stdTypeshed); } - // The "default" workspace has a root-less execution environment; ignore it. - if (execEnv.root) { - roots.push(execEnv.root); + // 非 Viper 模式:保持原有顺序(root 与 extraPaths 在 typeshed 之后)。 + if (!IsViperDialect(this._configOptions.dialect)) { + if (execEnv.root) { + roots.push(execEnv.root); + } + appendArray(roots, execEnv.extraPaths); } - appendArray(roots, execEnv.extraPaths); - if (this._configOptions.stubPath) { roots.push(this._configOptions.stubPath); } @@ -1546,19 +1559,14 @@ export class ImportResolver { continue; } - if (IsViperDialect(execEnv.dialect)) { - // Viper 模式:最后一部分目录存在但未找到模块文件 - // (__init__.vp 或 同名.vp),不退化为 namespace package, - // 直接中断解析使 isImportFound=false,让 binder 在 import - // 语句处报 reportMissingImports 错误。 - // Viper 是 AOT 静态语言,最终模块必须有实际源文件。 - importLogger?.log(`Viper import failed: directory '${dirPath}' has no module file`); - } else { - importLogger?.log(`Partially resolved import with directory '${dirPath}'`); - resolvedPaths.push(Uri.empty()); - implicitImports = this.findImplicitImports(importName, dirPath, [pyFilePath, pyiFilePath]); - isNamespacePackage = true; - } + // 最后一部分目录存在但未找到模块文件(__init__ 或同名 .vp), + // 退化为 namespace package,使 isImportFound=true。 + // Viper 与 Python 均允许此行为:便于在编写时提示文件夹内的 .vp/.py 库。 + // findImplicitImports 会扫描目录内的 .vp/.vpi/.py/.pyi 文件作为隐式导入。 + importLogger?.log(`Partially resolved import with directory '${dirPath}'`); + resolvedPaths.push(Uri.empty()); + implicitImports = this.findImplicitImports(importName, dirPath, [pyFilePath, pyiFilePath]); + isNamespacePackage = true; } if (!pyTypedInfo && lookForPyTyped) { diff --git a/packages/vipeye-internal/src/analyzer/operations.ts b/packages/vipeye-internal/src/analyzer/operations.ts index 12f9b57a..9bb1887d 100644 --- a/packages/vipeye-internal/src/analyzer/operations.ts +++ b/packages/vipeye-internal/src/analyzer/operations.ts @@ -1,1463 +1,1571 @@ -/* - * operations.ts - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT license. - * Author: Eric Traut - * - * Provides type evaluation logic for unary, binary, augmented assignment, - * and ternary operators. - */ - -import { DiagnosticAddendum } from '../common/diagnostic'; -import { DiagnosticRule } from '../common/diagnosticRules'; -import { PythonVersion, pythonVersion3_10 } from '../common/pythonVersion'; -import { IsViperDialect } from '../common/dialect'; -import { LocMessage } from '../localization/localize'; -import { - AugmentedAssignmentNode, - BinaryOperationNode, - ExpressionNode, - ParseNodeType, - TernaryNode, - UnaryOperationNode, -} from '../parser/parseNodes'; -import { OperatorType } from '../parser/tokenizerTypes'; -import { getFileInfo } from './analyzerNodeInfo'; -import { getEnclosingLambda, isWithinLoop, operatorSupportsChaining, printOperator } from './parseTreeUtils'; -import { getScopeForNode } from './scopeUtils'; -import { evaluateStaticBoolExpression } from './staticExpressions'; -import { EvalFlags, MagicMethodDeprecationInfo, TypeEvaluator, TypeResult } from './typeEvaluatorTypes'; -import { - InferenceContext, - convertToInstantiable, - getLiteralTypeClassName, - getTypeCondition, - getUnionSubtypeCount, - isNoneInstance, - isOptionalType, - isTupleClass, - isUnboundedTupleClass, - isUnionableType, - lookUpClassMember, - makeInferenceContext, - mapSubtypes, - preserveUnknown, - removeNoneFromUnion, - someSubtypes, - specializeTupleClass, - specializeWithDefaultTypeArgs, - transformPossibleRecursiveTypeAlias, -} from './typeUtils'; -import { - ClassType, - NeverType, - Type, - TypeBase, - UnknownType, - combineTypes, - isAnyOrUnknown, - isClass, - isClassInstance, - isCConstType, - isCPtrType, - isCStaticType, - isCVoidType, - isFunctionOrOverloaded, - isInstantiableClass, - isNever, - isUnion, -} from './types'; - -// Maps binary operators to the magic methods that implement them. -const binaryOperatorMap: { [operator: number]: [string, string] } = { - [OperatorType.Add]: ['__add__', '__radd__'], - [OperatorType.Subtract]: ['__sub__', '__rsub__'], - [OperatorType.Multiply]: ['__mul__', '__rmul__'], - [OperatorType.FloorDivide]: ['__floordiv__', '__rfloordiv__'], - [OperatorType.Divide]: ['__truediv__', '__rtruediv__'], - [OperatorType.Mod]: ['__mod__', '__rmod__'], - [OperatorType.Power]: ['__pow__', '__rpow__'], - [OperatorType.MatrixMultiply]: ['__matmul__', '__rmatmul__'], - [OperatorType.BitwiseAnd]: ['__and__', '__rand__'], - [OperatorType.BitwiseOr]: ['__or__', '__ror__'], - [OperatorType.BitwiseXor]: ['__xor__', '__rxor__'], - [OperatorType.LeftShift]: ['__lshift__', '__rlshift__'], - [OperatorType.RightShift]: ['__rshift__', '__rrshift__'], - [OperatorType.Equals]: ['__eq__', '__eq__'], - [OperatorType.NotEquals]: ['__ne__', '__ne__'], - [OperatorType.LessThan]: ['__lt__', '__gt__'], - [OperatorType.LessThanOrEqual]: ['__le__', '__ge__'], - [OperatorType.GreaterThan]: ['__gt__', '__lt__'], - [OperatorType.GreaterThanOrEqual]: ['__ge__', '__le__'], -}; - -// Map of operators that always return a bool result. -const booleanOperatorMap: { [operator: number]: true } = { - [OperatorType.And]: true, - [OperatorType.Or]: true, - [OperatorType.Is]: true, - [OperatorType.IsNot]: true, - [OperatorType.In]: true, - [OperatorType.NotIn]: true, -}; - -interface BinaryOperationOptions { - isLiteralMathAllowed?: boolean; - isTupleAddAllowed?: boolean; -} - -// If the number of subtypes starts to explode when applying "literal math", -// cut off the literal union and fall back to the non-literal supertype. -const maxLiteralMathSubtypeCount = 64; - -export function validateBinaryOperation( - evaluator: TypeEvaluator, - operator: OperatorType, - leftTypeResult: TypeResult, - rightTypeResult: TypeResult, - errorNode: ExpressionNode, - inferenceContext: InferenceContext | undefined, - diag: DiagnosticAddendum, - options: BinaryOperationOptions -): TypeResult { - const leftType = leftTypeResult.type; - const rightType = rightTypeResult.type; - const isIncomplete = !!leftTypeResult.isIncomplete || !!rightTypeResult.isIncomplete; - let type: Type | undefined; - let concreteLeftType = evaluator.makeTopLevelTypeVarsConcrete(leftType); - let deprecatedInfo: MagicMethodDeprecationInfo | undefined; - - if (booleanOperatorMap[operator] !== undefined) { - // If it's an AND or OR, we need to handle short-circuiting by - // eliminating any known-truthy or known-falsy types. - if (operator === OperatorType.And) { - // If the LHS evaluates to falsy, the And expression will - // always return the type of the left-hand side. - if (!evaluator.canBeTruthy(concreteLeftType)) { - return { type: leftType }; - } - - // If the LHS evaluates to truthy, the And expression will - // always return the type of the right-hand side. - if (!evaluator.canBeFalsy(concreteLeftType)) { - return { type: rightType }; - } - - concreteLeftType = evaluator.removeTruthinessFromType(concreteLeftType); - - if (isNever(rightType)) { - return { type: concreteLeftType }; - } - } else if (operator === OperatorType.Or) { - // If the LHS evaluates to truthy, the Or expression will - // always return the type of the left-hand side. - if (!evaluator.canBeFalsy(concreteLeftType)) { - return { type: leftType }; - } - - // If the LHS evaluates to falsy, the Or expression will - // always return the type of the right-hand side. - if (!evaluator.canBeTruthy(concreteLeftType)) { - return { type: rightType }; - } - - concreteLeftType = evaluator.removeFalsinessFromType(concreteLeftType); - - if (isNever(rightType)) { - return { type: concreteLeftType }; - } - } - - if (isNever(leftType) || isNever(rightType)) { - return { type: NeverType.createNever() }; - } - - // The "in" and "not in" operators make use of the __contains__ - // magic method. - if (operator === OperatorType.In || operator === OperatorType.NotIn) { - const result = validateContainmentOperation( - evaluator, - operator, - leftTypeResult, - concreteLeftType, - rightTypeResult, - errorNode, - diag - ); - - if (result.magicMethodDeprecationInfo) { - deprecatedInfo = result.magicMethodDeprecationInfo; - } - - type = result.type; - - // Assume that a bool is returned even if the type is unknown. - if (type && !isNever(type)) { - type = evaluator.getBuiltInObject(errorNode, 'bool'); - } - } else { - type = evaluator.mapSubtypesExpandTypeVars( - concreteLeftType, - /* options */ undefined, - (leftSubtypeExpanded, leftSubtypeUnexpanded) => { - return evaluator.mapSubtypesExpandTypeVars( - rightType, - { conditionFilter: getTypeCondition(leftSubtypeExpanded) }, - (rightSubtypeExpanded, rightSubtypeUnexpanded) => { - // If the operator is an AND or OR, we need to combine the two types. - if (operator === OperatorType.And || operator === OperatorType.Or) { - return combineTypes([leftSubtypeUnexpanded, rightSubtypeUnexpanded]); - } - // The other boolean operators always return a bool value. - return evaluator.getBuiltInObject(errorNode, 'bool'); - } - ); - } - ); - } - } else if (binaryOperatorMap[operator]) { - if (isNever(leftType) || isNever(rightType)) { - return { type: NeverType.createNever() }; - } - - // Handle certain operations on certain homogenous literal types - // using special-case math. For example, Literal[1, 2] + Literal[3, 4] - // should result in Literal[4, 5, 6]. - if (options.isLiteralMathAllowed) { - type = calcLiteralForBinaryOp(operator, leftType, rightType); - } - - if (!type) { - const result = validateArithmeticOperation( - evaluator, - operator, - leftTypeResult, - rightTypeResult, - errorNode, - inferenceContext, - diag, - options - ); - - if (result.magicMethodDeprecationInfo) { - deprecatedInfo = result.magicMethodDeprecationInfo; - } - - type = result.type; - } - } - - return { type: type ?? UnknownType.create(isIncomplete), magicMethodDeprecationInfo: deprecatedInfo }; -} - -export function getTypeOfBinaryOperation( - evaluator: TypeEvaluator, - node: BinaryOperationNode, - flags: EvalFlags, - inferenceContext: InferenceContext | undefined -): TypeResult { - const leftExpression = node.d.leftExpr; - let rightExpression = node.d.rightExpr; - let isIncomplete = false; - let typeErrors = false; - - // If this is a comparison and the left expression is also a comparison, - // we need to change the behavior to accommodate python's "chained - // comparisons" feature. - if (operatorSupportsChaining(node.d.operator)) { - if ( - rightExpression.nodeType === ParseNodeType.BinaryOperation && - !rightExpression.d.hasParens && - operatorSupportsChaining(rightExpression.d.operator) - ) { - // Evaluate the right expression so it is type checked. - getTypeOfBinaryOperation(evaluator, rightExpression, flags, inferenceContext); - - // Use the left side of the right expression for comparison purposes. - rightExpression = rightExpression.d.leftExpr; - } - } - - // For most binary operations, the "expected type" is applied to the output - // of the magic method for that operation. However, the "or" and "and" operators - // have no magic method, so we apply the expected type directly to both operands. - let expectedOperandType = - node.d.operator === OperatorType.Or || node.d.operator === OperatorType.And - ? inferenceContext?.expectedType - : undefined; - - // Handle the very special case where the expected type is a list - // and the operator is a multiply. This comes up in the common case - // of "x: List[Optional[X]] = [None] * y" where y is an integer literal. - let expectedLeftOperandType: Type | undefined; - if ( - node.d.operator === OperatorType.Multiply && - inferenceContext && - isClassInstance(inferenceContext.expectedType) && - ClassType.isBuiltIn(inferenceContext.expectedType, 'list') && - inferenceContext.expectedType.priv.typeArgs && - inferenceContext.expectedType.priv.typeArgs.length >= 1 && - node.d.leftExpr.nodeType === ParseNodeType.List - ) { - expectedLeftOperandType = inferenceContext.expectedType; - } - - const effectiveExpectedType = expectedOperandType ?? expectedLeftOperandType; - const leftTypeResult = evaluator.getTypeOfExpression( - leftExpression, - flags, - makeInferenceContext(effectiveExpectedType) - ); - let leftType = leftTypeResult.type; - - if (!expectedOperandType) { - if (node.d.operator === OperatorType.Or || node.d.operator === OperatorType.And) { - // For "or" and "and", use the type of the left operand under certain - // circumstances. This allows us to infer a better type for expressions - // like `x or []`. Do this only if it's a generic class (like list or dict) - // or a TypedDict. - if ( - someSubtypes(leftType, (subtype) => { - if (!isClassInstance(subtype)) { - return false; - } - - return ClassType.isTypedDictClass(subtype) || subtype.shared.typeParams.length > 0; - }) - ) { - expectedOperandType = leftType; - } - } else if (node.d.operator === OperatorType.Add && node.d.rightExpr.nodeType === ParseNodeType.List) { - // For the "+" operator , use this technique only if the right operand is - // a list expression. This heuristic handles the common case of `my_list + [0]`. - expectedOperandType = leftType; - } else if (node.d.operator === OperatorType.BitwiseOr) { - // If this is a bitwise or ("|"), use the type of the left operand. This allows - // us to support the case where a TypedDict is being updated with a dict expression. - if (isClassInstance(leftType) && ClassType.isTypedDictClass(leftType)) { - expectedOperandType = leftType; - } - } - } - - const rightTypeResult = evaluator.getTypeOfExpression( - rightExpression, - flags, - makeInferenceContext(expectedOperandType) - ); - let rightType = rightTypeResult.type; - - if (leftTypeResult.isIncomplete || rightTypeResult.isIncomplete) { - isIncomplete = true; - } - - // Is this a "|" operator used in a context where it is supposed to be - // interpreted as a union operator? - if ( - node.d.operator === OperatorType.BitwiseOr && - !customMetaclassSupportsMethod(leftType, '__or__') && - !customMetaclassSupportsMethod(rightType, '__ror__') - ) { - let adjustedRightType = rightType; - let adjustedLeftType = leftType; - if (!isNoneInstance(leftType) && isNoneInstance(rightType)) { - // Handle the special case where "None" is being added to the union - // with something else. Even though "None" will normally be interpreted - // as the None singleton object in contexts where a type annotation isn't - // assumed, we'll allow it here. - adjustedRightType = convertToInstantiable(evaluator.getNoneType()); - } else if (!isNoneInstance(rightType) && isNoneInstance(leftType)) { - adjustedLeftType = convertToInstantiable(evaluator.getNoneType()); - } - - if (isUnionableType([adjustedLeftType, adjustedRightType])) { - if (isInstantiableClass(adjustedLeftType)) { - adjustedLeftType = specializeWithDefaultTypeArgs(adjustedLeftType); - } - - if (isInstantiableClass(adjustedRightType)) { - adjustedRightType = specializeWithDefaultTypeArgs(adjustedRightType); - } - - return createUnionType( - evaluator, - node, - flags, - leftTypeResult, - rightTypeResult, - adjustedRightType, - adjustedLeftType - ); - } - } - - if ((flags & EvalFlags.TypeExpression) !== 0) { - // Exempt "|" because it might be a union operation involving unknowns. - if (node.d.operator !== OperatorType.BitwiseOr) { - evaluator.addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.binaryOperationNotAllowed(), node); - return { type: UnknownType.create() }; - } - } - - // Optional checks apply to all operations except for boolean operations. - let isLeftOptionalType = false; - if (booleanOperatorMap[node.d.operator] === undefined) { - // None is a valid operand for == and != even if the type stub says otherwise. - if (node.d.operator === OperatorType.Equals || node.d.operator === OperatorType.NotEquals) { - leftType = removeNoneFromUnion(leftType); - } else { - isLeftOptionalType = isOptionalType(leftType); - } - - // None is a valid operand for == and != even if the type stub says otherwise. - if (node.d.operator === OperatorType.Equals || node.d.operator === OperatorType.NotEquals) { - rightType = removeNoneFromUnion(rightType); - } - } - - const diag = new DiagnosticAddendum(); - - // Don't use literal math if the operation is within a loop - // because the literal values may change each time. We also don't want to - // apply literal math within the body of a lambda because they are often - // used as callbacks where the value changes each time they are called. - const isLiteralMathAllowed = !isWithinLoop(node) && !getEnclosingLambda(node); - - // Don't special-case tuple __add__ if the left type is a union. This - // can result in an infinite loop if we keep creating new tuple types - // within a loop construct using __add__. - const isTupleAddAllowed = !isUnion(leftType); - - const typeResult = validateBinaryOperation( - evaluator, - node.d.operator, - { type: leftType, isIncomplete: leftTypeResult.isIncomplete }, - { type: rightType, isIncomplete: rightTypeResult.isIncomplete }, - node, - inferenceContext, - diag, - { isLiteralMathAllowed, isTupleAddAllowed } - ); - - if (typeResult.isIncomplete) { - isIncomplete = true; - } - - if (!diag.isEmpty()) { - typeErrors = true; - - if (!isIncomplete) { - if (isLeftOptionalType && diag.getMessages().length === 1) { - // If the left was an optional type and there is just one diagnostic, - // assume that it was due to a "None" not being supported. Report - // this as a reportOptionalOperand diagnostic rather than a - // reportGeneralTypeIssues diagnostic. - evaluator.addDiagnostic( - DiagnosticRule.reportOptionalOperand, - LocMessage.noneOperator().format({ - operator: printOperator(node.d.operator), - }), - node.d.leftExpr - ); - } else { - // If neither the LHS or RHS are unions, don't include a diagnostic addendum - // because it will be redundant with the main diagnostic message. The addenda - // are useful only if union expansion was used for one or both operands. - let diagString = ''; - if ( - isUnion(evaluator.makeTopLevelTypeVarsConcrete(leftType)) || - isUnion(evaluator.makeTopLevelTypeVarsConcrete(rightType)) - ) { - diagString = diag.getString(); - } - - evaluator.addDiagnostic( - DiagnosticRule.reportOperatorIssue, - LocMessage.typeNotSupportBinaryOperator().format({ - operator: printOperator(node.d.operator), - leftType: evaluator.printType(leftType), - rightType: evaluator.printType(rightType), - }) + diagString, - node - ); - } - } - } - - return { - type: typeResult.type, - isIncomplete, - typeErrors, - magicMethodDeprecationInfo: typeResult.magicMethodDeprecationInfo, - }; -} - -export function getTypeOfAugmentedAssignment( - evaluator: TypeEvaluator, - node: AugmentedAssignmentNode, - inferenceContext: InferenceContext | undefined -): TypeResult { - const operatorMap: { [operator: number]: [string, OperatorType] } = { - [OperatorType.AddEqual]: ['__iadd__', OperatorType.Add], - [OperatorType.SubtractEqual]: ['__isub__', OperatorType.Subtract], - [OperatorType.MultiplyEqual]: ['__imul__', OperatorType.Multiply], - [OperatorType.FloorDivideEqual]: ['__ifloordiv__', OperatorType.FloorDivide], - [OperatorType.DivideEqual]: ['__itruediv__', OperatorType.Divide], - [OperatorType.ModEqual]: ['__imod__', OperatorType.Mod], - [OperatorType.PowerEqual]: ['__ipow__', OperatorType.Power], - [OperatorType.MatrixMultiplyEqual]: ['__imatmul__', OperatorType.MatrixMultiply], - [OperatorType.BitwiseAndEqual]: ['__iand__', OperatorType.BitwiseAnd], - [OperatorType.BitwiseOrEqual]: ['__ior__', OperatorType.BitwiseOr], - [OperatorType.BitwiseXorEqual]: ['__ixor__', OperatorType.BitwiseXor], - [OperatorType.LeftShiftEqual]: ['__ilshift__', OperatorType.LeftShift], - [OperatorType.RightShiftEqual]: ['__irshift__', OperatorType.RightShift], - }; - - let type: Type | undefined; - let typeResult: TypeResult | undefined; - const diag = new DiagnosticAddendum(); - let deprecatedInfo: MagicMethodDeprecationInfo | undefined; - - const leftTypeResult = evaluator.getTypeOfExpression(node.d.leftExpr); - const leftType = leftTypeResult.type; - - let expectedOperandType: Type | undefined; - if (node.d.operator === OperatorType.BitwiseOrEqual) { - // If this is a bitwise or ("|="), use the type of the left operand. This allows - // us to support the case where a TypedDict is being updated with a dict expression. - expectedOperandType = leftType; - } - - const rightTypeResult = evaluator.getTypeOfExpression( - node.d.rightExpr, - /* flags */ undefined, - makeInferenceContext(expectedOperandType) - ); - const rightType = rightTypeResult.type; - const isIncomplete = !!rightTypeResult.isIncomplete || !!leftTypeResult.isIncomplete; - - if (isNever(leftType) || isNever(rightType)) { - typeResult = { type: NeverType.createNever(), isIncomplete }; - } else { - type = evaluator.mapSubtypesExpandTypeVars( - leftType, - /* options */ undefined, - (leftSubtypeExpanded, leftSubtypeUnexpanded) => { - return evaluator.mapSubtypesExpandTypeVars( - rightType, - { conditionFilter: getTypeCondition(leftSubtypeExpanded) }, - (rightSubtypeExpanded, rightSubtypeUnexpanded) => { - if (isAnyOrUnknown(leftSubtypeUnexpanded) || isAnyOrUnknown(rightSubtypeUnexpanded)) { - return preserveUnknown(leftSubtypeUnexpanded, rightSubtypeUnexpanded); - } - - const magicMethodName = operatorMap[node.d.operator][0]; - let returnTypeResult = evaluator.getTypeOfMagicMethodCall( - leftSubtypeUnexpanded, - magicMethodName, - [{ type: rightSubtypeUnexpanded, isIncomplete: rightTypeResult.isIncomplete }], - node, - inferenceContext - ); - - if (!returnTypeResult && leftSubtypeUnexpanded !== leftSubtypeExpanded) { - // Try with the expanded left type. - returnTypeResult = evaluator.getTypeOfMagicMethodCall( - leftSubtypeExpanded, - magicMethodName, - [{ type: rightSubtypeUnexpanded, isIncomplete: rightTypeResult.isIncomplete }], - node, - inferenceContext - ); - } - - if (!returnTypeResult && rightSubtypeUnexpanded !== rightSubtypeExpanded) { - // Try with the expanded left and right type. - returnTypeResult = evaluator.getTypeOfMagicMethodCall( - leftSubtypeExpanded, - magicMethodName, - [{ type: rightSubtypeExpanded, isIncomplete: rightTypeResult.isIncomplete }], - node, - inferenceContext - ); - } - - if (!returnTypeResult) { - // If the LHS class didn't support the magic method for augmented - // assignment, fall back on the normal binary expression evaluator. - const binaryOperator = operatorMap[node.d.operator][1]; - - // Don't use literal math if the operation is within a loop - // because the literal values may change each time. - const isLiteralMathAllowed = - !isWithinLoop(node) && - isExpressionLocalVariable(evaluator, node.d.leftExpr) && - getUnionSubtypeCount(leftType) * getUnionSubtypeCount(rightType) < - maxLiteralMathSubtypeCount; - - // Don't special-case tuple __add__ if the left type is a union. This - // can result in an infinite loop if we keep creating new tuple types - // within a loop construct using __add__. - const isTupleAddAllowed = !isUnion(leftType); - - returnTypeResult = validateBinaryOperation( - evaluator, - binaryOperator, - { type: leftSubtypeUnexpanded, isIncomplete: leftTypeResult.isIncomplete }, - { type: rightSubtypeUnexpanded, isIncomplete: rightTypeResult.isIncomplete }, - node, - inferenceContext, - diag, - { isLiteralMathAllowed, isTupleAddAllowed } - ); - } - - if (returnTypeResult?.magicMethodDeprecationInfo) { - deprecatedInfo = returnTypeResult.magicMethodDeprecationInfo; - } - - return returnTypeResult?.type; - } - ); - } - ); - - // If the LHS class didn't support the magic method for augmented - // assignment, fall back on the normal binary expression evaluator. - if (!diag.isEmpty() || !type || isNever(type)) { - if (!isIncomplete) { - evaluator.addDiagnostic( - DiagnosticRule.reportOperatorIssue, - LocMessage.typeNotSupportBinaryOperator().format({ - operator: printOperator(node.d.operator), - leftType: evaluator.printType(leftType), - rightType: evaluator.printType(rightType), - }) + diag.getString(), - node - ); - } - } - - typeResult = { type, isIncomplete, magicMethodDeprecationInfo: deprecatedInfo }; - } - - evaluator.assignTypeToExpression(node.d.destExpr, typeResult, node.d.rightExpr); - - return typeResult; -} - -export function getTypeOfUnaryOperation( - evaluator: TypeEvaluator, - node: UnaryOperationNode, - flags: EvalFlags, - inferenceContext: InferenceContext | undefined -): TypeResult { - if ((flags & EvalFlags.TypeExpression) !== 0) { - evaluator.addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.unaryOperationNotAllowed(), node); - return { type: UnknownType.create() }; - } - - const exprTypeResult = evaluator.getTypeOfExpression(node.d.expr); - let exprType = evaluator.makeTopLevelTypeVarsConcrete(transformPossibleRecursiveTypeAlias(exprTypeResult.type)); - - const isIncomplete = exprTypeResult.isIncomplete; - - if (isNever(exprType)) { - return { type: NeverType.createNever(), isIncomplete }; - } - - // Map unary operators to magic functions. Note that the bitwise - // invert has two magic functions that are aliases of each other. - const unaryOperatorMap: { [operator: number]: string } = { - [OperatorType.Add]: '__pos__', - [OperatorType.Subtract]: '__neg__', - [OperatorType.BitwiseInvert]: '__invert__', - [OperatorType.Not]: '__bool__', - }; - - let type: Type | undefined; - let deprecatedInfo: MagicMethodDeprecationInfo | undefined; - - if (node.d.operator !== OperatorType.Not) { - if (isOptionalType(exprType)) { - evaluator.addDiagnostic( - DiagnosticRule.reportOptionalOperand, - LocMessage.noneOperator().format({ - operator: printOperator(node.d.operator), - }), - node.d.expr - ); - exprType = removeNoneFromUnion(exprType); - } - } - - // Handle certain operations on certain literal types - // using special-case math. Do not apply this if the input type - // is incomplete because we may be evaluating an expression within - // a loop, so the literal value may change each time. - if (!exprTypeResult.isIncomplete) { - type = calcLiteralForUnaryOp(node.d.operator, exprType); - } - - if (!type) { - if (isAnyOrUnknown(exprType)) { - type = exprType; - } else { - const magicMethodName = unaryOperatorMap[node.d.operator]; - let isResultValid = true; - - type = evaluator.mapSubtypesExpandTypeVars(exprType, /* options */ undefined, (subtypeExpanded) => { - const typeResult = evaluator.getTypeOfMagicMethodCall( - subtypeExpanded, - magicMethodName, - [], - node, - inferenceContext - ); - - if (!typeResult) { - isResultValid = false; - } - - if (typeResult?.magicMethodDeprecationInfo) { - deprecatedInfo = typeResult.magicMethodDeprecationInfo; - } - - return typeResult?.type; - }); - - if (!isResultValid) { - type = undefined; - } - } - - // __not__ always returns a boolean. - if (node.d.operator === OperatorType.Not) { - type = evaluator.getBuiltInObject(node, 'bool'); - if (!type) { - type = UnknownType.create(); - } - } - - if (!type) { - if (!isIncomplete) { - if (inferenceContext && !isAnyOrUnknown(inferenceContext.expectedType)) { - evaluator.addDiagnostic( - DiagnosticRule.reportOperatorIssue, - LocMessage.typeNotSupportUnaryOperatorBidirectional().format({ - operator: printOperator(node.d.operator), - type: evaluator.printType(exprType), - expectedType: evaluator.printType(inferenceContext.expectedType), - }), - node - ); - } else { - evaluator.addDiagnostic( - DiagnosticRule.reportOperatorIssue, - LocMessage.typeNotSupportUnaryOperator().format({ - operator: printOperator(node.d.operator), - type: evaluator.printType(exprType), - }), - node - ); - } - } - - type = UnknownType.create(isIncomplete); - } - } - - return { type, isIncomplete, magicMethodDeprecationInfo: deprecatedInfo }; -} - -export function getTypeOfTernaryOperation( - evaluator: TypeEvaluator, - node: TernaryNode, - flags: EvalFlags, - inferenceContext: InferenceContext | undefined -): TypeResult { - const fileInfo = getFileInfo(node); - - if ((flags & EvalFlags.TypeExpression) !== 0) { - evaluator.addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.ternaryNotAllowed(), node); - return { type: UnknownType.create() }; - } - - evaluator.getTypeOfExpression(node.d.testExpr); - - const typesToCombine: Type[] = []; - let isIncomplete = false; - let typeErrors = false; - - const constExprValue = evaluateStaticBoolExpression( - node.d.testExpr, - fileInfo.executionEnvironment, - fileInfo.definedConstants - ); - - if (constExprValue !== false && evaluator.isNodeReachable(node.d.ifExpr)) { - const ifType = evaluator.getTypeOfExpression(node.d.ifExpr, flags, inferenceContext); - typesToCombine.push(ifType.type); - if (ifType.isIncomplete) { - isIncomplete = true; - } - if (ifType.typeErrors) { - typeErrors = true; - } - } - - if (constExprValue !== true && evaluator.isNodeReachable(node.d.elseExpr)) { - const elseType = evaluator.getTypeOfExpression(node.d.elseExpr, flags, inferenceContext); - typesToCombine.push(elseType.type); - if (elseType.isIncomplete) { - isIncomplete = true; - } - if (elseType.typeErrors) { - typeErrors = true; - } - } - - return { type: combineTypes(typesToCombine), isIncomplete, typeErrors }; -} - -function createUnionType( - evaluator: TypeEvaluator, - node: BinaryOperationNode, - flags: EvalFlags, - leftTypeResult: TypeResult, - rightTypeResult: TypeResult, - adjustedRightType: Type, - adjustedLeftType: Type -): TypeResult { - const leftExpression = node.d.leftExpr; - const rightExpression = node.d.rightExpr; - const fileInfo = getFileInfo(node); - const unionNotationSupported = - fileInfo.isStubFile || - (flags & EvalFlags.ForwardRefs) !== 0 || - PythonVersion.isGreaterOrEqualTo(fileInfo.executionEnvironment.pythonVersion, pythonVersion3_10); - - if (!unionNotationSupported) { - // If the left type is Any, we can't say for sure whether this - // is an illegal syntax or a valid application of the "|" operator. - if (!isAnyOrUnknown(adjustedLeftType)) { - evaluator.addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.unionSyntaxIllegal(), - node, - node.d.operatorToken - ); - } - } - - const isLeftTypeArgValid = evaluator.validateTypeArg({ ...leftTypeResult, node: leftExpression }); - const isRightTypeArgValid = evaluator.validateTypeArg({ ...rightTypeResult, node: rightExpression }); - - if (!isLeftTypeArgValid || !isRightTypeArgValid) { - return { type: UnknownType.create() }; - } - - adjustedLeftType = evaluator.reportMissingTypeArgs( - node.d.leftExpr, - adjustedLeftType, - flags | EvalFlags.InstantiableType - ); - adjustedRightType = evaluator.reportMissingTypeArgs( - node.d.rightExpr, - adjustedRightType, - flags | EvalFlags.InstantiableType - ); - - let newUnion = combineTypes([adjustedLeftType, adjustedRightType], { skipElideRedundantLiterals: true }); - - // Viper 扩展:若 combineTypes 已将 Struct | CPtr 折叠为 CPtr[Struct], - // 则结果不再是真正的联合类型,不应附加 UnionType specialForm。 - // 否则后续调用 `(memory_block | t.CPtr)(addr)` 会被 specialForm 检查 - // 误报为 "Object of type \"UnionType\" is not callable"。 - const unionClass = evaluator.getUnionClassType(); - if ( - unionClass && - isInstantiableClass(unionClass) && - (flags & EvalFlags.IsinstanceArg) === 0 && - isUnion(newUnion) - ) { - newUnion = TypeBase.cloneAsSpecialForm(newUnion, ClassType.cloneAsInstance(unionClass)); - } - - if (leftTypeResult.type.props?.typeForm && rightTypeResult.type.props?.typeForm) { - const newTypeForm = combineTypes([leftTypeResult.type.props.typeForm, rightTypeResult.type.props.typeForm]); - newUnion = TypeBase.cloneWithTypeForm(newUnion, newTypeForm); - } - - // Check for "stringified" forward reference type expressions. The "|" operator - // doesn't support these except in certain circumstances. Notably, it can't be used - // with other strings or with types that are not specialized using an index form. - // Viper 扩展:跳过此检查。Viper 中 `'ClassName' | t.CPtr` 是合法的 TypeSet 语法, - // 表示"指向 ClassName 的指针"(前向引用字符串 + CPtr 组合)。 - if (!fileInfo.isStubFile && !IsViperDialect(fileInfo.executionEnvironment.dialect)) { - let stringNode: ExpressionNode | undefined; - let otherNode: ExpressionNode | undefined; - let otherType: Type | undefined; - - if (leftExpression.nodeType === ParseNodeType.StringList) { - stringNode = leftExpression; - otherNode = rightExpression; - otherType = rightTypeResult.type; - } else if (rightExpression.nodeType === ParseNodeType.StringList) { - stringNode = rightExpression; - otherNode = leftExpression; - otherType = leftTypeResult.type; - } - - if (stringNode && otherNode && otherType) { - let isAllowed = true; - if (isClass(otherType)) { - if (!otherType.priv.isTypeArgExplicit || isClassInstance(otherType)) { - isAllowed = false; - } - } - - if (!isAllowed) { - evaluator.addDiagnostic( - DiagnosticRule.reportGeneralTypeIssues, - LocMessage.unionForwardReferenceNotAllowed(), - stringNode - ); - } - } - } - - return { type: newUnion }; -} - -// Attempts to apply "literal math" for a literal operands. -function calcLiteralForUnaryOp(operator: OperatorType, operandType: Type): Type | undefined { - let type: Type | undefined; - - if (getUnionSubtypeCount(operandType) >= maxLiteralMathSubtypeCount) { - return undefined; - } - - if (!!getTypeCondition(operandType) || someSubtypes(operandType, (subtype) => !!getTypeCondition(subtype))) { - return undefined; - } - - const literalClassName = getLiteralTypeClassName(operandType); - - if (literalClassName === 'int') { - if (operator === OperatorType.Add) { - type = operandType; - } else if (operator === OperatorType.Subtract) { - type = mapSubtypes(operandType, (subtype) => { - const classSubtype = subtype as ClassType; - return ClassType.cloneWithLiteral(classSubtype, -(classSubtype.priv.literalValue as number | bigint)); - }); - } else if (operator === OperatorType.BitwiseInvert) { - // Python defines bitwise invert (~x) as -(x + 1). Use BigInt math - // to avoid JavaScript's 32-bit truncation when using the ~ operator - // on Number values. - type = mapSubtypes(operandType, (subtype) => { - const classSubtype = subtype as ClassType; - const literalValue = classSubtype.priv.literalValue as number | bigint; - const bigVal = typeof literalValue === 'bigint' ? literalValue : BigInt(literalValue as number); - let newValue: number | bigint = -(bigVal + BigInt(1)); - if (newValue >= BigInt(Number.MIN_SAFE_INTEGER) && newValue <= BigInt(Number.MAX_SAFE_INTEGER)) { - newValue = Number(newValue); - } - return ClassType.cloneWithLiteral(classSubtype, newValue); - }); - } - } else if (literalClassName === 'bool') { - if (operator === OperatorType.Not) { - type = mapSubtypes(operandType, (subtype) => { - const classSubtype = subtype as ClassType; - return ClassType.cloneWithLiteral(classSubtype, !(classSubtype.priv.literalValue as boolean)); - }); - } - } - - return type; -} - -// Attempts to apply "literal math" for two literal operands. -function calcLiteralForBinaryOp(operator: OperatorType, leftType: Type, rightType: Type): Type | undefined { - const leftLiteralClassName = getLiteralTypeClassName(leftType); - if ( - !leftLiteralClassName || - getTypeCondition(leftType) || - someSubtypes(leftType, (subtype) => !!getTypeCondition(subtype)) - ) { - return undefined; - } - - const rightLiteralClassName = getLiteralTypeClassName(rightType); - if ( - leftLiteralClassName !== rightLiteralClassName || - getTypeCondition(rightType) || - someSubtypes(rightType, (subtype) => !!getTypeCondition(subtype)) || - getUnionSubtypeCount(leftType) * getUnionSubtypeCount(rightType) >= maxLiteralMathSubtypeCount - ) { - return undefined; - } - - // Handle str and bytes literals. - if (leftLiteralClassName === 'str' || leftLiteralClassName === 'bytes') { - if (operator === OperatorType.Add) { - return mapSubtypes(leftType, (leftSubtype) => { - return mapSubtypes(rightType, (rightSubtype) => { - const leftClassSubtype = leftSubtype as ClassType; - const rightClassSubtype = rightSubtype as ClassType; - - return ClassType.cloneWithLiteral( - leftClassSubtype, - ((leftClassSubtype.priv.literalValue as string) + rightClassSubtype.priv.literalValue) as string - ); - }); - }); - } - } - - // Handle int literals. - if (leftLiteralClassName === 'int') { - const supportedOps = [ - OperatorType.Add, - OperatorType.Subtract, - OperatorType.Multiply, - OperatorType.FloorDivide, - OperatorType.Mod, - OperatorType.Power, - OperatorType.LeftShift, - OperatorType.RightShift, - OperatorType.BitwiseAnd, - OperatorType.BitwiseOr, - OperatorType.BitwiseXor, - ]; - if (!supportedOps.includes(operator)) { - return undefined; - } - - let isValidResult = true; - - const type = mapSubtypes(leftType, (leftSubtype) => { - return mapSubtypes(rightType, (rightSubtype) => { - try { - const leftClassSubtype = leftSubtype as ClassType; - const rightClassSubtype = rightSubtype as ClassType; - const leftLiteralValue = BigInt(leftClassSubtype.priv.literalValue as number | bigint); - const rightLiteralValue = BigInt(rightClassSubtype.priv.literalValue as number | bigint); - - let newValue: number | bigint | undefined; - if (operator === OperatorType.Add) { - newValue = leftLiteralValue + rightLiteralValue; - } else if (operator === OperatorType.Subtract) { - newValue = leftLiteralValue - rightLiteralValue; - } else if (operator === OperatorType.Multiply) { - newValue = leftLiteralValue * rightLiteralValue; - } else if (operator === OperatorType.FloorDivide) { - if (rightLiteralValue !== BigInt(0)) { - newValue = leftLiteralValue / rightLiteralValue; - - // BigInt rounds to zero, but floor divide rounds to negative - // infinity, so we need to adjust the result if the signs - // of the operands are different. - if ( - newValue * rightLiteralValue !== leftLiteralValue && - leftLiteralValue < BigInt(0) !== rightLiteralValue < BigInt(0) - ) { - newValue -= BigInt(1); - } - } - } else if (operator === OperatorType.Mod) { - if (rightLiteralValue !== BigInt(0)) { - // BigInt always produces a remainder, but Python produces - // a modulo result whose sign is always the same as the - // right operand. - newValue = ((leftLiteralValue % rightLiteralValue) + rightLiteralValue) % rightLiteralValue; - } - } else if (operator === OperatorType.Power) { - if (rightLiteralValue >= BigInt(0)) { - try { - newValue = leftLiteralValue ** rightLiteralValue; - } catch { - // Don't allow if we exceed max bigint integer value. - } - } - } else if (operator === OperatorType.LeftShift) { - if (rightLiteralValue >= BigInt(0)) { - newValue = leftLiteralValue << rightLiteralValue; - } - } else if (operator === OperatorType.RightShift) { - if (rightLiteralValue >= BigInt(0)) { - newValue = leftLiteralValue >> rightLiteralValue; - } - } else if (operator === OperatorType.BitwiseAnd) { - newValue = leftLiteralValue & rightLiteralValue; - } else if (operator === OperatorType.BitwiseOr) { - newValue = leftLiteralValue | rightLiteralValue; - } else if (operator === OperatorType.BitwiseXor) { - newValue = leftLiteralValue ^ rightLiteralValue; - } - - if (newValue === undefined) { - isValidResult = false; - return undefined; - } else if (typeof newValue === 'number' && isNaN(newValue)) { - isValidResult = false; - return undefined; - } else { - // Convert back to a simple number if it fits. Leave as a bigint - // if it doesn't. - if (newValue >= Number.MIN_SAFE_INTEGER && newValue <= Number.MAX_SAFE_INTEGER) { - newValue = Number(newValue); - } - - return ClassType.cloneWithLiteral(leftClassSubtype, newValue); - } - } catch { - isValidResult = false; - return undefined; - } - }); - }); - - if (isValidResult) { - return type; - } - } - - return undefined; -} - -function customMetaclassSupportsMethod(type: Type, methodName: string): boolean { - if (!isInstantiableClass(type)) { - return false; - } - - const metaclass = type.shared.effectiveMetaclass; - if (!metaclass || !isInstantiableClass(metaclass)) { - return false; - } - - if (ClassType.isBuiltIn(metaclass, 'type')) { - return false; - } - - const memberInfo = lookUpClassMember(metaclass, methodName); - if (!memberInfo) { - return false; - } - - // If the metaclass inherits from Any or Unknown, we have to guess - // whether the method is supported. We'll assume it's not, since this - // is the most likely case. - if (isAnyOrUnknown(memberInfo.classType)) { - return false; - } - - if (isInstantiableClass(memberInfo.classType) && ClassType.isBuiltIn(memberInfo.classType, 'type')) { - return false; - } - - return true; -} - -// All functions in Python derive from object, so they inherit all -// of the capabilities of an object. This function converts a function -// to an object instance. -function convertFunctionToObject(evaluator: TypeEvaluator, type: Type) { - if (isFunctionOrOverloaded(type)) { - return evaluator.getObjectType(); - } - - return type; -} - -// Determines whether the expression refers to a variable that -// is defined within the current scope or some outer scope. -function isExpressionLocalVariable(evaluator: TypeEvaluator, node: ExpressionNode): boolean { - if (node.nodeType !== ParseNodeType.Name) { - return false; - } - - const symbolWithScope = evaluator.lookUpSymbolRecursive(node, node.d.value, /* honorCodeFlow */ false); - if (!symbolWithScope) { - return false; - } - - const currentScope = getScopeForNode(node); - return currentScope === symbolWithScope.scope; -} - -function validateContainmentOperation( - evaluator: TypeEvaluator, - operator: OperatorType, - leftTypeResult: TypeResult, - concreteLeftType: Type, - rightTypeResult: TypeResult, - errorNode: ExpressionNode, - diag: DiagnosticAddendum -): TypeResult { - let deprecatedInfo: MagicMethodDeprecationInfo | undefined; - - const type = evaluator.mapSubtypesExpandTypeVars( - rightTypeResult.type, - /* options */ undefined, - (rightSubtypeExpanded, rightSubtypeUnexpanded) => { - return evaluator.mapSubtypesExpandTypeVars( - concreteLeftType, - { conditionFilter: getTypeCondition(rightSubtypeExpanded) }, - (leftSubtype) => { - if (isAnyOrUnknown(leftSubtype) || isAnyOrUnknown(rightSubtypeUnexpanded)) { - return preserveUnknown(leftSubtype, rightSubtypeExpanded); - } - - let returnTypeResult = evaluator.getTypeOfMagicMethodCall( - rightSubtypeExpanded, - '__contains__', - [{ type: leftSubtype, isIncomplete: leftTypeResult.isIncomplete }], - errorNode, - /* inferenceContext */ undefined - ); - - if (!returnTypeResult) { - // If __contains__ was not supported, fall back - // on an iterable. - const iteratorType = evaluator.getTypeOfIterator( - { type: rightSubtypeExpanded, isIncomplete: rightTypeResult.isIncomplete }, - /* isAsync */ false, - errorNode, - /* emitNotIterableError */ false - )?.type; - - if (iteratorType && evaluator.assignType(iteratorType, leftSubtype)) { - returnTypeResult = { type: evaluator.getBuiltInObject(errorNode, 'bool') }; - } - } - - if (!returnTypeResult) { - diag.addMessage( - LocMessage.typeNotSupportBinaryOperator().format({ - operator: printOperator(operator), - leftType: evaluator.printType(leftSubtype), - rightType: evaluator.printType(rightSubtypeExpanded), - }) - ); - } - - if (returnTypeResult?.magicMethodDeprecationInfo) { - deprecatedInfo = returnTypeResult.magicMethodDeprecationInfo; - } - - return returnTypeResult?.type ?? evaluator.getBuiltInObject(errorNode, 'bool'); - } - ); - } - ); - - return { type, magicMethodDeprecationInfo: deprecatedInfo }; -} - -function validateArithmeticOperation( - evaluator: TypeEvaluator, - operator: OperatorType, - leftTypeResult: TypeResult, - rightTypeResult: TypeResult, - errorNode: ExpressionNode, - inferenceContext: InferenceContext | undefined, - diag: DiagnosticAddendum, - options: BinaryOperationOptions -): TypeResult { - let deprecatedInfo: MagicMethodDeprecationInfo | undefined; - const isIncomplete = !!leftTypeResult.isIncomplete || !!rightTypeResult.isIncomplete; - - // Viper 扩展:指针/整数类型的比较运算。 - // C 语言中指针支持所有比较运算(<, >, <=, >=, ==, !=),Viper 的 CPtr - // 类型也应如此。当左右操作数中至少有一个是 CPtr(或 CVoid,void* 通用指针), - // 且另一个是指针或整数类型时,直接返回 bool,不报 operatorNotSupported。 - const fileInfo = getFileInfo(errorNode); - const isViperDialect = IsViperDialect(fileInfo.executionEnvironment.dialect); - const isComparisonOperator = - operator === OperatorType.Equals || - operator === OperatorType.NotEquals || - operator === OperatorType.LessThan || - operator === OperatorType.LessThanOrEqual || - operator === OperatorType.GreaterThan || - operator === OperatorType.GreaterThanOrEqual; - - if (isViperDialect && isComparisonOperator) { - const leftIsPtrLike = isClassInstance(leftTypeResult.type) && - (isCPtrType(leftTypeResult.type) || isCVoidType(leftTypeResult.type)); - const rightIsPtrLike = isClassInstance(rightTypeResult.type) && - (isCPtrType(rightTypeResult.type) || isCVoidType(rightTypeResult.type)); - const leftIsIntLike = isClassInstance(leftTypeResult.type) && - ClassType.isBuiltIn(leftTypeResult.type, 'int'); - const rightIsIntLike = isClassInstance(rightTypeResult.type) && - ClassType.isBuiltIn(rightTypeResult.type, 'int'); - - // 任一方是指针,另一方是指针或整数 → 合法的指针比较 - if ((leftIsPtrLike && (rightIsPtrLike || rightIsIntLike)) || - (rightIsPtrLike && (leftIsPtrLike || leftIsIntLike))) { - return { type: evaluator.getBuiltInObject(errorNode, 'bool') }; - } - } - - const type = evaluator.mapSubtypesExpandTypeVars( - leftTypeResult.type, - /* options */ undefined, - (leftSubtypeExpanded, leftSubtypeUnexpanded) => { - return evaluator.mapSubtypesExpandTypeVars( - rightTypeResult.type, - { conditionFilter: getTypeCondition(leftSubtypeExpanded) }, - (rightSubtypeExpanded, rightSubtypeUnexpanded) => { - if (isAnyOrUnknown(leftSubtypeUnexpanded) || isAnyOrUnknown(rightSubtypeUnexpanded)) { - return preserveUnknown(leftSubtypeUnexpanded, rightSubtypeUnexpanded); - } - - // Viper 扩展:跳过 Viper 修饰类型(CStatic, CConst 等类型修饰符)。 - // 这些修饰类型只是类型修饰符(如 static, const),不参与运算。 - // 运算时只处理基础类型(如 CInt, str 等)。 - if (isViperDialect) { - if ( - isCStaticType(leftSubtypeUnexpanded) || isCStaticType(rightSubtypeUnexpanded) || - isCConstType(leftSubtypeUnexpanded) || isCConstType(rightSubtypeUnexpanded) - ) { - return UnknownType.create(isIncomplete); - } - } - - const tupleClassType = evaluator.getTupleClassType(); - - // Special-case __add__ for tuples when the types for both tuples are known. - if ( - options.isTupleAddAllowed && - operator === OperatorType.Add && - isClassInstance(leftSubtypeExpanded) && - isTupleClass(leftSubtypeExpanded) && - leftSubtypeExpanded.priv.tupleTypeArgs && - isClassInstance(rightSubtypeExpanded) && - isTupleClass(rightSubtypeExpanded) && - rightSubtypeExpanded.priv.tupleTypeArgs && - tupleClassType && - isInstantiableClass(tupleClassType) - ) { - // If at least one of the tuples is of fixed size, we can - // combine them into a precise new type. If both are unbounded - // (or contain an unbounded element), we cannot combine them - // in this manner because tuples can contain at most one - // unbounded element. - if ( - !isUnboundedTupleClass(leftSubtypeExpanded) || - !isUnboundedTupleClass(rightSubtypeExpanded) - ) { - return ClassType.cloneAsInstance( - specializeTupleClass(tupleClassType, [ - ...leftSubtypeExpanded.priv.tupleTypeArgs, - ...rightSubtypeExpanded.priv.tupleTypeArgs, - ]) - ); - } - } - - const magicMethodName = binaryOperatorMap[operator][0]; - let resultTypeResult = evaluator.getTypeOfMagicMethodCall( - convertFunctionToObject(evaluator, leftSubtypeUnexpanded), - magicMethodName, - [{ type: rightSubtypeUnexpanded, isIncomplete: rightTypeResult.isIncomplete }], - errorNode, - inferenceContext - ); - - if (!resultTypeResult && leftSubtypeUnexpanded !== leftSubtypeExpanded) { - // Try the expanded left type. - resultTypeResult = evaluator.getTypeOfMagicMethodCall( - convertFunctionToObject(evaluator, leftSubtypeExpanded), - magicMethodName, - [{ type: rightSubtypeUnexpanded, isIncomplete: rightTypeResult.isIncomplete }], - errorNode, - inferenceContext - ); - } - - if (!resultTypeResult && rightSubtypeUnexpanded !== rightSubtypeExpanded) { - // Try the expanded left and right type. - resultTypeResult = evaluator.getTypeOfMagicMethodCall( - convertFunctionToObject(evaluator, leftSubtypeExpanded), - magicMethodName, - [{ type: rightSubtypeExpanded, isIncomplete: rightTypeResult.isIncomplete }], - errorNode, - inferenceContext - ); - } - - if (!resultTypeResult) { - // Try the alternate form (swapping right and left). - const altMagicMethodName = binaryOperatorMap[operator][1]; - resultTypeResult = evaluator.getTypeOfMagicMethodCall( - convertFunctionToObject(evaluator, rightSubtypeUnexpanded), - altMagicMethodName, - [{ type: leftSubtypeUnexpanded, isIncomplete: leftTypeResult.isIncomplete }], - errorNode, - inferenceContext - ); - - if (!resultTypeResult && rightSubtypeUnexpanded !== rightSubtypeExpanded) { - // Try the expanded right type. - resultTypeResult = evaluator.getTypeOfMagicMethodCall( - convertFunctionToObject(evaluator, rightSubtypeExpanded), - altMagicMethodName, - [ - { - type: leftSubtypeUnexpanded, - isIncomplete: leftTypeResult.isIncomplete, - }, - ], - errorNode, - inferenceContext - ); - } - - if (!resultTypeResult && leftSubtypeUnexpanded !== leftSubtypeExpanded) { - // Try the expanded right and left type. - resultTypeResult = evaluator.getTypeOfMagicMethodCall( - convertFunctionToObject(evaluator, rightSubtypeExpanded), - altMagicMethodName, - [{ type: leftSubtypeExpanded, isIncomplete: leftTypeResult.isIncomplete }], - errorNode, - inferenceContext - ); - } - } - - if (!resultTypeResult) { - if (inferenceContext && !isAnyOrUnknown(inferenceContext.expectedType)) { - diag.addMessage( - LocMessage.typeNotSupportBinaryOperatorBidirectional().format({ - operator: printOperator(operator), - leftType: evaluator.printType(leftSubtypeExpanded), - rightType: evaluator.printType(rightSubtypeExpanded), - expectedType: evaluator.printType(inferenceContext.expectedType), - }) - ); - } else { - diag.addMessage( - LocMessage.typeNotSupportBinaryOperator().format({ - operator: printOperator(operator), - leftType: evaluator.printType(leftSubtypeExpanded), - rightType: evaluator.printType(rightSubtypeExpanded), - }) - ); - } - } - - if (resultTypeResult?.magicMethodDeprecationInfo) { - deprecatedInfo = resultTypeResult.magicMethodDeprecationInfo; - } - - return resultTypeResult?.type ?? UnknownType.create(isIncomplete); - } - ); - } - ); - - return { type, magicMethodDeprecationInfo: deprecatedInfo }; -} +/* + * operations.ts + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * Author: Eric Traut + * + * Provides type evaluation logic for unary, binary, augmented assignment, + * and ternary operators. + */ + +import { DiagnosticAddendum } from '../common/diagnostic'; +import { DiagnosticRule } from '../common/diagnosticRules'; +import { PythonVersion, pythonVersion3_10 } from '../common/pythonVersion'; +import { IsViperDialect } from '../common/dialect'; +import { LocMessage } from '../localization/localize'; +import { + AugmentedAssignmentNode, + BinaryOperationNode, + ExpressionNode, + ParseNodeType, + TernaryNode, + UnaryOperationNode, +} from '../parser/parseNodes'; +import { OperatorType } from '../parser/tokenizerTypes'; +import { getFileInfo } from './analyzerNodeInfo'; +import { getEnclosingLambda, isWithinLoop, operatorSupportsChaining, printOperator } from './parseTreeUtils'; +import { getScopeForNode } from './scopeUtils'; +import { evaluateStaticBoolExpression } from './staticExpressions'; +import { EvalFlags, MagicMethodDeprecationInfo, TypeEvaluator, TypeResult } from './typeEvaluatorTypes'; +import { + InferenceContext, + convertToInstantiable, + getLiteralTypeClassName, + getTypeCondition, + getUnionSubtypeCount, + isNoneInstance, + isOptionalType, + isTupleClass, + isUnboundedTupleClass, + isUnionableType, + lookUpClassMember, + makeInferenceContext, + mapSubtypes, + preserveUnknown, + removeNoneFromUnion, + someSubtypes, + specializeTupleClass, + specializeWithDefaultTypeArgs, + transformPossibleRecursiveTypeAlias, +} from './typeUtils'; +import { + ClassType, + NeverType, + Type, + TypeBase, + UnknownType, + combineTypes, + isAnyOrUnknown, + isClass, + isClassInstance, + isCConstType, + isCDefineType, + isCPtrType, + isCStaticType, + isCTypeSubclassType, + isCVoidType, + isFunctionOrOverloaded, + isViperIntegerType, + isInstantiableClass, + isNever, + isUnion, +} from './types'; + +// Maps binary operators to the magic methods that implement them. +const binaryOperatorMap: { [operator: number]: [string, string] } = { + [OperatorType.Add]: ['__add__', '__radd__'], + [OperatorType.Subtract]: ['__sub__', '__rsub__'], + [OperatorType.Multiply]: ['__mul__', '__rmul__'], + [OperatorType.FloorDivide]: ['__floordiv__', '__rfloordiv__'], + [OperatorType.Divide]: ['__truediv__', '__rtruediv__'], + [OperatorType.Mod]: ['__mod__', '__rmod__'], + [OperatorType.Power]: ['__pow__', '__rpow__'], + [OperatorType.MatrixMultiply]: ['__matmul__', '__rmatmul__'], + [OperatorType.BitwiseAnd]: ['__and__', '__rand__'], + [OperatorType.BitwiseOr]: ['__or__', '__ror__'], + [OperatorType.BitwiseXor]: ['__xor__', '__rxor__'], + [OperatorType.LeftShift]: ['__lshift__', '__rlshift__'], + [OperatorType.RightShift]: ['__rshift__', '__rrshift__'], + [OperatorType.Equals]: ['__eq__', '__eq__'], + [OperatorType.NotEquals]: ['__ne__', '__ne__'], + [OperatorType.LessThan]: ['__lt__', '__gt__'], + [OperatorType.LessThanOrEqual]: ['__le__', '__ge__'], + [OperatorType.GreaterThan]: ['__gt__', '__lt__'], + [OperatorType.GreaterThanOrEqual]: ['__ge__', '__le__'], +}; + +// Map of operators that always return a bool result. +const booleanOperatorMap: { [operator: number]: true } = { + [OperatorType.And]: true, + [OperatorType.Or]: true, + [OperatorType.Is]: true, + [OperatorType.IsNot]: true, + [OperatorType.In]: true, + [OperatorType.NotIn]: true, +}; + +interface BinaryOperationOptions { + isLiteralMathAllowed?: boolean; + isTupleAddAllowed?: boolean; +} + +// If the number of subtypes starts to explode when applying "literal math", +// cut off the literal union and fall back to the non-literal supertype. +const maxLiteralMathSubtypeCount = 64; + +export function validateBinaryOperation( + evaluator: TypeEvaluator, + operator: OperatorType, + leftTypeResult: TypeResult, + rightTypeResult: TypeResult, + errorNode: ExpressionNode, + inferenceContext: InferenceContext | undefined, + diag: DiagnosticAddendum, + options: BinaryOperationOptions +): TypeResult { + const leftType = leftTypeResult.type; + const rightType = rightTypeResult.type; + const isIncomplete = !!leftTypeResult.isIncomplete || !!rightTypeResult.isIncomplete; + let type: Type | undefined; + let concreteLeftType = evaluator.makeTopLevelTypeVarsConcrete(leftType); + let deprecatedInfo: MagicMethodDeprecationInfo | undefined; + + if (booleanOperatorMap[operator] !== undefined) { + // If it's an AND or OR, we need to handle short-circuiting by + // eliminating any known-truthy or known-falsy types. + if (operator === OperatorType.And) { + // If the LHS evaluates to falsy, the And expression will + // always return the type of the left-hand side. + if (!evaluator.canBeTruthy(concreteLeftType)) { + return { type: leftType }; + } + + // If the LHS evaluates to truthy, the And expression will + // always return the type of the right-hand side. + if (!evaluator.canBeFalsy(concreteLeftType)) { + return { type: rightType }; + } + + concreteLeftType = evaluator.removeTruthinessFromType(concreteLeftType); + + if (isNever(rightType)) { + return { type: concreteLeftType }; + } + } else if (operator === OperatorType.Or) { + // If the LHS evaluates to truthy, the Or expression will + // always return the type of the left-hand side. + if (!evaluator.canBeFalsy(concreteLeftType)) { + return { type: leftType }; + } + + // If the LHS evaluates to falsy, the Or expression will + // always return the type of the right-hand side. + if (!evaluator.canBeTruthy(concreteLeftType)) { + return { type: rightType }; + } + + concreteLeftType = evaluator.removeFalsinessFromType(concreteLeftType); + + if (isNever(rightType)) { + return { type: concreteLeftType }; + } + } + + if (isNever(leftType) || isNever(rightType)) { + return { type: NeverType.createNever() }; + } + + // The "in" and "not in" operators make use of the __contains__ + // magic method. + if (operator === OperatorType.In || operator === OperatorType.NotIn) { + const result = validateContainmentOperation( + evaluator, + operator, + leftTypeResult, + concreteLeftType, + rightTypeResult, + errorNode, + diag + ); + + if (result.magicMethodDeprecationInfo) { + deprecatedInfo = result.magicMethodDeprecationInfo; + } + + type = result.type; + + // Assume that a bool is returned even if the type is unknown. + if (type && !isNever(type)) { + type = evaluator.getBuiltInObject(errorNode, 'bool'); + } + } else { + type = evaluator.mapSubtypesExpandTypeVars( + concreteLeftType, + /* options */ undefined, + (leftSubtypeExpanded, leftSubtypeUnexpanded) => { + return evaluator.mapSubtypesExpandTypeVars( + rightType, + { conditionFilter: getTypeCondition(leftSubtypeExpanded) }, + (rightSubtypeExpanded, rightSubtypeUnexpanded) => { + // If the operator is an AND or OR, we need to combine the two types. + if (operator === OperatorType.And || operator === OperatorType.Or) { + return combineTypes([leftSubtypeUnexpanded, rightSubtypeUnexpanded]); + } + // The other boolean operators always return a bool value. + return evaluator.getBuiltInObject(errorNode, 'bool'); + } + ); + } + ); + } + } else if (binaryOperatorMap[operator]) { + if (isNever(leftType) || isNever(rightType)) { + return { type: NeverType.createNever() }; + } + + // Handle certain operations on certain homogenous literal types + // using special-case math. For example, Literal[1, 2] + Literal[3, 4] + // should result in Literal[4, 5, 6]. + if (options.isLiteralMathAllowed) { + type = calcLiteralForBinaryOp(operator, leftType, rightType); + } + + if (!type) { + const result = validateArithmeticOperation( + evaluator, + operator, + leftTypeResult, + rightTypeResult, + errorNode, + inferenceContext, + diag, + options + ); + + if (result.magicMethodDeprecationInfo) { + deprecatedInfo = result.magicMethodDeprecationInfo; + } + + type = result.type; + } + } + + return { type: type ?? UnknownType.create(isIncomplete), magicMethodDeprecationInfo: deprecatedInfo }; +} + +export function getTypeOfBinaryOperation( + evaluator: TypeEvaluator, + node: BinaryOperationNode, + flags: EvalFlags, + inferenceContext: InferenceContext | undefined +): TypeResult { + const leftExpression = node.d.leftExpr; + let rightExpression = node.d.rightExpr; + let isIncomplete = false; + let typeErrors = false; + + // If this is a comparison and the left expression is also a comparison, + // we need to change the behavior to accommodate python's "chained + // comparisons" feature. + if (operatorSupportsChaining(node.d.operator)) { + if ( + rightExpression.nodeType === ParseNodeType.BinaryOperation && + !rightExpression.d.hasParens && + operatorSupportsChaining(rightExpression.d.operator) + ) { + // Evaluate the right expression so it is type checked. + getTypeOfBinaryOperation(evaluator, rightExpression, flags, inferenceContext); + + // Use the left side of the right expression for comparison purposes. + rightExpression = rightExpression.d.leftExpr; + } + } + + // For most binary operations, the "expected type" is applied to the output + // of the magic method for that operation. However, the "or" and "and" operators + // have no magic method, so we apply the expected type directly to both operands. + let expectedOperandType = + node.d.operator === OperatorType.Or || node.d.operator === OperatorType.And + ? inferenceContext?.expectedType + : undefined; + + // Handle the very special case where the expected type is a list + // and the operator is a multiply. This comes up in the common case + // of "x: List[Optional[X]] = [None] * y" where y is an integer literal. + let expectedLeftOperandType: Type | undefined; + if ( + node.d.operator === OperatorType.Multiply && + inferenceContext && + isClassInstance(inferenceContext.expectedType) && + ClassType.isBuiltIn(inferenceContext.expectedType, 'list') && + inferenceContext.expectedType.priv.typeArgs && + inferenceContext.expectedType.priv.typeArgs.length >= 1 && + node.d.leftExpr.nodeType === ParseNodeType.List + ) { + expectedLeftOperandType = inferenceContext.expectedType; + } + + const effectiveExpectedType = expectedOperandType ?? expectedLeftOperandType; + const leftTypeResult = evaluator.getTypeOfExpression( + leftExpression, + flags, + makeInferenceContext(effectiveExpectedType) + ); + let leftType = leftTypeResult.type; + + if (!expectedOperandType) { + if (node.d.operator === OperatorType.Or || node.d.operator === OperatorType.And) { + // For "or" and "and", use the type of the left operand under certain + // circumstances. This allows us to infer a better type for expressions + // like `x or []`. Do this only if it's a generic class (like list or dict) + // or a TypedDict. + if ( + someSubtypes(leftType, (subtype) => { + if (!isClassInstance(subtype)) { + return false; + } + + return ClassType.isTypedDictClass(subtype) || subtype.shared.typeParams.length > 0; + }) + ) { + expectedOperandType = leftType; + } + } else if (node.d.operator === OperatorType.Add && node.d.rightExpr.nodeType === ParseNodeType.List) { + // For the "+" operator , use this technique only if the right operand is + // a list expression. This heuristic handles the common case of `my_list + [0]`. + expectedOperandType = leftType; + } else if (node.d.operator === OperatorType.BitwiseOr) { + // If this is a bitwise or ("|"), use the type of the left operand. This allows + // us to support the case where a TypedDict is being updated with a dict expression. + if (isClassInstance(leftType) && ClassType.isTypedDictClass(leftType)) { + expectedOperandType = leftType; + } + } + } + + const rightTypeResult = evaluator.getTypeOfExpression( + rightExpression, + flags, + makeInferenceContext(expectedOperandType) + ); + let rightType = rightTypeResult.type; + + if (leftTypeResult.isIncomplete || rightTypeResult.isIncomplete) { + isIncomplete = true; + } + + // Is this a "|" operator used in a context where it is supposed to be + // interpreted as a union operator? + if ( + node.d.operator === OperatorType.BitwiseOr && + !customMetaclassSupportsMethod(leftType, '__or__') && + !customMetaclassSupportsMethod(rightType, '__ror__') + ) { + let adjustedRightType = rightType; + let adjustedLeftType = leftType; + if (!isNoneInstance(leftType) && isNoneInstance(rightType)) { + // Handle the special case where "None" is being added to the union + // with something else. Even though "None" will normally be interpreted + // as the None singleton object in contexts where a type annotation isn't + // assumed, we'll allow it here. + adjustedRightType = convertToInstantiable(evaluator.getNoneType()); + } else if (!isNoneInstance(rightType) && isNoneInstance(leftType)) { + adjustedLeftType = convertToInstantiable(evaluator.getNoneType()); + } + + if (isUnionableType([adjustedLeftType, adjustedRightType])) { + if (isInstantiableClass(adjustedLeftType)) { + adjustedLeftType = specializeWithDefaultTypeArgs(adjustedLeftType); + } + + if (isInstantiableClass(adjustedRightType)) { + adjustedRightType = specializeWithDefaultTypeArgs(adjustedRightType); + } + + return createUnionType( + evaluator, + node, + flags, + leftTypeResult, + rightTypeResult, + adjustedRightType, + adjustedLeftType + ); + } + } + + if ((flags & EvalFlags.TypeExpression) !== 0) { + // Exempt "|" because it might be a union operation involving unknowns. + // Viper 扩展:Viper 是 AOT 静态语言,类型表达式中的算术/位运算用于编译时常量 + // (如 t.CArray[T, MAX_ORDER + 1] 的数组长度),放行不报 binaryOperationNotAllowed, + // 让其继续按常量表达式求值。 + if ( + node.d.operator !== OperatorType.BitwiseOr && + !IsViperDialect(getFileInfo(node).executionEnvironment.dialect) + ) { + evaluator.addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.binaryOperationNotAllowed(), node); + return { type: UnknownType.create() }; + } + } + + // Optional checks apply to all operations except for boolean operations. + let isLeftOptionalType = false; + if (booleanOperatorMap[node.d.operator] === undefined) { + // None is a valid operand for == and != even if the type stub says otherwise. + if (node.d.operator === OperatorType.Equals || node.d.operator === OperatorType.NotEquals) { + leftType = removeNoneFromUnion(leftType); + } else { + isLeftOptionalType = isOptionalType(leftType); + } + + // None is a valid operand for == and != even if the type stub says otherwise. + if (node.d.operator === OperatorType.Equals || node.d.operator === OperatorType.NotEquals) { + rightType = removeNoneFromUnion(rightType); + } + } + + const diag = new DiagnosticAddendum(); + + // Don't use literal math if the operation is within a loop + // because the literal values may change each time. We also don't want to + // apply literal math within the body of a lambda because they are often + // used as callbacks where the value changes each time they are called. + const isLiteralMathAllowed = !isWithinLoop(node) && !getEnclosingLambda(node); + + // Don't special-case tuple __add__ if the left type is a union. This + // can result in an infinite loop if we keep creating new tuple types + // within a loop construct using __add__. + const isTupleAddAllowed = !isUnion(leftType); + + const typeResult = validateBinaryOperation( + evaluator, + node.d.operator, + { type: leftType, isIncomplete: leftTypeResult.isIncomplete }, + { type: rightType, isIncomplete: rightTypeResult.isIncomplete }, + node, + inferenceContext, + diag, + { isLiteralMathAllowed, isTupleAddAllowed } + ); + + if (typeResult.isIncomplete) { + isIncomplete = true; + } + + if (!diag.isEmpty()) { + typeErrors = true; + + if (!isIncomplete) { + if (isLeftOptionalType && diag.getMessages().length === 1) { + // If the left was an optional type and there is just one diagnostic, + // assume that it was due to a "None" not being supported. Report + // this as a reportOptionalOperand diagnostic rather than a + // reportGeneralTypeIssues diagnostic. + evaluator.addDiagnostic( + DiagnosticRule.reportOptionalOperand, + LocMessage.noneOperator().format({ + operator: printOperator(node.d.operator), + }), + node.d.leftExpr + ); + } else { + // If neither the LHS or RHS are unions, don't include a diagnostic addendum + // because it will be redundant with the main diagnostic message. The addenda + // are useful only if union expansion was used for one or both operands. + let diagString = ''; + if ( + isUnion(evaluator.makeTopLevelTypeVarsConcrete(leftType)) || + isUnion(evaluator.makeTopLevelTypeVarsConcrete(rightType)) + ) { + diagString = diag.getString(); + } + + evaluator.addDiagnostic( + DiagnosticRule.reportOperatorIssue, + LocMessage.typeNotSupportBinaryOperator().format({ + operator: printOperator(node.d.operator), + leftType: evaluator.printType(leftType), + rightType: evaluator.printType(rightType), + }) + diagString, + node + ); + } + } + } + + return { + type: typeResult.type, + isIncomplete, + typeErrors, + magicMethodDeprecationInfo: typeResult.magicMethodDeprecationInfo, + }; +} + +export function getTypeOfAugmentedAssignment( + evaluator: TypeEvaluator, + node: AugmentedAssignmentNode, + inferenceContext: InferenceContext | undefined +): TypeResult { + const operatorMap: { [operator: number]: [string, OperatorType] } = { + [OperatorType.AddEqual]: ['__iadd__', OperatorType.Add], + [OperatorType.SubtractEqual]: ['__isub__', OperatorType.Subtract], + [OperatorType.MultiplyEqual]: ['__imul__', OperatorType.Multiply], + [OperatorType.FloorDivideEqual]: ['__ifloordiv__', OperatorType.FloorDivide], + [OperatorType.DivideEqual]: ['__itruediv__', OperatorType.Divide], + [OperatorType.ModEqual]: ['__imod__', OperatorType.Mod], + [OperatorType.PowerEqual]: ['__ipow__', OperatorType.Power], + [OperatorType.MatrixMultiplyEqual]: ['__imatmul__', OperatorType.MatrixMultiply], + [OperatorType.BitwiseAndEqual]: ['__iand__', OperatorType.BitwiseAnd], + [OperatorType.BitwiseOrEqual]: ['__ior__', OperatorType.BitwiseOr], + [OperatorType.BitwiseXorEqual]: ['__ixor__', OperatorType.BitwiseXor], + [OperatorType.LeftShiftEqual]: ['__ilshift__', OperatorType.LeftShift], + [OperatorType.RightShiftEqual]: ['__irshift__', OperatorType.RightShift], + }; + + let type: Type | undefined; + let typeResult: TypeResult | undefined; + const diag = new DiagnosticAddendum(); + let deprecatedInfo: MagicMethodDeprecationInfo | undefined; + + const leftTypeResult = evaluator.getTypeOfExpression(node.d.leftExpr); + const leftType = leftTypeResult.type; + + let expectedOperandType: Type | undefined; + if (node.d.operator === OperatorType.BitwiseOrEqual) { + // If this is a bitwise or ("|="), use the type of the left operand. This allows + // us to support the case where a TypedDict is being updated with a dict expression. + expectedOperandType = leftType; + } + + const rightTypeResult = evaluator.getTypeOfExpression( + node.d.rightExpr, + /* flags */ undefined, + makeInferenceContext(expectedOperandType) + ); + const rightType = rightTypeResult.type; + const isIncomplete = !!rightTypeResult.isIncomplete || !!leftTypeResult.isIncomplete; + + if (isNever(leftType) || isNever(rightType)) { + typeResult = { type: NeverType.createNever(), isIncomplete }; + } else { + type = evaluator.mapSubtypesExpandTypeVars( + leftType, + /* options */ undefined, + (leftSubtypeExpanded, leftSubtypeUnexpanded) => { + return evaluator.mapSubtypesExpandTypeVars( + rightType, + { conditionFilter: getTypeCondition(leftSubtypeExpanded) }, + (rightSubtypeExpanded, rightSubtypeUnexpanded) => { + if (isAnyOrUnknown(leftSubtypeUnexpanded) || isAnyOrUnknown(rightSubtypeUnexpanded)) { + return preserveUnknown(leftSubtypeUnexpanded, rightSubtypeUnexpanded); + } + + const magicMethodName = operatorMap[node.d.operator][0]; + + // Viper 扩展:CType 子类的增强赋值运算快速路径。 + // CInt/CUInt32T 等 CType 子类没有 __iadd__ 等魔法方法。 + // 当左操作数是 CType 子类,右操作数是 CType 子类或 Python + // 内置数值/字符串类型时,直接返回左操作数类型,避免报错。 + // 这使得 `_next_sheet_id: t.CInt = 0; _next_sheet_id += 1` 合法。 + if (IsViperDialect(getFileInfo(node).executionEnvironment.dialect)) { + const leftIsCType = isClassInstance(leftSubtypeUnexpanded) && + isCTypeSubclassType(leftSubtypeUnexpanded); + const rightIsCType = isClassInstance(rightSubtypeUnexpanded) && + isCTypeSubclassType(rightSubtypeUnexpanded); + const rightIsBuiltin = isClassInstance(rightSubtypeUnexpanded) && + ClassType.isBuiltIn(rightSubtypeUnexpanded, ['int', 'float', 'bool', 'bytes', 'str']); + if (leftIsCType && (rightIsCType || rightIsBuiltin)) { + return leftSubtypeUnexpanded; + } + } + + let returnTypeResult = evaluator.getTypeOfMagicMethodCall( + leftSubtypeUnexpanded, + magicMethodName, + [{ type: rightSubtypeUnexpanded, isIncomplete: rightTypeResult.isIncomplete }], + node, + inferenceContext + ); + + if (!returnTypeResult && leftSubtypeUnexpanded !== leftSubtypeExpanded) { + // Try with the expanded left type. + returnTypeResult = evaluator.getTypeOfMagicMethodCall( + leftSubtypeExpanded, + magicMethodName, + [{ type: rightSubtypeUnexpanded, isIncomplete: rightTypeResult.isIncomplete }], + node, + inferenceContext + ); + } + + if (!returnTypeResult && rightSubtypeUnexpanded !== rightSubtypeExpanded) { + // Try with the expanded left and right type. + returnTypeResult = evaluator.getTypeOfMagicMethodCall( + leftSubtypeExpanded, + magicMethodName, + [{ type: rightSubtypeExpanded, isIncomplete: rightTypeResult.isIncomplete }], + node, + inferenceContext + ); + } + + if (!returnTypeResult) { + // If the LHS class didn't support the magic method for augmented + // assignment, fall back on the normal binary expression evaluator. + const binaryOperator = operatorMap[node.d.operator][1]; + + // Don't use literal math if the operation is within a loop + // because the literal values may change each time. + const isLiteralMathAllowed = + !isWithinLoop(node) && + isExpressionLocalVariable(evaluator, node.d.leftExpr) && + getUnionSubtypeCount(leftType) * getUnionSubtypeCount(rightType) < + maxLiteralMathSubtypeCount; + + // Don't special-case tuple __add__ if the left type is a union. This + // can result in an infinite loop if we keep creating new tuple types + // within a loop construct using __add__. + const isTupleAddAllowed = !isUnion(leftType); + + returnTypeResult = validateBinaryOperation( + evaluator, + binaryOperator, + { type: leftSubtypeUnexpanded, isIncomplete: leftTypeResult.isIncomplete }, + { type: rightSubtypeUnexpanded, isIncomplete: rightTypeResult.isIncomplete }, + node, + inferenceContext, + diag, + { isLiteralMathAllowed, isTupleAddAllowed } + ); + } + + if (returnTypeResult?.magicMethodDeprecationInfo) { + deprecatedInfo = returnTypeResult.magicMethodDeprecationInfo; + } + + return returnTypeResult?.type; + } + ); + } + ); + + // If the LHS class didn't support the magic method for augmented + // assignment, fall back on the normal binary expression evaluator. + if (!diag.isEmpty() || !type || isNever(type)) { + if (!isIncomplete) { + evaluator.addDiagnostic( + DiagnosticRule.reportOperatorIssue, + LocMessage.typeNotSupportBinaryOperator().format({ + operator: printOperator(node.d.operator), + leftType: evaluator.printType(leftType), + rightType: evaluator.printType(rightType), + }) + diag.getString(), + node + ); + } + } + + typeResult = { type, isIncomplete, magicMethodDeprecationInfo: deprecatedInfo }; + } + + evaluator.assignTypeToExpression(node.d.destExpr, typeResult, node.d.rightExpr); + + return typeResult; +} + +export function getTypeOfUnaryOperation( + evaluator: TypeEvaluator, + node: UnaryOperationNode, + flags: EvalFlags, + inferenceContext: InferenceContext | undefined +): TypeResult { + if ((flags & EvalFlags.TypeExpression) !== 0) { + evaluator.addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.unaryOperationNotAllowed(), node); + return { type: UnknownType.create() }; + } + + const exprTypeResult = evaluator.getTypeOfExpression(node.d.expr); + let exprType = evaluator.makeTopLevelTypeVarsConcrete(transformPossibleRecursiveTypeAlias(exprTypeResult.type)); + + const isIncomplete = exprTypeResult.isIncomplete; + + if (isNever(exprType)) { + return { type: NeverType.createNever(), isIncomplete }; + } + + // Map unary operators to magic functions. Note that the bitwise + // invert has two magic functions that are aliases of each other. + const unaryOperatorMap: { [operator: number]: string } = { + [OperatorType.Add]: '__pos__', + [OperatorType.Subtract]: '__neg__', + [OperatorType.BitwiseInvert]: '__invert__', + [OperatorType.Not]: '__bool__', + }; + + let type: Type | undefined; + let deprecatedInfo: MagicMethodDeprecationInfo | undefined; + + if (node.d.operator !== OperatorType.Not) { + if (isOptionalType(exprType)) { + evaluator.addDiagnostic( + DiagnosticRule.reportOptionalOperand, + LocMessage.noneOperator().format({ + operator: printOperator(node.d.operator), + }), + node.d.expr + ); + exprType = removeNoneFromUnion(exprType); + } + } + + // Handle certain operations on certain literal types + // using special-case math. Do not apply this if the input type + // is incomplete because we may be evaluating an expression within + // a loop, so the literal value may change each time. + if (!exprTypeResult.isIncomplete) { + type = calcLiteralForUnaryOp(node.d.operator, exprType); + } + + if (!type) { + if (isAnyOrUnknown(exprType)) { + type = exprType; + } else { + const magicMethodName = unaryOperatorMap[node.d.operator]; + let isResultValid = true; + const isViperDialect = IsViperDialect(getFileInfo(node).executionEnvironment.dialect); + + type = evaluator.mapSubtypesExpandTypeVars(exprType, /* options */ undefined, (subtypeExpanded) => { + // Viper 扩展:CType 子类的一元算术/位运算。 + // CFloat/CInt 等 CType 子类没有 __neg__/__pos__/__invert__ 魔法方法, + // 但在 C 语义中,一元负号/正号/按位取反对数值类型是合法的,结果类型不变。 + if ( + isViperDialect && + node.d.operator !== OperatorType.Not && + isClassInstance(subtypeExpanded) && + isCTypeSubclassType(subtypeExpanded) + ) { + return subtypeExpanded; + } + + const typeResult = evaluator.getTypeOfMagicMethodCall( + subtypeExpanded, + magicMethodName, + [], + node, + inferenceContext + ); + + if (!typeResult) { + isResultValid = false; + } + + if (typeResult?.magicMethodDeprecationInfo) { + deprecatedInfo = typeResult.magicMethodDeprecationInfo; + } + + return typeResult?.type; + }); + + if (!isResultValid) { + type = undefined; + } + } + + // __not__ always returns a boolean. + if (node.d.operator === OperatorType.Not) { + type = evaluator.getBuiltInObject(node, 'bool'); + if (!type) { + type = UnknownType.create(); + } + } + + if (!type) { + if (!isIncomplete) { + if (inferenceContext && !isAnyOrUnknown(inferenceContext.expectedType)) { + evaluator.addDiagnostic( + DiagnosticRule.reportOperatorIssue, + LocMessage.typeNotSupportUnaryOperatorBidirectional().format({ + operator: printOperator(node.d.operator), + type: evaluator.printType(exprType), + expectedType: evaluator.printType(inferenceContext.expectedType), + }), + node + ); + } else { + evaluator.addDiagnostic( + DiagnosticRule.reportOperatorIssue, + LocMessage.typeNotSupportUnaryOperator().format({ + operator: printOperator(node.d.operator), + type: evaluator.printType(exprType), + }), + node + ); + } + } + + type = UnknownType.create(isIncomplete); + } + } + + return { type, isIncomplete, magicMethodDeprecationInfo: deprecatedInfo }; +} + +export function getTypeOfTernaryOperation( + evaluator: TypeEvaluator, + node: TernaryNode, + flags: EvalFlags, + inferenceContext: InferenceContext | undefined +): TypeResult { + const fileInfo = getFileInfo(node); + + if ((flags & EvalFlags.TypeExpression) !== 0) { + evaluator.addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.ternaryNotAllowed(), node); + return { type: UnknownType.create() }; + } + + evaluator.getTypeOfExpression(node.d.testExpr); + + const typesToCombine: Type[] = []; + let isIncomplete = false; + let typeErrors = false; + + const constExprValue = evaluateStaticBoolExpression( + node.d.testExpr, + fileInfo.executionEnvironment, + fileInfo.definedConstants + ); + + if (constExprValue !== false && evaluator.isNodeReachable(node.d.ifExpr)) { + const ifType = evaluator.getTypeOfExpression(node.d.ifExpr, flags, inferenceContext); + typesToCombine.push(ifType.type); + if (ifType.isIncomplete) { + isIncomplete = true; + } + if (ifType.typeErrors) { + typeErrors = true; + } + } + + if (constExprValue !== true && evaluator.isNodeReachable(node.d.elseExpr)) { + const elseType = evaluator.getTypeOfExpression(node.d.elseExpr, flags, inferenceContext); + typesToCombine.push(elseType.type); + if (elseType.isIncomplete) { + isIncomplete = true; + } + if (elseType.typeErrors) { + typeErrors = true; + } + } + + return { type: combineTypes(typesToCombine), isIncomplete, typeErrors }; +} + +function createUnionType( + evaluator: TypeEvaluator, + node: BinaryOperationNode, + flags: EvalFlags, + leftTypeResult: TypeResult, + rightTypeResult: TypeResult, + adjustedRightType: Type, + adjustedLeftType: Type +): TypeResult { + const leftExpression = node.d.leftExpr; + const rightExpression = node.d.rightExpr; + const fileInfo = getFileInfo(node); + const unionNotationSupported = + fileInfo.isStubFile || + (flags & EvalFlags.ForwardRefs) !== 0 || + PythonVersion.isGreaterOrEqualTo(fileInfo.executionEnvironment.pythonVersion, pythonVersion3_10); + + if (!unionNotationSupported) { + // If the left type is Any, we can't say for sure whether this + // is an illegal syntax or a valid application of the "|" operator. + if (!isAnyOrUnknown(adjustedLeftType)) { + evaluator.addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.unionSyntaxIllegal(), + node, + node.d.operatorToken + ); + } + } + + const isLeftTypeArgValid = evaluator.validateTypeArg({ ...leftTypeResult, node: leftExpression }); + const isRightTypeArgValid = evaluator.validateTypeArg({ ...rightTypeResult, node: rightExpression }); + + if (!isLeftTypeArgValid || !isRightTypeArgValid) { + return { type: UnknownType.create() }; + } + + adjustedLeftType = evaluator.reportMissingTypeArgs( + node.d.leftExpr, + adjustedLeftType, + flags | EvalFlags.InstantiableType + ); + adjustedRightType = evaluator.reportMissingTypeArgs( + node.d.rightExpr, + adjustedRightType, + flags | EvalFlags.InstantiableType + ); + + let newUnion = combineTypes([adjustedLeftType, adjustedRightType], { skipElideRedundantLiterals: true }); + + // Viper 扩展:若 combineTypes 已将 Struct | CPtr 折叠为 CPtr[Struct], + // 则结果不再是真正的联合类型,不应附加 UnionType specialForm。 + // 否则后续调用 `(memory_block | t.CPtr)(addr)` 会被 specialForm 检查 + // 误报为 "Object of type \"UnionType\" is not callable"。 + const unionClass = evaluator.getUnionClassType(); + if ( + unionClass && + isInstantiableClass(unionClass) && + (flags & EvalFlags.IsinstanceArg) === 0 && + isUnion(newUnion) + ) { + newUnion = TypeBase.cloneAsSpecialForm(newUnion, ClassType.cloneAsInstance(unionClass)); + } + + if (leftTypeResult.type.props?.typeForm && rightTypeResult.type.props?.typeForm) { + const newTypeForm = combineTypes([leftTypeResult.type.props.typeForm, rightTypeResult.type.props.typeForm]); + newUnion = TypeBase.cloneWithTypeForm(newUnion, newTypeForm); + } + + // Check for "stringified" forward reference type expressions. The "|" operator + // doesn't support these except in certain circumstances. Notably, it can't be used + // with other strings or with types that are not specialized using an index form. + // Viper 扩展:跳过此检查。Viper 中 `'ClassName' | t.CPtr` 是合法的 TypeSet 语法, + // 表示"指向 ClassName 的指针"(前向引用字符串 + CPtr 组合)。 + if (!fileInfo.isStubFile && !IsViperDialect(fileInfo.executionEnvironment.dialect)) { + let stringNode: ExpressionNode | undefined; + let otherNode: ExpressionNode | undefined; + let otherType: Type | undefined; + + if (leftExpression.nodeType === ParseNodeType.StringList) { + stringNode = leftExpression; + otherNode = rightExpression; + otherType = rightTypeResult.type; + } else if (rightExpression.nodeType === ParseNodeType.StringList) { + stringNode = rightExpression; + otherNode = leftExpression; + otherType = leftTypeResult.type; + } + + if (stringNode && otherNode && otherType) { + let isAllowed = true; + if (isClass(otherType)) { + if (!otherType.priv.isTypeArgExplicit || isClassInstance(otherType)) { + isAllowed = false; + } + } + + if (!isAllowed) { + evaluator.addDiagnostic( + DiagnosticRule.reportGeneralTypeIssues, + LocMessage.unionForwardReferenceNotAllowed(), + stringNode + ); + } + } + } + + return { type: newUnion }; +} + +// Attempts to apply "literal math" for a literal operands. +function calcLiteralForUnaryOp(operator: OperatorType, operandType: Type): Type | undefined { + let type: Type | undefined; + + if (getUnionSubtypeCount(operandType) >= maxLiteralMathSubtypeCount) { + return undefined; + } + + if (!!getTypeCondition(operandType) || someSubtypes(operandType, (subtype) => !!getTypeCondition(subtype))) { + return undefined; + } + + const literalClassName = getLiteralTypeClassName(operandType); + + if (literalClassName === 'int') { + if (operator === OperatorType.Add) { + type = operandType; + } else if (operator === OperatorType.Subtract) { + type = mapSubtypes(operandType, (subtype) => { + const classSubtype = subtype as ClassType; + return ClassType.cloneWithLiteral(classSubtype, -(classSubtype.priv.literalValue as number | bigint)); + }); + } else if (operator === OperatorType.BitwiseInvert) { + // Python defines bitwise invert (~x) as -(x + 1). Use BigInt math + // to avoid JavaScript's 32-bit truncation when using the ~ operator + // on Number values. + type = mapSubtypes(operandType, (subtype) => { + const classSubtype = subtype as ClassType; + const literalValue = classSubtype.priv.literalValue as number | bigint; + const bigVal = typeof literalValue === 'bigint' ? literalValue : BigInt(literalValue as number); + let newValue: number | bigint = -(bigVal + BigInt(1)); + if (newValue >= BigInt(Number.MIN_SAFE_INTEGER) && newValue <= BigInt(Number.MAX_SAFE_INTEGER)) { + newValue = Number(newValue); + } + return ClassType.cloneWithLiteral(classSubtype, newValue); + }); + } + } else if (literalClassName === 'bool') { + if (operator === OperatorType.Not) { + type = mapSubtypes(operandType, (subtype) => { + const classSubtype = subtype as ClassType; + return ClassType.cloneWithLiteral(classSubtype, !(classSubtype.priv.literalValue as boolean)); + }); + } + } + + return type; +} + +// Attempts to apply "literal math" for two literal operands. +function calcLiteralForBinaryOp(operator: OperatorType, leftType: Type, rightType: Type): Type | undefined { + const leftLiteralClassName = getLiteralTypeClassName(leftType); + if ( + !leftLiteralClassName || + getTypeCondition(leftType) || + someSubtypes(leftType, (subtype) => !!getTypeCondition(subtype)) + ) { + return undefined; + } + + const rightLiteralClassName = getLiteralTypeClassName(rightType); + if ( + leftLiteralClassName !== rightLiteralClassName || + getTypeCondition(rightType) || + someSubtypes(rightType, (subtype) => !!getTypeCondition(subtype)) || + getUnionSubtypeCount(leftType) * getUnionSubtypeCount(rightType) >= maxLiteralMathSubtypeCount + ) { + return undefined; + } + + // Handle str and bytes literals. + if (leftLiteralClassName === 'str' || leftLiteralClassName === 'bytes') { + if (operator === OperatorType.Add) { + return mapSubtypes(leftType, (leftSubtype) => { + return mapSubtypes(rightType, (rightSubtype) => { + const leftClassSubtype = leftSubtype as ClassType; + const rightClassSubtype = rightSubtype as ClassType; + + return ClassType.cloneWithLiteral( + leftClassSubtype, + ((leftClassSubtype.priv.literalValue as string) + rightClassSubtype.priv.literalValue) as string + ); + }); + }); + } + } + + // Handle int literals. + if (leftLiteralClassName === 'int') { + const supportedOps = [ + OperatorType.Add, + OperatorType.Subtract, + OperatorType.Multiply, + OperatorType.FloorDivide, + OperatorType.Mod, + OperatorType.Power, + OperatorType.LeftShift, + OperatorType.RightShift, + OperatorType.BitwiseAnd, + OperatorType.BitwiseOr, + OperatorType.BitwiseXor, + ]; + if (!supportedOps.includes(operator)) { + return undefined; + } + + let isValidResult = true; + + const type = mapSubtypes(leftType, (leftSubtype) => { + return mapSubtypes(rightType, (rightSubtype) => { + try { + const leftClassSubtype = leftSubtype as ClassType; + const rightClassSubtype = rightSubtype as ClassType; + const leftLiteralValue = BigInt(leftClassSubtype.priv.literalValue as number | bigint); + const rightLiteralValue = BigInt(rightClassSubtype.priv.literalValue as number | bigint); + + let newValue: number | bigint | undefined; + if (operator === OperatorType.Add) { + newValue = leftLiteralValue + rightLiteralValue; + } else if (operator === OperatorType.Subtract) { + newValue = leftLiteralValue - rightLiteralValue; + } else if (operator === OperatorType.Multiply) { + newValue = leftLiteralValue * rightLiteralValue; + } else if (operator === OperatorType.FloorDivide) { + if (rightLiteralValue !== BigInt(0)) { + newValue = leftLiteralValue / rightLiteralValue; + + // BigInt rounds to zero, but floor divide rounds to negative + // infinity, so we need to adjust the result if the signs + // of the operands are different. + if ( + newValue * rightLiteralValue !== leftLiteralValue && + leftLiteralValue < BigInt(0) !== rightLiteralValue < BigInt(0) + ) { + newValue -= BigInt(1); + } + } + } else if (operator === OperatorType.Mod) { + if (rightLiteralValue !== BigInt(0)) { + // BigInt always produces a remainder, but Python produces + // a modulo result whose sign is always the same as the + // right operand. + newValue = ((leftLiteralValue % rightLiteralValue) + rightLiteralValue) % rightLiteralValue; + } + } else if (operator === OperatorType.Power) { + if (rightLiteralValue >= BigInt(0)) { + try { + newValue = leftLiteralValue ** rightLiteralValue; + } catch { + // Don't allow if we exceed max bigint integer value. + } + } + } else if (operator === OperatorType.LeftShift) { + if (rightLiteralValue >= BigInt(0)) { + newValue = leftLiteralValue << rightLiteralValue; + } + } else if (operator === OperatorType.RightShift) { + if (rightLiteralValue >= BigInt(0)) { + newValue = leftLiteralValue >> rightLiteralValue; + } + } else if (operator === OperatorType.BitwiseAnd) { + newValue = leftLiteralValue & rightLiteralValue; + } else if (operator === OperatorType.BitwiseOr) { + newValue = leftLiteralValue | rightLiteralValue; + } else if (operator === OperatorType.BitwiseXor) { + newValue = leftLiteralValue ^ rightLiteralValue; + } + + if (newValue === undefined) { + isValidResult = false; + return undefined; + } else if (typeof newValue === 'number' && isNaN(newValue)) { + isValidResult = false; + return undefined; + } else { + // Convert back to a simple number if it fits. Leave as a bigint + // if it doesn't. + if (newValue >= Number.MIN_SAFE_INTEGER && newValue <= Number.MAX_SAFE_INTEGER) { + newValue = Number(newValue); + } + + return ClassType.cloneWithLiteral(leftClassSubtype, newValue); + } + } catch { + isValidResult = false; + return undefined; + } + }); + }); + + if (isValidResult) { + return type; + } + } + + return undefined; +} + +function customMetaclassSupportsMethod(type: Type, methodName: string): boolean { + if (!isInstantiableClass(type)) { + return false; + } + + const metaclass = type.shared.effectiveMetaclass; + if (!metaclass || !isInstantiableClass(metaclass)) { + return false; + } + + if (ClassType.isBuiltIn(metaclass, 'type')) { + return false; + } + + const memberInfo = lookUpClassMember(metaclass, methodName); + if (!memberInfo) { + return false; + } + + // If the metaclass inherits from Any or Unknown, we have to guess + // whether the method is supported. We'll assume it's not, since this + // is the most likely case. + if (isAnyOrUnknown(memberInfo.classType)) { + return false; + } + + if (isInstantiableClass(memberInfo.classType) && ClassType.isBuiltIn(memberInfo.classType, 'type')) { + return false; + } + + return true; +} + +// All functions in Python derive from object, so they inherit all +// of the capabilities of an object. This function converts a function +// to an object instance. +function convertFunctionToObject(evaluator: TypeEvaluator, type: Type) { + if (isFunctionOrOverloaded(type)) { + return evaluator.getObjectType(); + } + + return type; +} + +// Determines whether the expression refers to a variable that +// is defined within the current scope or some outer scope. +function isExpressionLocalVariable(evaluator: TypeEvaluator, node: ExpressionNode): boolean { + if (node.nodeType !== ParseNodeType.Name) { + return false; + } + + const symbolWithScope = evaluator.lookUpSymbolRecursive(node, node.d.value, /* honorCodeFlow */ false); + if (!symbolWithScope) { + return false; + } + + const currentScope = getScopeForNode(node); + return currentScope === symbolWithScope.scope; +} + +function validateContainmentOperation( + evaluator: TypeEvaluator, + operator: OperatorType, + leftTypeResult: TypeResult, + concreteLeftType: Type, + rightTypeResult: TypeResult, + errorNode: ExpressionNode, + diag: DiagnosticAddendum +): TypeResult { + let deprecatedInfo: MagicMethodDeprecationInfo | undefined; + + const type = evaluator.mapSubtypesExpandTypeVars( + rightTypeResult.type, + /* options */ undefined, + (rightSubtypeExpanded, rightSubtypeUnexpanded) => { + return evaluator.mapSubtypesExpandTypeVars( + concreteLeftType, + { conditionFilter: getTypeCondition(rightSubtypeExpanded) }, + (leftSubtype) => { + if (isAnyOrUnknown(leftSubtype) || isAnyOrUnknown(rightSubtypeUnexpanded)) { + return preserveUnknown(leftSubtype, rightSubtypeExpanded); + } + + // Viper 扩展:当右侧类型为 CPtr[T] 时,跳过 __contains__/__iter__ 检查。 + // C 语义中指针不支持 in 运算,但 Viper 可能扩展了指针的成员检查语义 + // (如检查 k 是否在指针指向的数组中)。具体语义由 Viper 编译器处理, + // LSP 直接返回 bool,不报错。 + if ( + IsViperDialect(getFileInfo(errorNode).executionEnvironment.dialect) && + isCPtrType(rightSubtypeExpanded) + ) { + return evaluator.getBuiltInObject(errorNode, 'bool'); + } + + let returnTypeResult = evaluator.getTypeOfMagicMethodCall( + rightSubtypeExpanded, + '__contains__', + [{ type: leftSubtype, isIncomplete: leftTypeResult.isIncomplete }], + errorNode, + /* inferenceContext */ undefined + ); + + if (!returnTypeResult) { + // If __contains__ was not supported, fall back + // on an iterable. + const iteratorType = evaluator.getTypeOfIterator( + { type: rightSubtypeExpanded, isIncomplete: rightTypeResult.isIncomplete }, + /* isAsync */ false, + errorNode, + /* emitNotIterableError */ false + )?.type; + + if (iteratorType && evaluator.assignType(iteratorType, leftSubtype)) { + returnTypeResult = { type: evaluator.getBuiltInObject(errorNode, 'bool') }; + } + } + + if (!returnTypeResult) { + diag.addMessage( + LocMessage.typeNotSupportBinaryOperator().format({ + operator: printOperator(operator), + leftType: evaluator.printType(leftSubtype), + rightType: evaluator.printType(rightSubtypeExpanded), + }) + ); + } + + if (returnTypeResult?.magicMethodDeprecationInfo) { + deprecatedInfo = returnTypeResult.magicMethodDeprecationInfo; + } + + return returnTypeResult?.type ?? evaluator.getBuiltInObject(errorNode, 'bool'); + } + ); + } + ); + + return { type, magicMethodDeprecationInfo: deprecatedInfo }; +} + +function validateArithmeticOperation( + evaluator: TypeEvaluator, + operator: OperatorType, + leftTypeResult: TypeResult, + rightTypeResult: TypeResult, + errorNode: ExpressionNode, + inferenceContext: InferenceContext | undefined, + diag: DiagnosticAddendum, + options: BinaryOperationOptions +): TypeResult { + let deprecatedInfo: MagicMethodDeprecationInfo | undefined; + const isIncomplete = !!leftTypeResult.isIncomplete || !!rightTypeResult.isIncomplete; + + // Viper 扩展:指针/整数类型的比较运算。 + // C 语言中指针支持所有比较运算(<, >, <=, >=, ==, !=),Viper 的 CPtr + // 类型也应如此。当左右操作数中至少有一个是 CPtr(或 CVoid,void* 通用指针), + // 且另一个是指针或整数类型时,直接返回 bool,不报 operatorNotSupported。 + const fileInfo = getFileInfo(errorNode); + const isViperDialect = IsViperDialect(fileInfo.executionEnvironment.dialect); + const isComparisonOperator = + operator === OperatorType.Equals || + operator === OperatorType.NotEquals || + operator === OperatorType.LessThan || + operator === OperatorType.LessThanOrEqual || + operator === OperatorType.GreaterThan || + operator === OperatorType.GreaterThanOrEqual; + + if (isViperDialect && isComparisonOperator) { + const leftIsPtrLike = isClassInstance(leftTypeResult.type) && + (isCPtrType(leftTypeResult.type) || isCVoidType(leftTypeResult.type)); + const rightIsPtrLike = isClassInstance(rightTypeResult.type) && + (isCPtrType(rightTypeResult.type) || isCVoidType(rightTypeResult.type)); + const leftIsIntLike = isClassInstance(leftTypeResult.type) && + ClassType.isBuiltIn(leftTypeResult.type, 'int'); + const rightIsIntLike = isClassInstance(rightTypeResult.type) && + ClassType.isBuiltIn(rightTypeResult.type, 'int'); + + // 任一方是指针,另一方是指针或整数 → 合法的指针比较 + if ((leftIsPtrLike && (rightIsPtrLike || rightIsIntLike)) || + (rightIsPtrLike && (leftIsPtrLike || leftIsIntLike))) { + return { type: evaluator.getBuiltInObject(errorNode, 'bool') }; + } + } + + const type = evaluator.mapSubtypesExpandTypeVars( + leftTypeResult.type, + /* options */ undefined, + (leftSubtypeExpanded, leftSubtypeUnexpanded) => { + return evaluator.mapSubtypesExpandTypeVars( + rightTypeResult.type, + { conditionFilter: getTypeCondition(leftSubtypeExpanded) }, + (rightSubtypeExpanded, rightSubtypeUnexpanded) => { + if (isAnyOrUnknown(leftSubtypeUnexpanded) || isAnyOrUnknown(rightSubtypeUnexpanded)) { + return preserveUnknown(leftSubtypeUnexpanded, rightSubtypeUnexpanded); + } + + // Viper 扩展:跳过 Viper 修饰类型(CStatic, CConst 等类型修饰符)。 + // 这些修饰类型只是类型修饰符(如 static, const),不参与运算。 + // 运算时只处理基础类型(如 CInt, str 等)。 + if (isViperDialect) { + if ( + isCStaticType(leftSubtypeUnexpanded) || isCStaticType(rightSubtypeUnexpanded) || + isCConstType(leftSubtypeUnexpanded) || isCConstType(rightSubtypeUnexpanded) + ) { + return UnknownType.create(isIncomplete); + } + } + + // Viper 扩展:CDefine 类型参与运算时,结果保持 CDefine 类型。 + // CDefine 是编译期宏定义(如 MAX_VOLUMES = 4),类型检查时视为任意类型, + // 支持所有算术/位运算。运算结果仍为 CDefine(编译期内联展开后为具体值)。 + // 这使得 MAX_VOLUMES * MAX_PARTITIONS 等表达式不报 operatorNotSupported。 + if (isViperDialect) { + const leftIsCDefine = isClassInstance(leftSubtypeUnexpanded) && + isCDefineType(leftSubtypeUnexpanded); + const rightIsCDefine = isClassInstance(rightSubtypeUnexpanded) && + isCDefineType(rightSubtypeUnexpanded); + if (leftIsCDefine || rightIsCDefine) { + // 返回 CDefine 类型,保持"任意传参"语义 + return leftIsCDefine ? leftSubtypeUnexpanded : rightSubtypeUnexpanded; + } + } + + // Viper 扩展:CType 子类的算术/位运算。 + // CInt/CUInt32T 等 CType 子类在底层等价于 int,但作为独立 class + // 没有 __add__ 等魔法方法。当左右操作数均为 CType 子类(或一方为 + // CType 子类,另一方为 int/float 等 Python 内置类型)时,直接返回 + // 左操作数类型(C 语义:运算结果类型由左操作数决定)。 + // 这使得 `_next_sheet_id: t.CInt = 0; _next_sheet_id += 1` 合法。 + if (isViperDialect) { + const leftIsCType = isClassInstance(leftSubtypeExpanded) && + isCTypeSubclassType(leftSubtypeExpanded); + const rightIsCType = isClassInstance(rightSubtypeExpanded) && + isCTypeSubclassType(rightSubtypeExpanded); + const leftIsBuiltinNum = isClassInstance(leftSubtypeExpanded) && + ClassType.isBuiltIn(leftSubtypeExpanded, ['int', 'float', 'bool', 'bytes', 'str']); + const rightIsBuiltinNum = isClassInstance(rightSubtypeExpanded) && + ClassType.isBuiltIn(rightSubtypeExpanded, ['int', 'float', 'bool', 'bytes', 'str']); + // 任一方是 CType 子类,另一方是 CType 子类或 Python 内置数值/字符串类型 + if ((leftIsCType && (rightIsCType || rightIsBuiltinNum)) || + (rightIsCType && (leftIsCType || leftIsBuiltinNum))) { + // 算术/位运算结果类型:优先返回 CType 子类(保持类型语义) + if (leftIsCType) { + return leftSubtypeExpanded; + } + if (rightIsCType) { + return rightSubtypeExpanded; + } + } + } + + // Viper 扩展:整数除法返回 int(C 语义)。 + // Python 中 / (.__truediv__) 对两个 int 返回 float, + // 但 C 语言中整数 / 整数 返回整数(截断除法)。 + // 当左右操作数均为整数类型时,直接返回 int,不走 __truediv__。 + if (isViperDialect && operator === OperatorType.Divide) { + const leftIsInt = isClassInstance(leftSubtypeExpanded) && + ClassType.isBuiltIn(leftSubtypeExpanded, 'int'); + const rightIsInt = isClassInstance(rightSubtypeExpanded) && + ClassType.isBuiltIn(rightSubtypeExpanded, 'int'); + if (leftIsInt && rightIsInt) { + return evaluator.getBuiltInObject(errorNode, 'int'); + } + } + + const tupleClassType = evaluator.getTupleClassType(); + + // Special-case __add__ for tuples when the types for both tuples are known. + if ( + options.isTupleAddAllowed && + operator === OperatorType.Add && + isClassInstance(leftSubtypeExpanded) && + isTupleClass(leftSubtypeExpanded) && + leftSubtypeExpanded.priv.tupleTypeArgs && + isClassInstance(rightSubtypeExpanded) && + isTupleClass(rightSubtypeExpanded) && + rightSubtypeExpanded.priv.tupleTypeArgs && + tupleClassType && + isInstantiableClass(tupleClassType) + ) { + // If at least one of the tuples is of fixed size, we can + // combine them into a precise new type. If both are unbounded + // (or contain an unbounded element), we cannot combine them + // in this manner because tuples can contain at most one + // unbounded element. + if ( + !isUnboundedTupleClass(leftSubtypeExpanded) || + !isUnboundedTupleClass(rightSubtypeExpanded) + ) { + return ClassType.cloneAsInstance( + specializeTupleClass(tupleClassType, [ + ...leftSubtypeExpanded.priv.tupleTypeArgs, + ...rightSubtypeExpanded.priv.tupleTypeArgs, + ]) + ); + } + } + + const magicMethodName = binaryOperatorMap[operator][0]; + let resultTypeResult = evaluator.getTypeOfMagicMethodCall( + convertFunctionToObject(evaluator, leftSubtypeUnexpanded), + magicMethodName, + [{ type: rightSubtypeUnexpanded, isIncomplete: rightTypeResult.isIncomplete }], + errorNode, + inferenceContext + ); + + if (!resultTypeResult && leftSubtypeUnexpanded !== leftSubtypeExpanded) { + // Try the expanded left type. + resultTypeResult = evaluator.getTypeOfMagicMethodCall( + convertFunctionToObject(evaluator, leftSubtypeExpanded), + magicMethodName, + [{ type: rightSubtypeUnexpanded, isIncomplete: rightTypeResult.isIncomplete }], + errorNode, + inferenceContext + ); + } + + if (!resultTypeResult && rightSubtypeUnexpanded !== rightSubtypeExpanded) { + // Try the expanded left and right type. + resultTypeResult = evaluator.getTypeOfMagicMethodCall( + convertFunctionToObject(evaluator, leftSubtypeExpanded), + magicMethodName, + [{ type: rightSubtypeExpanded, isIncomplete: rightTypeResult.isIncomplete }], + errorNode, + inferenceContext + ); + } + + if (!resultTypeResult) { + // Try the alternate form (swapping right and left). + const altMagicMethodName = binaryOperatorMap[operator][1]; + resultTypeResult = evaluator.getTypeOfMagicMethodCall( + convertFunctionToObject(evaluator, rightSubtypeUnexpanded), + altMagicMethodName, + [{ type: leftSubtypeUnexpanded, isIncomplete: leftTypeResult.isIncomplete }], + errorNode, + inferenceContext + ); + + if (!resultTypeResult && rightSubtypeUnexpanded !== rightSubtypeExpanded) { + // Try the expanded right type. + resultTypeResult = evaluator.getTypeOfMagicMethodCall( + convertFunctionToObject(evaluator, rightSubtypeExpanded), + altMagicMethodName, + [ + { + type: leftSubtypeUnexpanded, + isIncomplete: leftTypeResult.isIncomplete, + }, + ], + errorNode, + inferenceContext + ); + } + + if (!resultTypeResult && leftSubtypeUnexpanded !== leftSubtypeExpanded) { + // Try the expanded right and left type. + resultTypeResult = evaluator.getTypeOfMagicMethodCall( + convertFunctionToObject(evaluator, rightSubtypeExpanded), + altMagicMethodName, + [{ type: leftSubtypeExpanded, isIncomplete: leftTypeResult.isIncomplete }], + errorNode, + inferenceContext + ); + } + } + + if (!resultTypeResult) { + if (inferenceContext && !isAnyOrUnknown(inferenceContext.expectedType)) { + diag.addMessage( + LocMessage.typeNotSupportBinaryOperatorBidirectional().format({ + operator: printOperator(operator), + leftType: evaluator.printType(leftSubtypeExpanded), + rightType: evaluator.printType(rightSubtypeExpanded), + expectedType: evaluator.printType(inferenceContext.expectedType), + }) + ); + } else { + diag.addMessage( + LocMessage.typeNotSupportBinaryOperator().format({ + operator: printOperator(operator), + leftType: evaluator.printType(leftSubtypeExpanded), + rightType: evaluator.printType(rightSubtypeExpanded), + }) + ); + } + } + + if (resultTypeResult?.magicMethodDeprecationInfo) { + deprecatedInfo = resultTypeResult.magicMethodDeprecationInfo; + } + + return resultTypeResult?.type ?? UnknownType.create(isIncomplete); + } + ); + } + ); + + return { type, magicMethodDeprecationInfo: deprecatedInfo }; +} diff --git a/packages/vipeye-internal/src/analyzer/parseTreeUtils.ts b/packages/vipeye-internal/src/analyzer/parseTreeUtils.ts index b55c8e17..c39a7a2b 100644 --- a/packages/vipeye-internal/src/analyzer/parseTreeUtils.ts +++ b/packages/vipeye-internal/src/analyzer/parseTreeUtils.ts @@ -844,6 +844,89 @@ export function getEvaluationNodeForAssignmentExpression( return undefined; } +// Determines whether `node` sits within the annotation sub-tree of a class +// member type annotation (e.g. the `t.CInt` part of `id: t.CInt`, including +// nested sub-expressions such as `sheet` in `sheet: sheet.Sheet | t.CPtr`). +// +// Per PEP 526, such annotations are evaluated in the class's enclosing scope, +// not the class scope itself, so a member name never shadows a module-level +// import while the annotation is being resolved. The member name node itself +// (`valueExpr`) and any nodes inside a nested method body are excluded. +function isInClassMemberTypeAnnotation(node: ParseNode): boolean { + let curNode: ParseNode | undefined = node; + while (curNode) { + const parent = curNode.parent; + if ( + parent?.nodeType === ParseNodeType.TypeAnnotation && + parent.d.annotation === curNode + ) { + // curNode is the annotation expression (or a descendant reached via + // the loop below). Verify the TypeAnnotation lives directly in a + // class suite (not inside a nested function). + let ancestor: ParseNode | undefined = parent.parent; + while (ancestor) { + if (ancestor.nodeType === ParseNodeType.Function) { + return false; + } + if (ancestor.nodeType === ParseNodeType.Class) { + return true; + } + ancestor = ancestor.parent; + } + return false; + } + curNode = parent; + } + return false; +} + +// Per PEP 526 and Viper's scoping rules, annotations on a method's signature +// (parameter annotations and the return annotation) are evaluated in the +// class's enclosing scope, not the class scope. This mirrors the treatment of +// class member variable annotations: a module-level import whose name matches +// a class member must not be shadowed by that member while resolving the +// method signature. Annotations inside the method *body* (e.g. local variable +// annotations) are unaffected because they use the function scope, which +// already skips the class scope during name lookup. +function isInClassMethodSignatureAnnotation(node: ParseNode): boolean { + let curNode: ParseNode | undefined = node; + let funcNode: ParseNode | undefined; + + // Walk up to find whether the node lives within a parameter annotation or + // a function return annotation. + while (curNode) { + const parent = curNode.parent; + if (parent?.nodeType === ParseNodeType.Parameter && parent.d.annotation === curNode) { + // Parameter annotation: the enclosing function is the parameter's parent. + funcNode = parent.parent ?? undefined; + break; + } + if (parent?.nodeType === ParseNodeType.Function && parent.d.returnAnnotation === curNode) { + funcNode = parent; + break; + } + curNode = parent; + } + + if (!funcNode || funcNode.nodeType !== ParseNodeType.Function) { + return false; + } + + // The function must be a direct member of a class (not a nested function + // inside a method body, and not a module-level function). + let ancestor: ParseNode | undefined = funcNode.parent; + while (ancestor) { + if (ancestor.nodeType === ParseNodeType.Function) { + return false; + } + if (ancestor.nodeType === ParseNodeType.Class) { + return true; + } + ancestor = ancestor.parent; + } + return false; +} + // Returns the parse node corresponding to the scope that is used to evaluate // a symbol referenced in the specified node. export function getEvaluationScopeNode(node: ParseNode): EvaluationScopeInfo { @@ -945,8 +1028,31 @@ export function getEvaluationScopeNode(node: ParseNode): EvaluationScopeInfo { } if (prevNode === curNode.d.suite) { + // PEP 526: class member type annotations (e.g. the `t.CInt` + // in `id: t.CInt`, or `sheet.Sheet | t.CPtr` in + // `sheet: sheet.Sheet | t.CPtr`) are evaluated in the + // class's enclosing scope, not the class scope. Otherwise a + // module-level import whose name matches a member being + // declared would be shadowed by that member, breaking the + // annotation. Only the annotation sub-tree is affected; + // the member name itself and all other class-body nodes + // continue to use the class scope. + if (isInClassMemberTypeAnnotation(node) || isInClassMethodSignatureAnnotation(node)) { + if (curNode.d.typeParams) { + const scopeNode = curNode.d.typeParams; + if (getScope(scopeNode) !== undefined) { + return { + node: scopeNode, + useChainedModuleLevelScopes: true, + }; + } + } + break; + } + + // All other nodes within the class suite use the class scope. if (getScope(curNode) !== undefined) { - return { node: curNode }; + return { node: curNode, useChainedModuleLevelScopes: true }; } } diff --git a/packages/vipeye-internal/src/analyzer/service.ts b/packages/vipeye-internal/src/analyzer/service.ts index 6fca51b0..230f4842 100644 --- a/packages/vipeye-internal/src/analyzer/service.ts +++ b/packages/vipeye-internal/src/analyzer/service.ts @@ -67,6 +67,7 @@ import { findConfigFileHereOrUp, findPyprojectTomlFile, findPyprojectTomlFileHereOrUp, + findVpjInChildDirs, } from './serviceUtils'; import { SourceEnumerator } from './sourceEnumerator'; import { IPythonMode } from './sourceFile'; @@ -831,6 +832,14 @@ export class AnalyzerService { // project root directory contains the config file. configFilePath = findConfigFile(this.fs, projectRoot); + // Viper 扩展:LSP 模式下,如果工作区根没有配置文件,扫描一级子目录查找 .vpj。 + // 支持工作区根(如 Viper_VSCode)包含子项目(如 Test/project.vpj)的场景。 + // 找到 .vpj 后,projectRoot 调整为 .vpj 所在目录(后续 L842 逻辑), + // .vpj 的 includes 会被 _initializeFromViperProjectJson 解析为 defaultExtraPaths。 + if (!configFilePath && commandLineOptions.fromLanguageServer) { + configFilePath = findVpjInChildDirs(this.fs, projectRoot); + } + // If viper is being executed from the command line, the working // directory may be deep within a project, and we need to walk up the // directory hierarchy to find the project root. diff --git a/packages/vipeye-internal/src/analyzer/serviceUtils.ts b/packages/vipeye-internal/src/analyzer/serviceUtils.ts index 47b94dcf..5812df56 100644 --- a/packages/vipeye-internal/src/analyzer/serviceUtils.ts +++ b/packages/vipeye-internal/src/analyzer/serviceUtils.ts @@ -69,6 +69,32 @@ export function findVpjFileInDir(fs: ReadOnlyFileSystem, dirUri: Uri): Uri | und return undefined; } +// Viper 扩展:在目录的一级子目录中查找 .vpj 文件。 +// 用于 LSP 模式下工作区根(如 Viper_VSCode)未直接包含 .vpj, +// 但子目录(如 Test/)包含 Viper 项目配置的场景。 +// 仅扫描一级子目录(非递归),避免深层遍历开销。 +// 失败时返回 undefined。 +export function findVpjInChildDirs(fs: ReadOnlyFileSystem, dirUri: Uri): Uri | undefined { + try { + if (!fs.existsSync(dirUri)) { + return undefined; + } + const entries = fs.readdirEntriesSync(dirUri); + for (const entry of entries) { + if (entry.isDirectory()) { + const childDir = dirUri.resolvePaths(entry.name); + const vpjFile = findVpjFileInDir(fs, childDir); + if (vpjFile) { + return vpjFile; + } + } + } + } catch { + // 读取目录失败:跳过 + } + return undefined; +} + // 校验 project.json / project.vpj 是否为 Viper(TransPyC)项目配置。 // project.json 是常见文件名,仅当含 source_dir / compiler / linker 之一时才视为 Viper 配置。 // 读取或解析失败时按非 Viper 项目处理,返回 false。 diff --git a/packages/vipeye-internal/src/analyzer/typeEvaluator.ts b/packages/vipeye-internal/src/analyzer/typeEvaluator.ts index 1aed66ca..30dbff06 100644 --- a/packages/vipeye-internal/src/analyzer/typeEvaluator.ts +++ b/packages/vipeye-internal/src/analyzer/typeEvaluator.ts @@ -1,29611 +1,30334 @@ -/* - * 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, - isCConstType, - isCStaticType, - isViperIntegerType, - isViperCIntegerTypeName, - 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; - } - - // 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; - } - - // 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); - } - - 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)) { - // 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)) { - 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); - - // 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'); - - 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; - } - - 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; - } - - 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(); - - // 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 - ) { - 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 { - addDiagnostic( - DiagnosticRule.reportUndefinedVariable, - LocMessage.symbolIsUndefined().format({ name }), - node - ); - - 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; - } - - 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; - } - - 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: { - 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 { - // Does the module export a top-level __getattr__ function? - // Viper has no module-level __getattr__ (PEP 562) semantics, - // so skip this dynamic attribute resolution entirely. - if (usage.method === 'get' && !IsViperDialect(fileInfo.executionEnvironment.dialect)) { - const getAttrSymbol = ModuleType.getField(baseType, '__getattr__'); - if (getAttrSymbol) { - const isModuleGetAttrSupported = - PythonVersion.isGreaterOrEqualTo( - fileInfo.executionEnvironment.pythonVersion, - pythonVersion3_7 - ) || getAttrSymbol.getDeclarations().some((decl) => decl.uri.hasExtension('.vpi')); - - if (isModuleGetAttrSupported) { - const getAttrTypeResult = getEffectiveTypeOfSymbolForUsage(getAttrSymbol); - if (isFunction(getAttrTypeResult.type)) { - const returnTypeResult = getEffectiveReturnTypeResult(getAttrTypeResult.type); - type = returnTypeResult.type; - if (getAttrTypeResult.isIncomplete || returnTypeResult.isIncomplete) { - isIncomplete = true; - } - } - } - } - } - - // 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); - } - } - - // Check for builtin classes that will generate runtime exceptions if subscripted. - if ((flags & EvalFlags.ForwardRefs) === 0) { - // We can skip this check if the class is used within a PEP 526 variable - // type annotation within a class or function. For some undocumented reason, - // they don't result in runtime exceptions when used in this manner. - let skipSubscriptCheck = (flags & EvalFlags.VarTypeAnnotation) !== 0; - if (skipSubscriptCheck) { - const scopeNode = ParseTreeUtils.getExecutionScopeNode(node); - if (scopeNode?.nodeType === ParseNodeType.Module) { - skipSubscriptCheck = false; - } - } - - if (!skipSubscriptCheck) { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - // Viper is an AOT static language with no PEP 585 subscript - // restrictions, so skip the runtime-subscriptable check. - if ( - !IsViperDialect(fileInfo.executionEnvironment.dialect) && - isInstantiableClass(baseTypeResult.type) && - ClassType.isBuiltIn(baseTypeResult.type) && - !baseTypeResult.type.priv.aliasName - ) { - const minPythonVersion = nonSubscriptableBuiltinTypes.get(baseTypeResult.type.shared.fullName); - if ( - minPythonVersion !== undefined && - PythonVersion.isLessThan(fileInfo.executionEnvironment.pythonVersion, minPythonVersion) && - !fileInfo.isStubFile - ) { - addDiagnostic( - DiagnosticRule.reportIndexIssue, - LocMessage.classNotRuntimeSubscriptable().format({ - name: baseTypeResult.type.priv.aliasName || baseTypeResult.type.shared.name, - }), - node.d.leftExpr - ); - } - } - } - } - - 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']) - ) { - // 从 t 模块中查找 CChar 类型别名,保留 t.CChar 显示信息。 - 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 intType = getBuiltInObject(node, 'int'); - const ccharAliasInfo: TypeAliasInfo = { - shared: { - name: 'CChar', - fullName: 't.CChar', - moduleName: 't', - fileUri: viperIndexFileInfo.fileUri, - typeVarScopeId: '', - isTypeAliasType: false, - typeParams: undefined, - computedVariance: undefined, - }, - typeArgs: undefined, - }; - return TypeBase.cloneForTypeAlias(intType, ccharAliasInfo); - } - // 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; - } - 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__'; - } - } - - 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.typeAnnotation); - } - - // 对于其他节点类型,尝试遍历子节点 - 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; - } - - 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, t.CPtr)), - // 语义是强转为"指向该整数类型的指针"(即 (uint32_t*)addr), - // 此时返回 CPtr[T] 实例类型,以支持指针下标赋值 ptr[i] = value。 - const integerInstanceType = ClassType.cloneAsInstance(baseTypeResult.type); - const cptrPointerType = tryGetCPtrTypeFromSecondArg(node); - if (cptrPointerType) { - // 构造 CPtr[IntegerT] 实例类型 - const cptrInstance = ClassType.cloneAsInstance(cptrPointerType); - typeResult.type = ClassType.specialize(cptrInstance, [integerInstanceType]); - } else { - typeResult.type = integerInstanceType; - } - } 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)) { - // Assign the argument type back to the expected type to assign - // values to any unification variables. - const clonedConstraints = constraints.clone(); - if ( - assignType( - expectedType, - argType, - /* 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 行)的保持声明类型逻辑对应。 - if (IsViperDialect(fileInfo.executionEnvironment.dialect) && node.d.leftExpr.nodeType === ParseNodeType.TypeAnnotation) { - 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; - } - - // 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(); - } - } - } - - // 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(); - - // 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; - - // Handle PEP 562 support for module-level __getattr__ function, - // introduced in Python 3.7. - // Viper has no module-level __getattr__ semantics; skip it. - if ( - !IsViperDialect(fileInfo.executionEnvironment.dialect) && - (PythonVersion.isGreaterOrEqualTo( - fileInfo.executionEnvironment.pythonVersion, - pythonVersion3_7 - ) || - fileInfo.isStubFile) - ) { - const getAttrSymbol = importLookupInfo.symbolTable.get('__getattr__'); - if (getAttrSymbol) { - const getAttrType = getEffectiveTypeOfSymbol(getAttrSymbol); - if (isFunction(getAttrType)) { - symbolType = getEffectiveReturnType(getAttrType); - reportError = false; - } - } - } - } 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)) { - // Try to find a member that has a declared type. If so, that - // overrides any inferred types. - let member = lookUpObjectMember(subtype, memberName, MemberAccessFlags.DeclaredTypesOnly); - if (!member) { - member = lookUpObjectMember(subtype, 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) && ClassType.isBuiltIn(declaredType, 'TypeAlias')) { - 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); - } - - 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; - if (usageNode && typedDecls.length > 1) { - 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); - - // 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 扩展: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/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 扩展: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) && - (isCConstType(subtype) || - isCStaticType(subtype) || - isCPtrType(subtype) || - isCVoidType(subtype) || - isCDefineType(subtype) || - isCArrayType(subtype)) - ); - if (hasViperModifier) { - // 检查 destType 是否可以赋值给 TypeSet 中的某个子类型 - for (const subtype of srcSubtypes) { - if (assignType(destType, subtype, undefined, constraints, flags, recursionCount + 1)) { - 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 type = getTypeOfAnnotation(decl.typeAnnotationNode, { varTypeAnnotation: true, allowClassVar: true }); - return isClassInstance(type) && ClassType.isBuiltIn(type, 'TypeAlias'); - } - - 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; -} +/* + * 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); + } + + 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 类型迭代返回 int(CChar)。 + // Viper 语义:str = bytes | CPtr(const char*),迭代返回单个字符 CChar = int。 + if (ClassType.isBuiltIn(subtype, 'str')) { + 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; + } + + 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,允许后续定义的符号被引用。 + if (!IsViperDialect(fileInfo.executionEnvironment.dialect)) { + addDiagnostic( + DiagnosticRule.reportUndefinedVariable, + LocMessage.symbolIsUndefined().format({ name }), + node + ); + } + + 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 { + // Does the module export a top-level __getattr__ function? + // Viper has no module-level __getattr__ (PEP 562) semantics, + // so skip this dynamic attribute resolution entirely. + if (usage.method === 'get' && !IsViperDialect(fileInfo.executionEnvironment.dialect)) { + const getAttrSymbol = ModuleType.getField(baseType, '__getattr__'); + if (getAttrSymbol) { + const isModuleGetAttrSupported = + PythonVersion.isGreaterOrEqualTo( + fileInfo.executionEnvironment.pythonVersion, + pythonVersion3_7 + ) || getAttrSymbol.getDeclarations().some((decl) => decl.uri.hasExtension('.vpi')); + + if (isModuleGetAttrSupported) { + const getAttrTypeResult = getEffectiveTypeOfSymbolForUsage(getAttrSymbol); + if (isFunction(getAttrTypeResult.type)) { + const returnTypeResult = getEffectiveReturnTypeResult(getAttrTypeResult.type); + type = returnTypeResult.type; + if (getAttrTypeResult.isIncomplete || returnTypeResult.isIncomplete) { + isIncomplete = true; + } + } + } + } + } + + // 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); + } + } + + // Check for builtin classes that will generate runtime exceptions if subscripted. + if ((flags & EvalFlags.ForwardRefs) === 0) { + // We can skip this check if the class is used within a PEP 526 variable + // type annotation within a class or function. For some undocumented reason, + // they don't result in runtime exceptions when used in this manner. + let skipSubscriptCheck = (flags & EvalFlags.VarTypeAnnotation) !== 0; + if (skipSubscriptCheck) { + const scopeNode = ParseTreeUtils.getExecutionScopeNode(node); + if (scopeNode?.nodeType === ParseNodeType.Module) { + skipSubscriptCheck = false; + } + } + + if (!skipSubscriptCheck) { + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + // Viper is an AOT static language with no PEP 585 subscript + // restrictions, so skip the runtime-subscriptable check. + if ( + !IsViperDialect(fileInfo.executionEnvironment.dialect) && + isInstantiableClass(baseTypeResult.type) && + ClassType.isBuiltIn(baseTypeResult.type) && + !baseTypeResult.type.priv.aliasName + ) { + const minPythonVersion = nonSubscriptableBuiltinTypes.get(baseTypeResult.type.shared.fullName); + if ( + minPythonVersion !== undefined && + PythonVersion.isLessThan(fileInfo.executionEnvironment.pythonVersion, minPythonVersion) && + !fileInfo.isStubFile + ) { + addDiagnostic( + DiagnosticRule.reportIndexIssue, + LocMessage.classNotRuntimeSubscriptable().format({ + name: baseTypeResult.type.priv.aliasName || baseTypeResult.type.shared.name, + }), + node.d.leftExpr + ); + } + } + } + } + + 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']) + ) { + // 从 t 模块中查找 CChar 类型别名,保留 t.CChar 显示信息。 + 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 intType = getBuiltInObject(node, 'int'); + const ccharAliasInfo: TypeAliasInfo = { + shared: { + name: 'CChar', + fullName: 't.CChar', + moduleName: 't', + fileUri: viperIndexFileInfo.fileUri, + typeVarScopeId: '', + isTypeAliasType: false, + typeParams: undefined, + computedVariance: undefined, + }, + typeArgs: undefined, + }; + return TypeBase.cloneForTypeAlias(intType, ccharAliasInfo); + } + // 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; + } + 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__'; + } + } + + 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)) { + // Assign the argument type back to the expected type to assign + // values to any unification variables. + const clonedConstraints = constraints.clone(); + if ( + assignType( + expectedType, + argType, + /* 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 { + // 无返回类型注解的函数默认返回 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; + + // Handle PEP 562 support for module-level __getattr__ function, + // introduced in Python 3.7. + // Viper has no module-level __getattr__ semantics; skip it. + if ( + !IsViperDialect(fileInfo.executionEnvironment.dialect) && + (PythonVersion.isGreaterOrEqualTo( + fileInfo.executionEnvironment.pythonVersion, + pythonVersion3_7 + ) || + fileInfo.isStubFile) + ) { + const getAttrSymbol = importLookupInfo.symbolTable.get('__getattr__'); + if (getAttrSymbol) { + const getAttrType = getEffectiveTypeOfSymbol(getAttrSymbol); + if (isFunction(getAttrType)) { + symbolType = getEffectiveReturnType(getAttrType); + reportError = false; + } + } + } + } 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)) { + // Try to find a member that has a declared type. If so, that + // overrides any inferred types. + let member = lookUpObjectMember(subtype, memberName, MemberAccessFlags.DeclaredTypesOnly); + if (!member) { + member = lookUpObjectMember(subtype, 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; + if (usageNode && typedDecls.length > 1) { + 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。 + 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) + ); + 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 扩展: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 扩展:字符串字面量 → 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/analyzer/typePrinter.ts b/packages/vipeye-internal/src/analyzer/typePrinter.ts index 7ab18124..c2fc9918 100644 --- a/packages/vipeye-internal/src/analyzer/typePrinter.ts +++ b/packages/vipeye-internal/src/analyzer/typePrinter.ts @@ -99,6 +99,9 @@ export const enum PrintTypeFlags { // Omit TypeVar scopes. OmitTypeVarScope = 1 << 13, + + // Viper 方言:将 NoneType 显示为 CVoid(C 语义 void)。 + ViperDialect = 1 << 14, } export type FunctionReturnTypeCallback = (type: FunctionType) => Type; @@ -736,7 +739,8 @@ function printUnionType( if (noneIndex >= 0 && !subtypeHandledSet.has(noneIndex)) { const typeWithoutNone = removeNoneFromUnion(type); if (isNever(typeWithoutNone)) { - return 'None'; + // Viper 方言下 None 表示 CVoid(C 语义 void)。 + return (printTypeFlags & PrintTypeFlags.ViperDialect) !== 0 ? 'CVoid' : 'None'; } const optionalType = printTypeInternal( @@ -1596,5 +1600,10 @@ export function getPrintTypeFlags(configOptions: ConfigOptions): PrintTypeFlags flags |= PrintTypeFlags.PEP604; } + // Viper 方言下 NoneType 显示为 CVoid。 + if (configOptions.dialect === 'viper') { + flags |= PrintTypeFlags.ViperDialect; + } + return flags; } diff --git a/packages/vipeye-internal/src/analyzer/types.ts b/packages/vipeye-internal/src/analyzer/types.ts index 78e0ee6d..79ddc3f4 100644 --- a/packages/vipeye-internal/src/analyzer/types.ts +++ b/packages/vipeye-internal/src/analyzer/types.ts @@ -1,4189 +1,4462 @@ -/* - * types.ts - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT license. - * Author: Eric Traut - * - * Representation of types used during type analysis within Python. - */ - -import { partition } from '../common/collectionUtils'; -import { assert } from '../common/debug'; -import { Uri } from '../common/uri/uri'; -import { ArgumentNode, ExpressionNode, NameNode, ParamCategory, TypeAnnotationNode } from '../parser/parseNodes'; -import { ClassDeclaration, FunctionDeclaration, SpecialBuiltInClassDeclaration } from './declaration'; -import { Symbol, SymbolTable } from './symbol'; - -export const enum TypeCategory { - // Name is not bound to a value of any type - Unbound, - - // Implicit Any type - Unknown, - - // Type can be anything - Any, - - // The bottom type, equivalent to an empty union - Never, - - // Callable type - Function, - - // Functions defined with @overload decorator - Overloaded, - - // Class definition - Class, - - // Module instance - Module, - - // Union of two or more other types - Union, - - // Type variable - TypeVar, -} - -export const enum TypeFlags { - None = 0, - - // This type refers to something that can be instantiated. - Instantiable = 1 << 0, - - // This type refers to something that has been instantiated. - Instance = 1 << 1, - - // This type is inferred within a py.typed source file and could be - // inferred differently by other type checkers. - Ambiguous = 1 << 2, - - // This mask indicates which flags should be considered significant - // when comparing two types for equivalence. - TypeCompatibilityMask = Instantiable | Instance, -} - -export type UnionableType = - | UnboundType - | UnknownType - | AnyType - | FunctionType - | OverloadedType - | ClassType - | ModuleType - | TypeVarType; - -export type Type = UnionableType | NeverType | UnionType; - -// A string that uniquely identifies a TypeVar that is bound to a scope -// (a generic class, function, or type alias). -export type TypeVarScopeId = string; -export const UnificationScopeId: TypeVarScopeId = '-'; - -// Information about an enum member that can be used within a Literal -// type annotation. -export class EnumLiteral { - constructor( - public classFullName: string, - public className: string, - public itemName: string, - public itemType: Type, - public isReprEnum: boolean - ) {} - - getName() { - return `${this.classFullName}.${this.itemName}`; - } -} - -export class SentinelLiteral { - constructor(public classFullName: string, public className: string) {} - - getName() { - return this.className; - } -} - -export type LiteralValue = number | bigint | boolean | string | EnumLiteral | SentinelLiteral; - -export type TypeSourceId = number; - -// This constant controls the maximum number of nested types (i.e. types -// used as type arguments or parameter types in other types) before we -// give up. This constant was previously set to 32, but there were certain -// pathological recursive types where this resulted in a hang. It was also -// previously lowered to 10, but this caused some legitimate failures in -// code that used numpy. Even at 16, there are some legitimate failures in -// numpy. -export const maxTypeRecursionCount = 20; - -export type InheritanceChain = (ClassType | UnknownType)[]; - -// Options used with the isTypeSame function -export interface TypeSameOptions { - ignorePseudoGeneric?: boolean; - ignoreTypeFlags?: boolean; - ignoreConditions?: boolean; - ignoreTypedDictNarrowEntries?: boolean; - honorTypeForm?: boolean; - honorIsTypeArgExplicit?: boolean; - treatAnySameAsUnknown?: boolean; -} - -export interface TypeAliasSharedInfo { - name: string; - fullName: string; - moduleName: string; - fileUri: Uri; - - typeVarScopeId: TypeVarScopeId; - - // Is the type alias a PEP 695 TypeAliasType instance? - isTypeAliasType: boolean; - - // Type parameters, if type alias is generic - typeParams: TypeVarType[] | undefined; - - // Lazily-evaluated variance of type parameters based on how - // they are used in the type alias - computedVariance: Variance[] | undefined; -} - -export interface TypeAliasInfo { - shared: TypeAliasSharedInfo; - - // Type argument, if type alias is specialized - typeArgs: Type[] | undefined; -} - -interface CachedTypeInfo { - // Type converted to instantiable and instance by convertToInstance - // and convertToInstantiable (cached) - instantiableType?: Type; - instanceType?: Type; - - // Type converted to instantiable and instance by TypeBase methods (cached) - typeBaseInstantiableType?: Type; - typeBaseInstanceType?: Type; - - // Requires specialization flag (cached) - requiresSpecialization?: boolean; -} - -export interface TypeBaseProps { - // Used to handle nested references to instantiable classes - // (e.g. type[type[type[T]]]). If the field isn't present, - // it is assumed to be zero - instantiableDepth: number | undefined; - - // Used in cases where the type is a special form when used in a - // value expression such as UnionType, Literal, or Required - specialForm: ClassType | undefined; - - // Used for "type form" objects, the evaluated form - // of a type expression in a value expression context - typeForm: Type | undefined; - - // Used only for type aliases - typeAliasInfo: TypeAliasInfo | undefined; - - // Used only for types that are conditioned on a TypeVar - condition: TypeCondition[] | undefined; -} - -export interface TypeBase { - category: T; - flags: TypeFlags; - - // Optional properties common to all types. - props: TypeBaseProps | undefined; - - // Optional cached values are not cloned. - cached: CachedTypeInfo | undefined; - - // Fields that are specific to a particular type category. - // These are shared between type instances and are not - // cloned. - shared: object | undefined; - - // Fields that are specific to a particular type category. - // These are private to each type instances and are - // cloned. - priv: object | undefined; -} - -export namespace TypeBase { - export function isInstantiable(type: TypeBase) { - return (type.flags & TypeFlags.Instantiable) !== 0; - } - - export function isInstance(type: TypeBase) { - return (type.flags & TypeFlags.Instance) !== 0; - } - - export function isAmbiguous(type: TypeBase) { - return (type.flags & TypeFlags.Ambiguous) !== 0; - } - - export function addProps(type: TypeBase): TypeBaseProps { - if (!type.props) { - type.props = { - instantiableDepth: undefined, - specialForm: undefined, - typeForm: undefined, - typeAliasInfo: undefined, - condition: undefined, - }; - } - return type.props; - } - - export function getInstantiableDepth(type: TypeBase) { - return type.props?.instantiableDepth ?? 0; - } - - export function setSpecialForm(type: TypeBase, specialForm: ClassType | undefined) { - TypeBase.addProps(type).specialForm = specialForm; - } - - export function setInstantiableDepth(type: TypeBase, depth: number | undefined) { - TypeBase.addProps(type).instantiableDepth = depth; - } - - export function setTypeAliasInfo(type: TypeBase, typeAliasInfo: TypeAliasInfo | undefined) { - TypeBase.addProps(type).typeAliasInfo = typeAliasInfo; - } - - export function setTypeForm(type: TypeBase, typeForm: Type | undefined) { - TypeBase.addProps(type).typeForm = typeForm; - } - - export function setCondition(type: TypeBase, condition: TypeCondition[] | undefined) { - TypeBase.addProps(type).condition = condition; - } - - export function cloneType>(type: T): T { - const clone = { ...type }; - if (type.props) { - clone.props = { ...type.props }; - } - if (type.priv) { - clone.priv = { ...type.priv }; - } - clone.cached = undefined; - return clone; - } - - export function cloneAsSpecialForm>(type: T, specialForm: ClassType | undefined): T { - const clone = TypeBase.cloneType(type); - TypeBase.setSpecialForm(clone, specialForm); - return clone; - } - - export function cloneTypeAsInstance(type: T, cache: boolean): T { - assert(TypeBase.isInstantiable(type)); - - const newInstance = TypeBase.cloneType(type); - - // Remove type form information from the type. - if (newInstance.props?.typeForm) { - TypeBase.setTypeForm(newInstance, undefined); - } - - const depth = newInstance.props?.instantiableDepth; - if (depth === undefined) { - newInstance.flags &= ~TypeFlags.Instantiable; - newInstance.flags |= TypeFlags.Instance; - } else if (depth <= 1) { - TypeBase.setInstantiableDepth(newInstance, undefined); - } else { - TypeBase.setInstantiableDepth(newInstance, depth - 1); - } - - // Should we cache it for next time? - if (cache) { - if (!type.cached) { - type.cached = {}; - } - - type.cached.typeBaseInstanceType = newInstance; - } - - return newInstance; - } - - export function cloneTypeAsInstantiable(type: T, cache: boolean): T { - const newInstance: T = TypeBase.cloneType(type); - - if (TypeBase.isInstance(type)) { - newInstance.flags &= ~TypeFlags.Instance; - newInstance.flags |= TypeFlags.Instantiable; - } else { - const oldDepth = type.props?.instantiableDepth; - TypeBase.setInstantiableDepth(newInstance, oldDepth === undefined ? 1 : oldDepth + 1); - } - - // Remove type alias information because the type will no longer match - // that of the type alias definition. - if (newInstance.props?.typeAliasInfo) { - TypeBase.setTypeAliasInfo(newInstance, undefined); - } - - // Remove type form information from the type. - if (newInstance.props?.typeForm) { - TypeBase.setTypeForm(newInstance, undefined); - } - - // Should we cache it for next time? - if (cache) { - if (!type.cached) { - type.cached = {}; - } - - type.cached.typeBaseInstantiableType = newInstance; - } - - return newInstance; - } - - export function cloneForTypeAlias(type: T, aliasInfo: TypeAliasInfo): T { - const typeClone = cloneType(type); - - TypeBase.setTypeAliasInfo(typeClone, aliasInfo); - - return typeClone; - } - - export function cloneWithTypeForm(type: T, typeForm: Type | undefined): T { - const typeClone = cloneType(type); - - TypeBase.setTypeForm(typeClone, typeForm); - - return typeClone; - } - - export function cloneForCondition(type: T, condition: TypeCondition[] | undefined): T { - // Handle the common case where there are no conditions. In this case, - // cloning isn't necessary. - if (type.props?.condition === undefined && condition === undefined) { - return type; - } - - const typeClone = cloneType(type); - TypeBase.setCondition(typeClone, condition); - return typeClone; - } - - export function cloneForAmbiguousType(type: Type) { - if (TypeBase.isAmbiguous(type)) { - return type; - } - - const typeClone = cloneType(type); - typeClone.flags |= TypeFlags.Ambiguous; - return typeClone; - } -} - -export interface UnboundType extends TypeBase {} - -export namespace UnboundType { - const _instance: UnboundType = { - category: TypeCategory.Unbound, - flags: TypeFlags.Instantiable | TypeFlags.Instance, - props: undefined, - cached: undefined, - shared: undefined, - priv: undefined, - }; - - export function create() { - // All Unbound objects are the same, so use a shared instance. - return _instance; - } - - export function convertToInstance(type: UnboundType): UnboundType { - // Remove the "special form" if present. Otherwise return the existing type. - return type.props?.specialForm ? UnboundType.create() : type; - } -} - -export interface UnknownDetailsPriv { - // Flag that indicates whether the type is a placeholder for an incomplete - // type during code flow analysis. - isIncomplete: boolean; - - // A "possible type" is a form of a "weak union" where the actual - // type is unknown, but it could be one of the subtypes in the union. - // This is used for overload matching in cases where more than one - // overload matches due to an argument that evaluates to Any or Unknown. - possibleType: Type | undefined; -} - -export interface UnknownType extends TypeBase { - priv: UnknownDetailsPriv; -} - -export namespace UnknownType { - const _instance: UnknownType = { - category: TypeCategory.Unknown, - flags: TypeFlags.Instantiable | TypeFlags.Instance, - props: undefined, - cached: undefined, - shared: undefined, - priv: { - isIncomplete: false, - possibleType: undefined, - }, - }; - const _incompleteInstance: UnknownType = { - category: TypeCategory.Unknown, - flags: TypeFlags.Instantiable | TypeFlags.Instance, - props: undefined, - cached: undefined, - shared: undefined, - priv: { - isIncomplete: true, - possibleType: undefined, - }, - }; - - export function create(isIncomplete = false) { - return isIncomplete ? _incompleteInstance : _instance; - } - - export function createPossibleType(possibleType: Type, isIncomplete: boolean) { - const unknownWithPossibleType: UnknownType = { - category: TypeCategory.Unknown, - flags: TypeFlags.Instantiable | TypeFlags.Instance, - props: undefined, - cached: undefined, - shared: undefined, - priv: { - isIncomplete, - possibleType, - }, - }; - - return unknownWithPossibleType; - } - - export function convertToInstance(type: UnknownType): UnknownType { - // Remove the "special form" if present. Otherwise return the existing type. - return type.props?.specialForm ? UnknownType.create(type.priv.isIncomplete) : type; - } -} - -export interface ModuleDetailsPriv { - fields: SymbolTable; - docString: string | undefined; - - // If a field lookup isn't found, should the type of the - // resulting field be Any/Unknown or treated as an error? - notPresentFieldType: AnyType | UnknownType | undefined; - - // A "loader" module includes symbols that were injected by - // the module loader. We keep these separate so we don't - // pollute the symbols exported by the module itself. - loaderFields: SymbolTable; - - // The period-delimited import name of this module. - moduleName: string; - - fileUri: Uri; -} - -export interface ModuleType extends TypeBase { - priv: ModuleDetailsPriv; -} - -export namespace ModuleType { - export function create(moduleName: string, fileUri: Uri, symbolTable?: SymbolTable) { - const newModuleType: ModuleType = { - category: TypeCategory.Module, - flags: TypeFlags.Instantiable | TypeFlags.Instantiable, - props: undefined, - cached: undefined, - shared: undefined, - priv: { - fields: symbolTable || new Map(), - docString: undefined, - notPresentFieldType: undefined, - loaderFields: new Map(), - moduleName, - fileUri, - }, - }; - return newModuleType; - } - - export function getField(moduleType: ModuleType, name: string): Symbol | undefined { - // Always look for the symbol in the module's fields before consulting - // the loader fields. The loader runs before the module, so its values - // will be overwritten by the module. - let symbol = moduleType.priv.fields.get(name); - - if (moduleType.priv.loaderFields) { - if (!symbol) { - symbol = moduleType.priv.loaderFields.get(name); - } else if (symbol.getDeclarations().length === 1) { - // If the symbol is hidden when accessed via the module but is - // also accessible through a loader field, use the latter so it - // isn't flagged as an error. - const loaderSymbol = moduleType.priv.loaderFields.get(name); - if (loaderSymbol && !loaderSymbol.isExternallyHidden()) { - symbol = loaderSymbol; - } - } - } - return symbol; - } -} - -export interface DataClassEntry { - name: string; - classType: ClassType; - mroClass?: ClassType; - isClassVar: boolean; - isKeywordOnly: boolean; - alias?: string | undefined; - hasDefault?: boolean | undefined; - isDefaultFactory?: boolean | undefined; - nameNode: NameNode | undefined; - typeAnnotationNode: TypeAnnotationNode | undefined; - defaultExpr?: ExpressionNode | undefined; - includeInInit: boolean; - type: Type; - converter?: ArgumentNode | undefined; -} - -export interface TypedDictEntry { - valueType: Type; - isRequired: boolean; - isReadOnly: boolean; - isProvided: boolean; -} - -export interface TypedDictEntries { - knownItems: Map; - extraItems?: TypedDictEntry | undefined; -} - -export const enum ClassTypeFlags { - None = 0, - - // Class is defined in the "builtins" or "typing" file. - BuiltIn = 1 << 0, - - // Class requires special-case handling because it - // exhibits non-standard behavior or is not defined - // formally as a class. Examples include 'Optional' - // and 'Union'. - SpecialBuiltIn = 1 << 1, - - // Introduced in PEP 589, TypedDict classes provide a way - // to specify type hints for dictionaries with different - // value types and a limited set of static keys. - TypedDictClass = 1 << 2, - - // Used in conjunction with TypedDictClass, indicates that - // the TypedDict class is marked "closed". - TypedDictMarkedClosed = 1 << 3, - - // Used in conjunction with TypedDictClass, indicates that - // the TypedDict class is marked "closed" or one or more of - // its superclasses is marked "closed". - TypedDictEffectivelyClosed = 1 << 4, - - // Used in conjunction with TypedDictClass, indicates that - // the dictionary values can be omitted. - CanOmitDictValues = 1 << 5, - - // The class derives from a class that has the ABCMeta - // metaclass. Such classes are allowed to contain - // @abstractmethod decorators. - SupportsAbstractMethods = 1 << 6, - - // Derives from property class and has the semantics of - // a property (with optional setter, deleter). - PropertyClass = 1 << 7, - - // The class is decorated with a "@final" decorator - // indicating that it cannot be subclassed. - Final = 1 << 8, - - // The class derives directly from "Protocol". - ProtocolClass = 1 << 9, - - // A class whose constructor (__init__ method) does not have - // annotated types and is treated as though each parameter - // is a generic type for purposes of type inference. - PseudoGenericClass = 1 << 10, - - // A protocol class that is "runtime checkable" can be used - // in an isinstance call. - RuntimeCheckable = 1 << 11, - - // The type is defined in the typing_extensions.pyi file. - TypingExtensionClass = 1 << 12, - - // The class type is in the process of being evaluated and - // is not yet complete. This allows us to detect cases where - // the class refers to itself (e.g. uses itself as a type - // argument to one of its generic base classes). - PartiallyEvaluated = 1 << 13, - - // The class or one of its ancestors defines a __class_getitem__ - // method that is used for subscripting. This is not set if the - // class is generic, and therefore supports standard subscripting - // semantics. - HasCustomClassGetItem = 1 << 14, - - // The tuple class requires special-case handling for its type arguments. - TupleClass = 1 << 15, - - // The class has a metaclass of EnumMeta or derives from - // a class that has this metaclass. - EnumClass = 1 << 16, - - // Properties that are defined using the @classmethod decorator. - ClassProperty = 1 << 17, - - // Class is declared within a type stub file. - DefinedInStub = 1 << 18, - - // Decorated with @type_check_only. - TypeCheckOnly = 1 << 20, - - // Created with the NewType call. - NewTypeClass = 1 << 21, - - // Class is allowed to be used as an implicit type alias even - // though it is not defined using a `class` statement. - ValidTypeAliasClass = 1 << 22, - - // A special form is not compatible with type[T] and cannot - // be directly instantiated. - SpecialFormClass = 1 << 23, - - // This class is rejected when used as the second argument to - // an isinstance or issubclass call. - IllegalIsinstanceClass = 1 << 24, -} - -export interface DataClassBehaviors { - skipGenerateInit?: boolean; - skipGenerateEq?: boolean; - generateOrder?: boolean; - generateSlots?: boolean; - generateHash?: boolean; - matchArgs?: boolean; - keywordOnly?: boolean; - frozen?: boolean; - frozenDefault?: boolean; - fieldDescriptorNames: string[]; -} - -export interface ClassDetailsShared { - name: string; - fullName: string; - moduleName: string; - fileUri: Uri; - flags: ClassTypeFlags; - typeSourceId: TypeSourceId; - baseClasses: Type[]; - mro: (ClassType | AnyType | UnknownType)[]; - declaration?: ClassDeclaration | SpecialBuiltInClassDeclaration | undefined; - declaredMetaclass?: ClassType | UnknownType | undefined; - effectiveMetaclass?: ClassType | UnknownType | undefined; - fields: SymbolTable; - typeParams: TypeVarType[]; - typeVarScopeId?: TypeVarScopeId | undefined; - docString?: string | undefined; - dataClassEntries?: DataClassEntry[] | undefined; - dataClassBehaviors?: DataClassBehaviors | undefined; - namedTupleEntries?: Set | undefined; - typedDictEntries?: TypedDictEntries | undefined; - typedDictExtraItemsExpr?: ExpressionNode | undefined; - localSlotsNames?: string[]; - - // If the class is decorated with a @deprecated decorator, this - // string provides the message to be displayed when the class - // is used. - deprecatedMessage?: string | undefined; - - // A cache of protocol classes (indexed by the class full name) - // that have been determined to be compatible or incompatible - // with this class. We use "object" here to avoid a circular dependency. - // It's actually a map of ProtocolCompatibility objects. - protocolCompatibility?: object; - - // Transforms to apply if this class is used as a metaclass - // or a base class. - classDataClassTransform?: DataClassBehaviors | undefined; - - // Indicates that one or more type parameters has an - // autovariance, so variance must be inferred. - requiresVarianceInference?: boolean; - - // A cached value that indicates whether an instance of this class - // is hashable (i.e. does not override "__hash__" with None). - isInstanceHashable?: boolean; - - // Callback for deferred synthesis of methods in symbol table. - synthesizeMethodsDeferred?: () => void; - - // Callback for calculating inherited slots names. - calculateInheritedSlotsNamesDeferred?: () => void; - inheritedSlotsNamesCached?: string[]; -} - -export interface TupleTypeArg { - type: Type; - - // Does the type argument represent a single value or - // an "unbounded" (zero or more) arguments? - isUnbounded: boolean; - - // For tuples captured from a callable, this indicates - // the corresponding positional parameter has a default - // argument and can therefore be omitted. - isOptional?: boolean; -} - -export interface PropertyMethodInfo { - // The decorated function (fget, fset, fdel) for a property. This is - // normally a single function, but it can be an OverloadedType if the - // accessor (currently only the setter) is overloaded. - methodType: FunctionType | OverloadedType; - - // The class that declared this function - classType: ClassType | undefined; -} - -export interface ClassDetailsPriv { - // A generic class that has been completely or partially - // specialized will have type arguments that correspond to - // some or all of the type parameters. - typeArgs?: Type[] | undefined; - - // If a generic container class (like a list or dict) is known - // to contain no elements, its type arguments may be "Unknown". - // This value allows us to elide the Unknown when it's safe to - // do so. - isEmptyContainer?: boolean | undefined; - - // For tuples, the class definition calls for a single type parameter but - // the spec allows the programmer to provide an arbitrary number of - // type arguments. This field holds the individual type arguments - // while the "typeArgs" field holds the derived non-variadic - // type argument, which is the union of the tuple type arguments. - tupleTypeArgs?: TupleTypeArg[] | undefined; - - // We sometimes package multiple types into a tuple internally - // for matching against a variadic type variable or another unpacked - // tuple. We need to be able to distinguish this case from normal tuples. - isUnpacked?: boolean | undefined; - - // If type arguments are present, were they explicit (i.e. - // provided explicitly in the code)? - isTypeArgExplicit?: boolean | undefined; - - // This class type represents the class and any classes that - // derive from it, as opposed to the original class only. This - // distinction is important in certain scenarios like instantiation - // of abstract or protocol classes. - includeSubclasses?: boolean; - - // This class type represents the class and any auto-promotion - // types that PEP 484 indicates should be treated as subclasses - // when the type appears within a type annotation. - includePromotions?: boolean; - - // Some types can be further constrained to have - // literal types (e.g. true or 'string' or 3). - literalValue?: LiteralValue | undefined; - - // The typing module defines aliases for builtin types - // (e.g. Tuple, List, Dict). This field holds the alias - // name. - aliasName?: string | undefined; - - // Used for "narrowing" of typed dicts where some entries - // that are not required have been confirmed to be present - // through the use of a guard expression. - typedDictNarrowedEntries?: Map | undefined; - - // Indicates that the typed dict class should be considered "partial", - // i.e. all of its entries are effectively NotRequired and only - // writable entries are considered present, and they are marked read-only. - // This is used for the TypedDict "update" method. - isTypedDictPartial?: boolean; - - // Indicates whether the class is an asymmetric descriptor - // or property - one where the __get__ and __set__ types differ. - // If undefined, it hasn't been tested yet for asymmetry. - isAsymmetricDescriptor?: boolean; - - // Indicates whether the class has an asymmetric __getattr__ and - // __setattr__ signature. - isAsymmetricAttributeAccessor?: boolean; - - // Special-case fields for property classes. - fgetInfo?: PropertyMethodInfo | undefined; - fsetInfo?: PropertyMethodInfo | undefined; - fdelInfo?: PropertyMethodInfo | undefined; - - // Provides the deprecated message specifically for instances of - // the "deprecated" class. This allows these instances to be used - // as decorators for other classes or functions. - deprecatedInstanceMessage?: string | undefined; - - // Special-case fields for partial class. - partialCallType?: Type | undefined; -} - -export interface ClassType extends TypeBase { - shared: ClassDetailsShared; - priv: ClassDetailsPriv; -} - -export namespace ClassType { - export function createInstantiable( - name: string, - fullName: string, - moduleName: string, - fileUri: Uri, - flags: ClassTypeFlags, - typeSourceId: TypeSourceId, - declaredMetaclass: ClassType | UnknownType | undefined, - effectiveMetaclass: ClassType | UnknownType | undefined, - docString?: string - ) { - const newClass: ClassType = { - category: TypeCategory.Class, - flags: TypeFlags.Instantiable, - props: undefined, - cached: undefined, - shared: { - name, - fullName, - moduleName, - fileUri, - flags, - typeSourceId, - baseClasses: [], - declaredMetaclass, - effectiveMetaclass, - mro: [], - fields: new Map(), - typeParams: [], - docString, - }, - priv: {}, - }; - - return newClass; - } - - export function cloneAsInstance(type: ClassType, includeSubclasses = true): ClassType { - if (TypeBase.isInstance(type)) { - return type; - } - - if (includeSubclasses && type.cached?.typeBaseInstanceType) { - return type.cached.typeBaseInstanceType as ClassType; - } - - const newInstance = TypeBase.cloneTypeAsInstance(type, /* cache */ includeSubclasses); - if (newInstance.props?.specialForm) { - TypeBase.setSpecialForm(newInstance, undefined); - } - - if (includeSubclasses) { - newInstance.priv.includeSubclasses = true; - } - - return newInstance; - } - - export function cloneAsInstantiable(type: ClassType, includeSubclasses = true): ClassType { - if (includeSubclasses && type.cached?.typeBaseInstantiableType) { - return type.cached.typeBaseInstantiableType as ClassType; - } - - const newInstance = TypeBase.cloneTypeAsInstantiable(type, includeSubclasses); - if (includeSubclasses) { - newInstance.priv.includeSubclasses = true; - } - - return newInstance; - } - - export function specialize( - classType: ClassType, - typeArgs: Type[] | undefined, - isTypeArgExplicit?: boolean, - includeSubclasses = false, - tupleTypeArgs?: TupleTypeArg[], - isEmptyContainer?: boolean - ): ClassType { - const newClassType = TypeBase.cloneType(classType); - - newClassType.priv.typeArgs = typeArgs?.length === 0 ? undefined : typeArgs; - - // If the user passed undefined for this argument, infer it - // based on whether typeArgs was provided. - if (isTypeArgExplicit === undefined) { - isTypeArgExplicit = !!typeArgs; - } - - newClassType.priv.isTypeArgExplicit = isTypeArgExplicit; - - if (includeSubclasses) { - newClassType.priv.includeSubclasses = true; - } - - newClassType.priv.tupleTypeArgs = tupleTypeArgs ? [...tupleTypeArgs] : undefined; - - if (isEmptyContainer !== undefined) { - newClassType.priv.isEmptyContainer = isEmptyContainer; - } - - return newClassType; - } - - export function cloneIncludeSubclasses(classType: ClassType, includeSubclasses = true) { - if (!!classType.priv.includeSubclasses === includeSubclasses) { - return classType; - } - - const newClassType = TypeBase.cloneType(classType); - newClassType.priv.includeSubclasses = includeSubclasses; - return newClassType; - } - - export function cloneWithLiteral(classType: ClassType, value: LiteralValue | undefined): ClassType { - const newClassType = TypeBase.cloneType(classType); - newClassType.priv.literalValue = value; - - // Remove type alias information because the type will no longer match - // that of the type alias definition if we change the literal type. - if (newClassType.props?.typeAliasInfo) { - TypeBase.setTypeAliasInfo(newClassType, undefined); - } - - return newClassType; - } - - export function cloneForDeprecatedInstance(type: ClassType, deprecatedMessage?: string): ClassType { - const newClassType = TypeBase.cloneType(type); - newClassType.priv.deprecatedInstanceMessage = deprecatedMessage; - return newClassType; - } - - export function cloneForTypingAlias(classType: ClassType, aliasName: string): ClassType { - const newClassType = TypeBase.cloneType(classType); - newClassType.priv.aliasName = aliasName; - return newClassType; - } - - export function cloneForNarrowedTypedDictEntries( - classType: ClassType, - narrowedEntries?: Map - ): ClassType { - const newClassType = TypeBase.cloneType(classType); - newClassType.priv.typedDictNarrowedEntries = narrowedEntries; - return newClassType; - } - - export function cloneForPartialTypedDict(classType: ClassType): ClassType { - const newClassType = TypeBase.cloneType(classType); - newClassType.priv.isTypedDictPartial = true; - return newClassType; - } - - export function cloneRemoveTypePromotions(classType: ClassType): ClassType { - if (!classType.priv.includePromotions) { - return classType; - } - - const newClassType = TypeBase.cloneType(classType); - if (newClassType.priv.includePromotions !== undefined) { - newClassType.priv.includePromotions = undefined; - } - return newClassType; - } - - export function cloneForPartial(classType: ClassType, partialCallType: Type): ClassType { - const newClassType = TypeBase.cloneType(classType); - newClassType.priv.partialCallType = partialCallType; - return newClassType; - } - - export function cloneForUnpacked(classType: ClassType): ClassType { - if (classType.priv.isUnpacked) { - return classType; - } - - const newClassType = TypeBase.cloneType(classType); - newClassType.priv.isUnpacked = true; - return newClassType; - } - - export function cloneForPacked(classType: ClassType): ClassType { - if (!classType.priv.isUnpacked) { - return classType; - } - - const newClassType = TypeBase.cloneType(classType); - newClassType.priv.isUnpacked = false; - return newClassType; - } - - export function cloneWithNewFlags(classType: ClassType, newFlags: ClassTypeFlags): ClassType { - const newClassType = TypeBase.cloneType(classType); - newClassType.shared = { ...newClassType.shared }; - newClassType.shared.flags = newFlags; - return newClassType; - } - - export function isLiteralValueSame(type1: ClassType, type2: ClassType): boolean { - if (type1.priv.literalValue === undefined) { - return type2.priv.literalValue === undefined; - } else if (type2.priv.literalValue === undefined) { - return false; - } - - if (type1.priv.literalValue instanceof EnumLiteral) { - if (type2.priv.literalValue instanceof EnumLiteral) { - return type1.priv.literalValue.itemName === type2.priv.literalValue.itemName; - } - return false; - } - - if (type1.priv.literalValue instanceof SentinelLiteral) { - if (type2.priv.literalValue instanceof SentinelLiteral) { - return type1.priv.literalValue.classFullName === type2.priv.literalValue.classFullName; - } - return false; - } - - return type1.priv.literalValue === type2.priv.literalValue; - } - - // Determines whether two typed dict classes are equivalent given - // that one or both have narrowed entries (i.e. entries that are - // guaranteed to be present). - export function isTypedDictNarrowedEntriesSame(type1: ClassType, type2: ClassType): boolean { - if (type1.priv.typedDictNarrowedEntries) { - if (!type2.priv.typedDictNarrowedEntries) { - return false; - } - - const tdEntries1 = type1.priv.typedDictNarrowedEntries; - const tdEntries2 = type2.priv.typedDictNarrowedEntries; - - if (tdEntries1.size !== tdEntries2.size) { - return false; - } - - let key: string; - let entry1: TypedDictEntry; - for ([key, entry1] of tdEntries1.entries()) { - const entry2 = tdEntries2.get(key); - if (!entry2) { - return false; - } - if (entry1.isProvided !== entry2.isProvided) { - return false; - } - } - } else if (type2.priv.typedDictNarrowedEntries) { - return false; - } - - return true; - } - - // Determines whether typed dict class type1 is a narrower form of type2, - // i.e. all of the "narrowed entries" found within type2 are also found - // within type1. - export function isTypedDictNarrower(type1: ClassType, type2: ClassType): boolean { - const tdEntries2 = type2.priv.typedDictNarrowedEntries; - if (!tdEntries2) { - return true; - } - - const tdEntries1 = type1.priv.typedDictNarrowedEntries ?? new Map(); - - let key: string; - let entry2: TypedDictEntry; - for ([key, entry2] of tdEntries2.entries()) { - if (entry2.isProvided) { - const entry1 = tdEntries1.get(key); - if (!entry1?.isProvided) { - return false; - } - } - } - - return true; - } - - // Is the class generic but not specialized? - export function isUnspecialized(classType: ClassType) { - return classType.shared.typeParams.length > 0 && classType.priv.typeArgs === undefined; - } - - export function isSpecialBuiltIn(classType: ClassType, className?: string) { - if (!(classType.shared.flags & ClassTypeFlags.SpecialBuiltIn) && !classType.priv.aliasName) { - return false; - } - - if (className !== undefined) { - return classType.shared.name === className; - } - - return true; - } - - export function isBuiltIn(classType: ClassType, className?: string | string[]) { - if (!(classType.shared.flags & ClassTypeFlags.BuiltIn)) { - return false; - } - - if (className !== undefined) { - const classArray = Array.isArray(className) ? className : [className]; - return classArray.some( - (name) => - name === classType.shared.name || - name === classType.shared.fullName || - name === classType.priv.aliasName - ); - } - - return true; - } - - export function supportsAbstractMethods(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.SupportsAbstractMethods); - } - - export function isDataClass(classType: ClassType) { - return !!classType.shared.dataClassBehaviors; - } - - export function isDataClassSkipGenerateInit(classType: ClassType) { - return !!classType.shared.dataClassBehaviors?.skipGenerateInit; - } - - export function isDataClassSkipGenerateEq(classType: ClassType) { - return !!classType.shared.dataClassBehaviors?.skipGenerateEq; - } - - export function isDataClassFrozen(classType: ClassType) { - return !!classType.shared.dataClassBehaviors?.frozen; - } - - export function isDataClassGenerateOrder(classType: ClassType) { - return !!classType.shared.dataClassBehaviors?.generateOrder; - } - - export function isDataClassKeywordOnly(classType: ClassType) { - return !!classType.shared.dataClassBehaviors?.keywordOnly; - } - - export function isDataClassGenerateSlots(classType: ClassType) { - return !!classType.shared.dataClassBehaviors?.generateSlots; - } - - export function isDataClassGenerateHash(classType: ClassType) { - return !!classType.shared.dataClassBehaviors?.generateHash; - } - - export function isTypeCheckOnly(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.TypeCheckOnly); - } - - export function isNewTypeClass(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.NewTypeClass); - } - - export function isValidTypeAliasClass(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.ValidTypeAliasClass); - } - - export function isSpecialFormClass(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.SpecialFormClass); - } - - export function isIllegalIsinstanceClass(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.IllegalIsinstanceClass); - } - - export function isTypedDictClass(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.TypedDictClass); - } - - export function isCanOmitDictValues(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.CanOmitDictValues); - } - - export function isTypedDictMarkedClosed(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.TypedDictMarkedClosed); - } - - export function isTypedDictEffectivelyClosed(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.TypedDictEffectivelyClosed); - } - - export function isEnumClass(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.EnumClass); - } - - export function isPropertyClass(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.PropertyClass); - } - - export function isClassProperty(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.ClassProperty); - } - - export function isFinal(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.Final); - } - - export function isProtocolClass(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.ProtocolClass); - } - - export function isDefinedInStub(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.DefinedInStub); - } - - export function isPseudoGenericClass(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.PseudoGenericClass); - } - - export function getDataClassEntries(classType: ClassType): DataClassEntry[] { - classType.shared.synthesizeMethodsDeferred?.(); - - return classType.shared.dataClassEntries || []; - } - - export function isRuntimeCheckable(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.RuntimeCheckable); - } - - export function isTypingExtensionClass(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.TypingExtensionClass); - } - - export function isPartiallyEvaluated(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.PartiallyEvaluated); - } - - export function hasCustomClassGetItem(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.HasCustomClassGetItem); - } - - export function isTupleClass(classType: ClassType) { - return !!(classType.shared.flags & ClassTypeFlags.TupleClass); - } - - export function getTypeParams(classType: ClassType) { - return classType.shared.typeParams; - } - - export function derivesFromAnyOrUnknown(classType: ClassType) { - return classType.shared.mro.some((baseClass) => isAnyOrUnknown(baseClass)); - } - - export function getSymbolTable(classType: ClassType) { - classType.shared.synthesizeMethodsDeferred?.(); - - return classType.shared.fields; - } - - export function getInheritedSlotsNames(classType: ClassType) { - // First synthesize methods if needed. The slots entries - // can depend on synthesized methods. - classType.shared.synthesizeMethodsDeferred?.(); - - classType.shared.calculateInheritedSlotsNamesDeferred?.(); - - return classType.shared.inheritedSlotsNamesCached; - } - - // Similar to isPartiallyEvaluated except that it also looks at all of the - // classes in the MRO list for this class to see if any of them are still - // partially evaluated. - export function isHierarchyPartiallyEvaluated(classType: ClassType) { - return ( - ClassType.isPartiallyEvaluated(classType) || - classType.shared.mro.some((mroClass) => isClass(mroClass) && ClassType.isPartiallyEvaluated(mroClass)) - ); - } - - export function hasNamedTupleEntry(classType: ClassType, name: string): boolean { - if (!classType.shared.namedTupleEntries) { - return false; - } - - return classType.shared.namedTupleEntries.has(name); - } - - // Same as isTypeSame except that it doesn't compare type arguments. - export function isSameGenericClass(classType: ClassType, type2: ClassType, recursionCount = 0) { - if (!classType.priv.isTypedDictPartial !== !type2.priv.isTypedDictPartial) { - return false; - } - - if (TypeBase.isInstance(classType) !== TypeBase.isInstance(type2)) { - return false; - } - - if (TypeBase.getInstantiableDepth(classType) !== TypeBase.getInstantiableDepth(type2)) { - return false; - } - - const class1Details = classType.shared; - const class2Details = type2.shared; - - if (class1Details === class2Details) { - return true; - } - - // Compare most of the details fields. We intentionally skip the isAbstractClass - // flag because it gets set dynamically. - if ( - class1Details.fullName !== class2Details.fullName || - class1Details.flags !== class2Details.flags || - class1Details.typeSourceId !== class2Details.typeSourceId || - class1Details.baseClasses.length !== class2Details.baseClasses.length || - class1Details.typeParams.length !== class2Details.typeParams.length - ) { - return false; - } - - if (recursionCount > maxTypeRecursionCount) { - return true; - } - recursionCount++; - - // Special-case NamedTuple and Tuple classes because we rewrite the base classes - // in these cases. - if (ClassType.isBuiltIn(classType, 'NamedTuple') && ClassType.isBuiltIn(type2, 'NamedTuple')) { - return true; - } - if (ClassType.isBuiltIn(classType, 'tuple') && ClassType.isBuiltIn(type2, 'tuple')) { - return true; - } - - // Make sure the base classes match. - for (let i = 0; i < class1Details.baseClasses.length; i++) { - if ( - !isTypeSame( - class1Details.baseClasses[i], - class2Details.baseClasses[i], - { ignorePseudoGeneric: true }, - recursionCount - ) - ) { - return false; - } - } - - if (class1Details.declaredMetaclass || class2Details.declaredMetaclass) { - if ( - !class1Details.declaredMetaclass || - !class2Details.declaredMetaclass || - !isTypeSame( - class1Details.declaredMetaclass, - class2Details.declaredMetaclass, - { ignorePseudoGeneric: true }, - recursionCount - ) - ) { - return false; - } - } - - for (let i = 0; i < class1Details.typeParams.length; i++) { - if ( - !isTypeSame( - class1Details.typeParams[i], - class2Details.typeParams[i], - { ignorePseudoGeneric: true }, - recursionCount - ) - ) { - return false; - } - } - - return true; - } - - // Determines whether this is a subclass (derived class) - // of the specified class. If the caller passes an empty - // array to inheritanceChain, it will be filled in by - // the call to include the chain of inherited classes starting - // with type2 and ending with this type. - export function isDerivedFrom( - subclassType: ClassType, - parentClassType: ClassType, - inheritanceChain?: InheritanceChain - ): boolean { - // Is it the exact same class? - if (isSameGenericClass(subclassType, parentClassType)) { - // Handle literal types. - if (parentClassType.priv.literalValue !== undefined) { - if ( - subclassType.priv.literalValue === undefined || - !ClassType.isLiteralValueSame(parentClassType, subclassType) - ) { - return false; - } - } - - if (inheritanceChain) { - inheritanceChain.push(subclassType); - } - return true; - } - - // Handle built-in types like 'dict' and 'list', which are all - // subclasses of object even though they are not explicitly declared - // that way. - if (isBuiltIn(subclassType) && isBuiltIn(parentClassType, 'object')) { - if (inheritanceChain) { - inheritanceChain.push(parentClassType); - } - return true; - } - - // Handle the case where the subclass is a type[type[T]] and the parent - // class is type. - const subclassDepth = TypeBase.getInstantiableDepth(subclassType); - if (subclassDepth > 0) { - if (isBuiltIn(parentClassType, 'type') && TypeBase.getInstantiableDepth(parentClassType) < subclassDepth) { - if (inheritanceChain) { - inheritanceChain.push(parentClassType); - } - return true; - } - } - - // Handle the case where both source and dest are property objects. This - // special case is needed because we synthesize a new class for each - // property declaration. - if (ClassType.isBuiltIn(subclassType, 'property') && ClassType.isBuiltIn(parentClassType, 'property')) { - if (inheritanceChain) { - inheritanceChain.push(subclassType); - } - return true; - } - - for (const baseClass of subclassType.shared.baseClasses) { - if (isInstantiableClass(baseClass)) { - if (isDerivedFrom(baseClass, parentClassType, inheritanceChain)) { - if (inheritanceChain) { - inheritanceChain.push(subclassType); - } - return true; - } - } else if (isAnyOrUnknown(baseClass)) { - if (inheritanceChain) { - inheritanceChain.push(UnknownType.create()); - } - return true; - } - } - - return false; - } - - export function getReverseMro(classType: ClassType): (ClassType | UnknownType | AnyType)[] { - return classType.shared.mro.slice(0).reverse(); - } -} - -export enum FunctionParamFlags { - None = 0, - - // Is the name of the parameter synthesize internally? - NameSynthesized = 1 << 0, - - // Does the parameter have an explicitly-declared type? - TypeDeclared = 1 << 1, - - // Is the type of the parameter inferred? - TypeInferred = 1 << 2, -} - -export interface FunctionParam { - category: ParamCategory; - flags: FunctionParamFlags; - name: string | undefined; - - // Use getParamType to access this field. - // eslint-disable-next-line @typescript-eslint/naming-convention - _type: Type; - - // Use getParamDefaultType to access this field. - // eslint-disable-next-line @typescript-eslint/naming-convention - _defaultType: Type | undefined; - - defaultExpr: ExpressionNode | undefined; -} - -export namespace FunctionParam { - export function create( - category: ParamCategory, - type: Type, - flags = FunctionParamFlags.None, - name?: string, - defaultType?: Type, - defaultExpr?: ExpressionNode - ): FunctionParam { - return { category, flags, name, _type: type, _defaultType: defaultType, defaultExpr }; - } - - export function isNameSynthesized(param: FunctionParam) { - return !!(param.flags & FunctionParamFlags.NameSynthesized); - } - - export function isTypeDeclared(param: FunctionParam) { - return !!(param.flags & FunctionParamFlags.TypeDeclared); - } - - export function isTypeInferred(param: FunctionParam) { - return !!(param.flags & FunctionParamFlags.TypeInferred); - } -} - -export function isPositionOnlySeparator(param: FunctionParam) { - // A simple parameter with no name is treated as a "/" separator. - return param.category === ParamCategory.Simple && !param.name; -} - -export function isKeywordOnlySeparator(param: FunctionParam) { - // An *args parameter with no name is treated as a "*" separator. - return param.category === ParamCategory.ArgsList && !param.name; -} - -export const enum FunctionTypeFlags { - None = 0, - - // Function is a __new__ method; first parameter is "cls" - ConstructorMethod = 1 << 0, - - // Function is decorated with @classmethod; first parameter is "cls"; - // can be bound to associated class - ClassMethod = 1 << 1, - - // Function is decorated with @staticmethod; cannot be bound to class - StaticMethod = 1 << 2, - - // Function is decorated with @abstractmethod - AbstractMethod = 1 << 3, - - // Function contains "yield" or "yield from" statements - Generator = 1 << 4, - - // Skip check that validates that all parameters without default - // value expressions have corresponding arguments; used for - // named tuples in some cases - DisableDefaultChecks = 1 << 5, - - // Method has no declaration in user code, it's synthesized; used - // for implied methods such as those used in namedtuple, dataclass, etc. - SynthesizedMethod = 1 << 6, - - // Decorated with @type_check_only. - TypeCheckOnly = 1 << 7, - - // Function is decorated with @overload - Overloaded = 1 << 8, - - // Function is declared with async keyword - Async = 1 << 9, - - // Function is declared within a type stub fille - StubDefinition = 1 << 11, - - // Function is declared within a module that claims to be fully typed - // (i.e. a "py.typed" file is present). - PyTypedDefinition = 1 << 12, - - // Function is decorated with @final - Final = 1 << 13, - - // Function has one or more parameters that are missing type annotations - UnannotatedParams = 1 << 14, - - // The *args and **kwargs parameters do not need to be present for this - // function to be compatible. This is used for Callable[..., x] and - // ... type arguments to ParamSpec and Concatenate. - GradualCallableForm = 1 << 15, - - // This function represents the value bound to a ParamSpec, so its return - // type is not meaningful. - ParamSpecValue = 1 << 16, - - // The function type is in the process of being evaluated and - // is not yet complete. This allows us to detect cases where - // the function refers to itself (e.g. uses a type annotation - // that contains a forward reference that requires the function - // type itself to be evaluated first). - PartiallyEvaluated = 1 << 17, - - // Decorated with @override as defined in PEP 698. - Overridden = 1 << 18, - - // Decorated with @no_type_check. - NoTypeCheck = 1 << 19, - - // Function defined in one of the core stdlib modules. - BuiltIn = 1 << 20, -} - -interface FunctionDetailsShared { - name: string; - fullName: string; - moduleName: string; - flags: FunctionTypeFlags; - typeParams: TypeVarType[]; - parameters: FunctionParam[]; - declaredReturnType: Type | undefined; - declaration: FunctionDeclaration | undefined; - typeVarScopeId: TypeVarScopeId | undefined; - docString: string | undefined; - deprecatedMessage: string | undefined; - - // If this is a method, this refers to the class that contains it. - methodClass: ClassType | undefined; - - // Transforms to apply if this function is used - // as a decorator. - decoratorDataClassBehaviors: DataClassBehaviors | undefined; - - // Inferred return type. Filled in lazily. - inferredReturnType?: { - type: Type; - isIncomplete?: boolean; - evaluationCount?: number; - }; -} - -export interface SpecializedFunctionTypes { - // Specialized types for each of the parameters in the "parameters" array. - parameterTypes: Type[]; - - // Specialized types of default arguments for each parameter in - // the "parameters" array. If an entry is undefined or the entire array - // is missing, there is no specialized type, and the original "defaultType" - // should be used. - parameterDefaultTypes: (Type | undefined)[] | undefined; - - // Specialized type of the declared return type. Undefined if there is - // no declared return type. - returnType: Type | undefined; -} - -export interface CallSiteInferenceTypeCacheEntry { - paramTypes: Type[]; - returnType: Type; -} - -export interface SignatureWithOffsets { - type: FunctionType | OverloadedType; - expressionOffsets: number[]; -} - -export interface FunctionDetailsPriv { - // For __new__ and __init__ methods, the TypeVar scope ID of the - // associated class. - constructorTypeVarScopeId?: TypeVarScopeId | undefined; - - // A function type can be specialized (i.e. generic type - // variables replaced by a concrete type). - specializedTypes?: SpecializedFunctionTypes | undefined; - - // Call-site return type inference cache. - callSiteReturnTypeCache?: CallSiteInferenceTypeCacheEntry[]; - - // If this is a bound function where the first parameter - // was stripped from the original unbound function, the - // (specialized) type of that stripped parameter. - strippedFirstParamType?: Type | undefined; - - // If this is a bound function where the first parameter - // was stripped from the original unbound function, - // the class or object to which the function was bound. - boundToType?: ClassType | undefined; - - // The flags for the function prior to binding - preBoundFlags?: FunctionTypeFlags; - - // If this function is part of an overloaded function, this - // refers back to the overloaded function type. - overloaded?: OverloadedType; - - // If this function is created with a "Callable" annotation with - // type arguments? This allows us to detect and report an error - // when this is used in an isinstance call. - isCallableWithTypeArgs?: boolean; -} - -export interface FunctionType extends TypeBase { - shared: FunctionDetailsShared; - priv: FunctionDetailsPriv; -} - -export namespace FunctionType { - export function createInstance( - name: string, - fullName: string, - moduleName: string, - functionFlags: FunctionTypeFlags, - docString?: string - ) { - return create(name, fullName, moduleName, functionFlags, TypeFlags.Instance, docString); - } - - export function createInstantiable(functionFlags: FunctionTypeFlags, docString?: string) { - return create('', '', '', functionFlags, TypeFlags.Instantiable, docString); - } - - export function createSynthesizedInstance(name: string, additionalFlags = FunctionTypeFlags.None) { - return create(name, name, '', additionalFlags | FunctionTypeFlags.SynthesizedMethod, TypeFlags.Instance); - } - - function create( - name: string, - fullName: string, - moduleName: string, - functionFlags: FunctionTypeFlags, - typeFlags: TypeFlags, - docString?: string - ) { - const newFunctionType: FunctionType = { - category: TypeCategory.Function, - flags: typeFlags, - props: undefined, - cached: undefined, - shared: { - name, - fullName, - moduleName, - flags: functionFlags, - typeParams: [], - parameters: [], - declaredReturnType: undefined, - declaration: undefined, - typeVarScopeId: undefined, - docString, - deprecatedMessage: undefined, - methodClass: undefined, - decoratorDataClassBehaviors: undefined, - }, - priv: {}, - }; - return newFunctionType; - } - - // Creates a deep copy of the function type, including a fresh - // version of _functionDetails. - export function clone(type: FunctionType, stripFirstParam = false, boundToType?: ClassType): FunctionType { - const newFunction = TypeBase.cloneType(type); - - newFunction.shared = { ...type.shared }; - newFunction.priv.preBoundFlags = newFunction.shared.flags; - newFunction.priv.boundToType = boundToType; - - if (boundToType) { - if (type.shared.name === '__new__' || type.shared.name === '__init__') { - newFunction.priv.constructorTypeVarScopeId = boundToType.shared.typeVarScopeId; - } - } - - if (stripFirstParam) { - if (type.shared.parameters.length > 0) { - if (type.shared.parameters[0].category === ParamCategory.Simple) { - if (type.shared.parameters.length > 0) { - // Stash away the effective type of the first parameter or - // Any if it was inferred. - newFunction.priv.strippedFirstParamType = FunctionParam.isTypeInferred( - type.shared.parameters[0] - ) - ? AnyType.create() - : getParamType(type, 0); - } - newFunction.shared.parameters = type.shared.parameters.slice(1); - } - } else { - stripFirstParam = false; - } - } - - if (type.props?.typeAliasInfo) { - TypeBase.setTypeAliasInfo(newFunction, type.props.typeAliasInfo); - } - - if (type.priv.specializedTypes) { - newFunction.priv.specializedTypes = { - parameterTypes: stripFirstParam - ? type.priv.specializedTypes.parameterTypes.slice(1) - : type.priv.specializedTypes.parameterTypes, - parameterDefaultTypes: stripFirstParam - ? type.priv.specializedTypes.parameterDefaultTypes?.slice(1) - : type.priv.specializedTypes.parameterDefaultTypes, - returnType: type.priv.specializedTypes.returnType, - }; - } - - newFunction.shared.inferredReturnType = type.shared.inferredReturnType; - - return newFunction; - } - - export function cloneAsInstance(type: FunctionType): FunctionType { - if (type.cached?.typeBaseInstanceType) { - return type.cached.typeBaseInstanceType as FunctionType; - } - - const newInstance = TypeBase.cloneTypeAsInstance(type, /* cache */ true); - if (newInstance.props?.specialForm) { - TypeBase.setSpecialForm(newInstance, undefined); - } - return newInstance; - } - - export function cloneAsInstantiable(type: FunctionType): FunctionType { - if (type.cached?.typeBaseInstantiableType) { - return type.cached.typeBaseInstantiableType as FunctionType; - } - - const newInstance = TypeBase.cloneTypeAsInstantiable(type, /* cache */ true); - return newInstance; - } - - // Creates a shallow copy of the function type with new - // specialized types. The clone shares the _functionDetails - // with the object being cloned. - export function specialize(type: FunctionType, specializedTypes: SpecializedFunctionTypes): FunctionType { - const newFunction = TypeBase.cloneType(type); - - assert(specializedTypes.parameterTypes.length === type.shared.parameters.length); - if (specializedTypes.parameterDefaultTypes) { - assert(specializedTypes.parameterDefaultTypes.length === type.shared.parameters.length); - } - - newFunction.priv.specializedTypes = specializedTypes; - return newFunction; - } - - // Creates a new function based on the parameters of another function. - export function applyParamSpecValue(type: FunctionType, paramSpecValue: FunctionType): FunctionType { - const hasPositionalOnly = paramSpecValue.shared.parameters.some((param) => isPositionOnlySeparator(param)); - const newFunction = FunctionType.cloneRemoveParamSpecArgsKwargs(TypeBase.cloneType(type), hasPositionalOnly); - const paramSpec = FunctionType.getParamSpecFromArgsKwargs(type); - assert(paramSpec !== undefined); - - // Make a shallow clone of the details. - newFunction.shared = { ...newFunction.shared }; - - newFunction.shared.typeParams = newFunction.shared.typeParams.filter((t) => !isTypeSame(t, paramSpec)); - - const prevParams = Array.from(newFunction.shared.parameters); - - newFunction.shared.parameters = [ - ...prevParams, - ...paramSpecValue.shared.parameters.map((param, index) => { - return FunctionParam.create( - param.category, - FunctionType.getParamType(paramSpecValue, index), - (param.flags & FunctionParamFlags.NameSynthesized) | FunctionParamFlags.TypeDeclared, - param.name, - FunctionType.getParamDefaultType(paramSpecValue, index), - param.defaultExpr - ); - }), - ]; - - if (newFunction.shared.docString === undefined) { - newFunction.shared.docString = paramSpecValue.shared.docString; - } - - if (newFunction.shared.deprecatedMessage === undefined) { - newFunction.shared.deprecatedMessage = paramSpecValue.shared.deprecatedMessage; - } - - const origFlagsMask = FunctionTypeFlags.Overloaded | FunctionTypeFlags.ParamSpecValue; - newFunction.shared.flags = type.shared.flags & origFlagsMask; - - const methodFlagsMask = - FunctionTypeFlags.ClassMethod | FunctionTypeFlags.StaticMethod | FunctionTypeFlags.ConstructorMethod; - - // If the original function was a method, use its method type. Otherwise - // use the method type of the param spec. - if (type.shared.methodClass) { - newFunction.shared.flags |= type.shared.flags & methodFlagsMask; - } else { - newFunction.shared.flags |= paramSpecValue.shared.flags & methodFlagsMask; - } - - // Use the "..." flag from the param spec. - newFunction.shared.flags |= paramSpecValue.shared.flags & FunctionTypeFlags.GradualCallableForm; - - // Mark the function as synthesized since there is no user-defined declaration for it. - newFunction.shared.flags |= FunctionTypeFlags.SynthesizedMethod; - if (newFunction.shared.declaration) { - newFunction.shared.declaration = undefined; - } - - // Update the specialized parameter types as well. - const specializedTypes = newFunction.priv.specializedTypes; - if (specializedTypes) { - paramSpecValue.shared.parameters.forEach((_, index) => { - specializedTypes.parameterTypes.push(FunctionType.getParamType(paramSpecValue, index)); - - if (specializedTypes.parameterDefaultTypes) { - specializedTypes.parameterDefaultTypes?.push( - FunctionType.getParamDefaultType(paramSpecValue, index) - ); - } - }); - } - - newFunction.priv.constructorTypeVarScopeId = paramSpecValue.priv.constructorTypeVarScopeId; - - if (!newFunction.shared.methodClass && paramSpecValue.shared.methodClass) { - newFunction.shared.methodClass = paramSpecValue.shared.methodClass; - } - - return newFunction; - } - - export function cloneWithNewFlags(type: FunctionType, flags: FunctionTypeFlags): FunctionType { - const newFunction = TypeBase.cloneType(type); - - // Make a shallow clone of the details. - newFunction.shared = { ...type.shared }; - newFunction.shared.flags = flags; - - return newFunction; - } - - export function cloneWithNewTypeVarScopeId( - type: FunctionType, - newScopeId: TypeVarScopeId | undefined, - newConstructorScopeId: TypeVarScopeId | undefined, - typeParams: TypeVarType[] - ): FunctionType { - const newFunction = TypeBase.cloneType(type); - - // Make a shallow clone of the details. - newFunction.shared = { ...type.shared }; - newFunction.shared.typeVarScopeId = newScopeId; - newFunction.priv.constructorTypeVarScopeId = newConstructorScopeId; - newFunction.shared.typeParams = typeParams; - - return newFunction; - } - - export function cloneWithDocString(type: FunctionType, docString?: string): FunctionType { - const newFunction = TypeBase.cloneType(type); - - // Make a shallow clone of the details. - newFunction.shared = { ...type.shared }; - - newFunction.shared.docString = docString; - - return newFunction; - } - - export function cloneWithDeprecatedMessage(type: FunctionType, deprecatedMessage?: string): FunctionType { - const newFunction = TypeBase.cloneType(type); - - // Make a shallow clone of the details. - newFunction.shared = { ...type.shared }; - - newFunction.shared.deprecatedMessage = deprecatedMessage; - - return newFunction; - } - - // If the function ends with "*args: P.args, **kwargs: P.kwargs", this function - // returns a new function that is a clone of the input function with the - // *args and **kwargs parameters removed. If stripPositionOnlySeparator is true, - // a trailing positional-only separator will be removed. - export function cloneRemoveParamSpecArgsKwargs( - type: FunctionType, - stripPositionOnlySeparator = false - ): FunctionType { - const paramCount = type.shared.parameters.length; - if (paramCount < 2) { - return type; - } - - const argsParam = type.shared.parameters[paramCount - 2]; - const kwargsParam = type.shared.parameters[paramCount - 1]; - - if (argsParam.category !== ParamCategory.ArgsList || kwargsParam.category !== ParamCategory.KwargsDict) { - return type; - } - - const argsType = FunctionType.getParamType(type, paramCount - 2); - const kwargsType = FunctionType.getParamType(type, paramCount - 1); - if (!isParamSpec(argsType) || !isParamSpec(kwargsType) || !isTypeSame(argsType, kwargsType)) { - return type; - } - - const newFunction = TypeBase.cloneType(type); - - // Make a shallow clone of the details. - newFunction.shared = { ...type.shared }; - const details = newFunction.shared; - - let paramsToDrop = 2; - - // If the last remaining parameter is a position-only separator, remove it as well. - // Always remove it if it's the only remaining parameter. - if (paramCount >= 3 && isPositionOnlySeparator(details.parameters[paramCount - 3])) { - if (paramCount === 3 || stripPositionOnlySeparator) { - paramsToDrop = 3; - } - } - - // Remove the last parameters, which are the *args and **kwargs. - details.parameters = details.parameters.slice(0, details.parameters.length - paramsToDrop); - - if (type.priv.specializedTypes) { - newFunction.priv.specializedTypes = { ...type.priv.specializedTypes }; - newFunction.priv.specializedTypes.parameterTypes = newFunction.priv.specializedTypes.parameterTypes.slice( - 0, - newFunction.priv.specializedTypes.parameterTypes.length - paramsToDrop - ); - if (newFunction.priv.specializedTypes.parameterDefaultTypes) { - newFunction.priv.specializedTypes.parameterDefaultTypes = - newFunction.priv.specializedTypes.parameterDefaultTypes.slice( - 0, - newFunction.priv.specializedTypes.parameterDefaultTypes.length - paramsToDrop - ); - } - } - - if (type.shared.inferredReturnType) { - newFunction.shared.inferredReturnType = type.shared.inferredReturnType; - } - - return newFunction; - } - - // If the function ends with "*args: P.args, **kwargs: P.kwargs", this function - // returns P. Otherwise, it returns undefined. - export function getParamSpecFromArgsKwargs(type: FunctionType): ParamSpecType | undefined { - const params = type.shared.parameters; - if (params.length < 2) { - return undefined; - } - - const secondLastParam = params[params.length - 2]; - const secondLastParamType = FunctionType.getParamType(type, params.length - 2); - const lastParam = params[params.length - 1]; - const lastParamType = FunctionType.getParamType(type, params.length - 1); - - if ( - secondLastParam.category === ParamCategory.ArgsList && - isParamSpec(secondLastParamType) && - secondLastParamType.priv.paramSpecAccess === 'args' && - lastParam.category === ParamCategory.KwargsDict && - isParamSpec(lastParamType) && - lastParamType.priv.paramSpecAccess === 'kwargs' - ) { - return TypeVarType.cloneForParamSpecAccess(secondLastParamType, /* access */ undefined); - } - - return undefined; - } - - export function addParamSpecVariadics(type: FunctionType, paramSpec: ParamSpecType) { - FunctionType.addParam( - type, - FunctionParam.create( - ParamCategory.ArgsList, - TypeVarType.cloneForParamSpecAccess(paramSpec, 'args'), - FunctionParamFlags.TypeDeclared, - 'args' - ) - ); - - FunctionType.addParam( - type, - FunctionParam.create( - ParamCategory.KwargsDict, - TypeVarType.cloneForParamSpecAccess(paramSpec, 'kwargs'), - FunctionParamFlags.TypeDeclared, - 'kwargs' - ) - ); - } - - export function addDefaultParams(type: FunctionType, useUnknown = false) { - getDefaultParams(useUnknown).forEach((param) => { - FunctionType.addParam(type, param); - }); - } - - export function getDefaultParams(useUnknown = false): FunctionParam[] { - return [ - FunctionParam.create( - ParamCategory.ArgsList, - useUnknown ? UnknownType.create() : AnyType.create(), - useUnknown ? FunctionParamFlags.None : FunctionParamFlags.TypeDeclared, - 'args' - ), - FunctionParam.create( - ParamCategory.KwargsDict, - useUnknown ? UnknownType.create() : AnyType.create(), - useUnknown ? FunctionParamFlags.None : FunctionParamFlags.TypeDeclared, - 'kwargs' - ), - ]; - } - - // Indicates whether the input signature consists of (*args: Any, **kwargs: Any). - export function hasDefaultParams(functionType: FunctionType): boolean { - let sawArgs = false; - let sawKwargs = false; - - for (let i = 0; i < functionType.shared.parameters.length; i++) { - const param = functionType.shared.parameters[i]; - - // Ignore nameless separator parameters. - if (!param.name) { - continue; - } - - if (param.category === ParamCategory.Simple) { - return false; - } else if (param.category === ParamCategory.ArgsList) { - sawArgs = true; - } else if (param.category === ParamCategory.KwargsDict) { - sawKwargs = true; - } - - if (!isAnyOrUnknown(FunctionType.getParamType(functionType, i))) { - return false; - } - } - - return sawArgs && sawKwargs; - } - - export function isInstanceMethod(type: FunctionType): boolean { - return ( - (type.shared.flags & - (FunctionTypeFlags.ConstructorMethod | - FunctionTypeFlags.StaticMethod | - FunctionTypeFlags.ClassMethod)) === - 0 - ); - } - - export function isConstructorMethod(type: FunctionType): boolean { - return (type.shared.flags & FunctionTypeFlags.ConstructorMethod) !== 0; - } - - export function isStaticMethod(type: FunctionType): boolean { - return (type.shared.flags & FunctionTypeFlags.StaticMethod) !== 0; - } - - export function isClassMethod(type: FunctionType): boolean { - return (type.shared.flags & FunctionTypeFlags.ClassMethod) !== 0; - } - - export function isAbstractMethod(type: FunctionType): boolean { - return (type.shared.flags & FunctionTypeFlags.AbstractMethod) !== 0; - } - - export function isGenerator(type: FunctionType): boolean { - return (type.shared.flags & FunctionTypeFlags.Generator) !== 0; - } - - export function isSynthesizedMethod(type: FunctionType): boolean { - return (type.shared.flags & FunctionTypeFlags.SynthesizedMethod) !== 0; - } - - export function isTypeCheckOnly(type: FunctionType): boolean { - return (type.shared.flags & FunctionTypeFlags.TypeCheckOnly) !== 0; - } - - export function isOverloaded(type: FunctionType): boolean { - return (type.shared.flags & FunctionTypeFlags.Overloaded) !== 0; - } - - export function isDefaultParamCheckDisabled(type: FunctionType) { - return (type.shared.flags & FunctionTypeFlags.DisableDefaultChecks) !== 0; - } - - export function isAsync(type: FunctionType) { - return (type.shared.flags & FunctionTypeFlags.Async) !== 0; - } - - export function isStubDefinition(type: FunctionType) { - return (type.shared.flags & FunctionTypeFlags.StubDefinition) !== 0; - } - - export function isPyTypedDefinition(type: FunctionType) { - return (type.shared.flags & FunctionTypeFlags.PyTypedDefinition) !== 0; - } - - export function isFinal(type: FunctionType) { - return (type.shared.flags & FunctionTypeFlags.Final) !== 0; - } - - export function hasUnannotatedParams(type: FunctionType) { - return (type.shared.flags & FunctionTypeFlags.UnannotatedParams) !== 0; - } - - export function isGradualCallableForm(type: FunctionType) { - return (type.shared.flags & FunctionTypeFlags.GradualCallableForm) !== 0; - } - - export function isParamSpecValue(type: FunctionType) { - return (type.shared.flags & FunctionTypeFlags.ParamSpecValue) !== 0; - } - - export function isPartiallyEvaluated(type: FunctionType) { - return !!(type.shared.flags & FunctionTypeFlags.PartiallyEvaluated); - } - - export function isOverridden(type: FunctionType) { - return !!(type.shared.flags & FunctionTypeFlags.Overridden); - } - - export function isBuiltIn(type: FunctionType, name?: string | string[]) { - if (!(type.shared.flags & FunctionTypeFlags.BuiltIn)) { - return false; - } - - if (name !== undefined) { - const functionArray = Array.isArray(name) ? name : [name]; - return functionArray.some((name) => name === type.shared.name || name === type.shared.fullName); - } - - return true; - } - - export function getDeclaredParamType(type: FunctionType, index: number): Type { - return type.shared.parameters[index]._type; - } - - export function getParamType(type: FunctionType, index: number): Type { - assert(index < type.shared.parameters.length, 'Parameter types array overflow'); - - if (type.priv.specializedTypes && index < type.priv.specializedTypes.parameterTypes.length) { - return type.priv.specializedTypes.parameterTypes[index]; - } - - return type.shared.parameters[index]._type; - } - - export function getParamDefaultType(type: FunctionType, index: number): Type | undefined { - assert(index < type.shared.parameters.length, 'Parameter types array overflow'); - - if ( - type.priv.specializedTypes?.parameterDefaultTypes && - index < type.priv.specializedTypes.parameterDefaultTypes.length - ) { - const defaultArgType = type.priv.specializedTypes.parameterDefaultTypes[index]; - if (defaultArgType) { - return defaultArgType; - } - } - - return type.shared.parameters[index]._defaultType; - } - - export function addParam(type: FunctionType, param: FunctionParam) { - type.shared.parameters.push(param); - - if (type.priv.specializedTypes) { - type.priv.specializedTypes.parameterTypes.push(param._type); - } - } - - export function addPositionOnlyParamSeparator(type: FunctionType) { - addParam(type, FunctionParam.create(ParamCategory.Simple, AnyType.create())); - } - - export function addKeywordOnlyParamSeparator(type: FunctionType) { - addParam(type, FunctionParam.create(ParamCategory.ArgsList, AnyType.create())); - } - - export function getEffectiveReturnType(type: FunctionType, includeInferred = true): Type | undefined { - if (type.priv.specializedTypes?.returnType) { - return type.priv.specializedTypes.returnType; - } - - if (type.shared.declaredReturnType) { - return type.shared.declaredReturnType; - } - - if (includeInferred) { - return type.shared.inferredReturnType?.type; - } - - return undefined; - } -} - -export interface OverloadedDetailsPriv { - // eslint-disable-next-line @typescript-eslint/naming-convention - _overloads: FunctionType[]; - - // eslint-disable-next-line @typescript-eslint/naming-convention - _implementation: Type | undefined; -} - -export interface OverloadedType extends TypeBase { - priv: OverloadedDetailsPriv; -} - -export namespace OverloadedType { - export function create(overloads: FunctionType[], implementation?: Type): OverloadedType { - const newType: OverloadedType = { - category: TypeCategory.Overloaded, - flags: TypeFlags.Instance, - props: undefined, - cached: undefined, - shared: undefined, - priv: { - _overloads: [], - _implementation: implementation, - }, - }; - - overloads.forEach((overload) => { - OverloadedType.addOverload(newType, overload); - }); - - if (implementation && isFunction(implementation)) { - implementation.priv.overloaded = newType; - } - - return newType; - } - - // Adds a new overload or an implementation. - export function addOverload(type: OverloadedType, functionType: FunctionType) { - functionType.priv.overloaded = type; - type.priv._overloads.push(functionType); - } - - export function getOverloads(type: OverloadedType): FunctionType[] { - return type.priv._overloads; - } - - export function getImplementation(type: OverloadedType): Type | undefined { - return type.priv._implementation; - } -} - -export interface NeverDetailsPriv { - isNoReturn: boolean; -} - -export interface NeverType extends TypeBase { - priv: NeverDetailsPriv; -} - -export namespace NeverType { - const _neverInstance: NeverType = { - category: TypeCategory.Never, - flags: TypeFlags.Instance | TypeFlags.Instantiable, - props: undefined, - cached: undefined, - shared: undefined, - priv: { isNoReturn: false }, - }; - - const _noReturnInstance: NeverType = { - category: TypeCategory.Never, - flags: TypeFlags.Instance | TypeFlags.Instantiable, - props: undefined, - cached: undefined, - shared: undefined, - priv: { isNoReturn: true }, - }; - - export function createNever() { - return _neverInstance; - } - - export function createNoReturn() { - return _noReturnInstance; - } - - export function convertToInstance(type: NeverType): NeverType { - // Remove the specialForm or typeForm if present. Otherwise return the existing type. - if (!type.props?.specialForm && !type.props?.typeForm) { - return type; - } - - return type.priv.isNoReturn ? NeverType.createNoReturn() : NeverType.createNever(); - } -} - -export interface AnyDetailsPriv { - isEllipsis: boolean; -} - -export interface AnyType extends TypeBase { - priv: AnyDetailsPriv; -} - -export namespace AnyType { - const _anyInstanceSpecialForm: AnyType = { - category: TypeCategory.Any, - flags: TypeFlags.Instance | TypeFlags.Instantiable, - props: undefined, - cached: undefined, - shared: undefined, - priv: { isEllipsis: false }, - }; - - const _anyInstance: AnyType = { - category: TypeCategory.Any, - flags: TypeFlags.Instance | TypeFlags.Instantiable, - props: undefined, - cached: undefined, - shared: undefined, - priv: { isEllipsis: false }, - }; - - const _ellipsisInstance: AnyType = { - category: TypeCategory.Any, - flags: TypeFlags.Instance | TypeFlags.Instantiable, - props: undefined, - cached: undefined, - shared: undefined, - priv: { isEllipsis: true }, - }; - - export function create(isEllipsis = false) { - return isEllipsis ? _ellipsisInstance : _anyInstance; - } - - export function createSpecialForm() { - return _anyInstanceSpecialForm; - } -} - -export namespace AnyType { - export function convertToInstance(type: AnyType): AnyType { - // Remove the "special form" if present. Otherwise return the existing type. - return type.props?.specialForm ? AnyType.create() : type; - } -} - -// References a single condition associated with a constrained TypeVar. -export interface TypeCondition { - typeVar: TypeVarType; - constraintIndex: number; -} - -export namespace TypeCondition { - export function combine( - conditions1: TypeCondition[] | undefined, - conditions2: TypeCondition[] | undefined - ): TypeCondition[] | undefined { - if (!conditions1) { - return conditions2; - } - - if (!conditions2) { - return conditions1; - } - - // Deduplicate the lists. - const combined = Array.from(conditions1); - conditions2.forEach((c1) => { - if (!combined.some((c2) => _compare(c1, c2) === 0)) { - combined.push(c1); - } - }); - - // Always keep the conditions sorted for easier comparison. - return combined.sort(_compare); - } - - function _compare(c1: TypeCondition, c2: TypeCondition) { - if (c1.typeVar.shared.name < c2.typeVar.shared.name) { - return -1; - } else if (c1.typeVar.shared.name > c2.typeVar.shared.name) { - return 1; - } - if (c1.constraintIndex < c2.constraintIndex) { - return -1; - } else if (c1.constraintIndex > c2.constraintIndex) { - return 1; - } - return 0; - } - - export function isSame( - conditions1: TypeCondition[] | undefined, - conditions2: TypeCondition[] | undefined - ): boolean { - if (!conditions1) { - return !conditions2; - } - - if (!conditions2 || conditions1.length !== conditions2.length) { - return false; - } - - return ( - conditions1.find( - (c1, index) => - c1.typeVar.priv.nameWithScope !== conditions2[index].typeVar.priv.nameWithScope || - c1.constraintIndex !== conditions2[index].constraintIndex - ) === undefined - ); - } - - // Determines if the two conditions can be used at the same time. If - // one constraint list contains a constraint for a type variable, and the - // same constraint is not in the other constraint list, the two are considered - // incompatible. - export function isCompatible( - conditions1: TypeCondition[] | undefined, - conditions2: TypeCondition[] | undefined - ): boolean { - if (!conditions1 || !conditions2) { - return true; - } - - for (const c1 of conditions1) { - let foundTypeVarMatch = false; - const exactMatch = conditions2.find((c2) => { - if (c1.typeVar.priv.nameWithScope === c2.typeVar.priv.nameWithScope) { - foundTypeVarMatch = true; - return c1.constraintIndex === c2.constraintIndex; - } - return false; - }); - - if (foundTypeVarMatch && !exactMatch) { - return false; - } - } - - return true; - } -} - -export interface LiteralTypes { - literalStrMap: Map | undefined; - literalIntMap: Map | undefined; - literalEnumMap: Map | undefined; -} - -export interface UnionDetailsPriv { - subtypes: UnionableType[]; - literalInstances: LiteralTypes; - literalClasses: LiteralTypes; - typeAliasSources: Set | undefined; - includesRecursiveTypeAlias: boolean; -} - -export interface UnionType extends TypeBase { - priv: UnionDetailsPriv; -} - -export namespace UnionType { - export function create() { - const newUnionType: UnionType = { - category: TypeCategory.Union, - flags: TypeFlags.Instance | TypeFlags.Instantiable, - props: undefined, - cached: undefined, - shared: undefined, - priv: { - subtypes: [], - literalInstances: { - literalStrMap: undefined, - literalIntMap: undefined, - literalEnumMap: undefined, - }, - literalClasses: { - literalStrMap: undefined, - literalIntMap: undefined, - literalEnumMap: undefined, - }, - typeAliasSources: undefined, - includesRecursiveTypeAlias: false, - }, - }; - - return newUnionType; - } - - export function addType(unionType: UnionType, newType: UnionableType) { - // If we're adding a string, integer or enum literal, add it to the - // corresponding literal map to speed up some operations. It's not - // uncommon for unions to contain hundreds of literals. - if (isClass(newType) && newType.priv.literalValue !== undefined && !newType.props?.condition) { - const literalMaps = isClassInstance(newType) - ? unionType.priv.literalInstances - : unionType.priv.literalClasses; - - if (ClassType.isBuiltIn(newType, 'str')) { - if (literalMaps.literalStrMap === undefined) { - literalMaps.literalStrMap = new Map(); - } - literalMaps.literalStrMap.set(newType.priv.literalValue as string, newType); - } else if (ClassType.isBuiltIn(newType, 'int')) { - if (literalMaps.literalIntMap === undefined) { - literalMaps.literalIntMap = new Map(); - } - literalMaps.literalIntMap.set(newType.priv.literalValue as number | bigint, newType); - } else if (ClassType.isEnumClass(newType)) { - if (literalMaps.literalEnumMap === undefined) { - literalMaps.literalEnumMap = new Map(); - } - const enumLiteral = newType.priv.literalValue as EnumLiteral; - literalMaps.literalEnumMap.set(enumLiteral.getName(), newType); - } - } - - unionType.flags &= newType.flags; - unionType.priv.subtypes.push(newType); - - if (isTypeVar(newType) && newType.shared.recursiveAlias?.name) { - // Note that at least one recursive type alias was included in - // this union. We'll need to expand it before the union is used. - unionType.priv.includesRecursiveTypeAlias = true; - } - } - - // Determines whether the union contains a specified subtype. If exclusionSet is passed, - // the method skips any subtype indexes that are in the set and adds a found index to - // the exclusion set. This speeds up union type comparisons. - export function containsType( - unionType: UnionType, - subtype: Type, - options: TypeSameOptions = {}, - exclusionSet?: Set, - recursionCount = 0 - ): boolean { - // Handle string literals as a special case because unions can sometimes - // contain hundreds of string literal types. - if (isClass(subtype) && subtype.props?.condition === undefined && subtype.priv.literalValue !== undefined) { - const literalMaps = isClassInstance(subtype) - ? unionType.priv.literalInstances - : unionType.priv.literalClasses; - - if (ClassType.isBuiltIn(subtype, 'str') && literalMaps.literalStrMap !== undefined) { - return literalMaps.literalStrMap.has(subtype.priv.literalValue as string); - } else if (ClassType.isBuiltIn(subtype, 'int') && literalMaps.literalIntMap !== undefined) { - return literalMaps.literalIntMap.has(subtype.priv.literalValue as number | bigint); - } else if (ClassType.isEnumClass(subtype) && literalMaps.literalEnumMap !== undefined) { - const enumLiteral = subtype.priv.literalValue as EnumLiteral; - return literalMaps.literalEnumMap.has(enumLiteral.getName()); - } - } - - const foundIndex = unionType.priv.subtypes.findIndex((t, i) => { - if (exclusionSet?.has(i)) { - return false; - } - - return isTypeSame(t, subtype, options, recursionCount); - }); - - if (foundIndex < 0) { - return false; - } - - exclusionSet?.add(foundIndex); - return true; - } - - export function addTypeAliasSource(unionType: UnionType, typeAliasSource: Type) { - if (typeAliasSource.category === TypeCategory.Union) { - const sourcesToAdd = typeAliasSource.props?.typeAliasInfo - ? [typeAliasSource] - : typeAliasSource.priv.typeAliasSources; - - if (sourcesToAdd) { - if (!unionType.priv.typeAliasSources) { - unionType.priv.typeAliasSources = new Set(); - } - - sourcesToAdd.forEach((source) => { - unionType.priv.typeAliasSources!.add(source); - }); - } - } - } -} - -export const enum Variance { - Auto, - Unknown, - Invariant, - Covariant, - Contravariant, -} - -export interface RecursiveAliasInfo { - // Used for recursive type aliases. - name: string; - scopeId: TypeVarScopeId; - isPep695Syntax: boolean; - - // Type parameters for a recursive type alias. - typeParams: TypeVarType[] | undefined; -} - -export enum TypeVarKind { - TypeVar, - TypeVarTuple, - ParamSpec, -} - -export interface TypeVarDetailsShared { - kind: TypeVarKind; - name: string; - constraints: Type[]; - boundType: Type | undefined; - isDefaultExplicit: boolean; - defaultType: Type; - - declaredVariance: Variance; - - // Internally created (e.g. for pseudo-generic classes) - isSynthesized: boolean; - isSynthesizedSelf: boolean; - synthesizedIndex: number | undefined; - isExemptFromBoundCheck: boolean; - - // Does this type variable originate from PEP 695 type parameter syntax? - isTypeParamSyntax: boolean; - - // Information about recursive type aliases. - recursiveAlias: TypeAliasSharedInfo | undefined; -} - -export type ParamSpecAccess = 'args' | 'kwargs'; - -export const enum TypeVarScopeType { - Class, - Function, - TypeAlias, -} - -export interface TypeVarDetailsPriv { - // An ID that uniquely identifies the scope to which this TypeVar is bound - scopeId?: TypeVarScopeId | undefined; - - // A human-readable name of the function, class, or type alias that - // provides the scope to which this type variable is bound. Unlike the - // scopeId, this might not be unique, so it should be used only for error - // messages. - scopeName?: string | undefined; - - // If the TypeVar is bound to a scope, this is the scope type - scopeType?: TypeVarScopeType; - - // String formatted as . - nameWithScope?: string | undefined; - - // May be different from declaredVariance if declared as Auto - computedVariance?: Variance; - - // When a TypeVar appears within an expected type during bidirectional - // type inference, it needs to be solved along with the in-scope TypeVars. - // This is done by cloning the TypeVar and making it a "unification" - // variable. - isUnificationVar?: boolean; - - // If the TypeVar is bound form of a TypeVar, this refers to - // the corresponding free TypeVar. - freeTypeVar?: TypeVarType | undefined; - - // Is this TypeVar or TypeVarTuple unpacked (i.e. Unpack or * operator applied)? - isUnpacked?: boolean | undefined; -} - -export interface TypeVarType extends TypeBase { - shared: TypeVarDetailsShared; - priv: TypeVarDetailsPriv; -} - -export interface ParamSpecDetailsPriv extends TypeVarDetailsPriv { - // Represents access to "args" or "kwargs" of a ParamSpec - paramSpecAccess?: ParamSpecAccess; - - freeTypeVar?: ParamSpecType | undefined; -} - -export interface ParamSpecType extends TypeVarType { - shared: TypeVarDetailsShared & { kind: TypeVarKind.ParamSpec }; - priv: ParamSpecDetailsPriv; -} - -export namespace ParamSpecType { - // Returns the "Unknown" equivalent for a ParamSpec. - export function getUnknown(): FunctionType { - const newFunction = FunctionType.createInstance( - '', - '', - '', - FunctionTypeFlags.ParamSpecValue | FunctionTypeFlags.GradualCallableForm - ); - FunctionType.addDefaultParams(newFunction); - return newFunction; - } -} - -export interface TypeVarTupleDetailsPriv extends TypeVarDetailsPriv { - // Is this TypeVarTuple included in a Union[]? This allows us to - // differentiate between Unpack[Vs] and Union[Unpack[Vs]]. - isInUnion?: boolean | undefined; - - freeTypeVar?: TypeVarTupleType | undefined; -} - -export interface TypeVarTupleType extends TypeVarType { - shared: TypeVarDetailsShared & { kind: TypeVarKind.TypeVarTuple }; - priv: TypeVarTupleDetailsPriv; -} - -export namespace TypeVarType { - export function createInstance(name: string, kind: TypeVarKind = TypeVarKind.TypeVar) { - return create(name, kind, TypeFlags.Instance); - } - - export function createInstantiable(name: string, kind: TypeVarKind = TypeVarKind.TypeVar) { - return create(name, kind, TypeFlags.Instantiable); - } - - export function cloneAsInstance(type: TypeVarType): TypeVarType { - assert(TypeBase.isInstantiable(type)); - - if (type.cached?.typeBaseInstanceType) { - return type.cached.typeBaseInstanceType as TypeVarType; - } - - const newInstance = TypeBase.cloneTypeAsInstance(type, /* cache */ true); - if (newInstance.props?.specialForm) { - TypeBase.setSpecialForm(newInstance, undefined); - } - - if (newInstance.priv.freeTypeVar) { - newInstance.priv.freeTypeVar = TypeVarType.cloneAsInstance(newInstance.priv.freeTypeVar); - } - - return newInstance; - } - - export function cloneAsInstantiable(type: TypeVarType): TypeVarType { - if (type.cached?.typeBaseInstantiableType) { - return type.cached.typeBaseInstantiableType as TypeVarType; - } - - const newInstance = TypeBase.cloneTypeAsInstantiable(type, /* cache */ true); - - if (newInstance.priv.freeTypeVar) { - newInstance.priv.freeTypeVar = TypeVarType.cloneAsInstantiable(newInstance.priv.freeTypeVar); - } - - return newInstance; - } - - export function cloneForNewName(type: TypeVarType, name: string): TypeVarType { - const newInstance = TypeBase.cloneType(type); - newInstance.shared = { ...type.shared }; - newInstance.shared.name = name; - - if (newInstance.priv.scopeId) { - newInstance.priv.nameWithScope = makeNameWithScope( - name, - newInstance.priv.scopeId, - newInstance.priv.scopeName ?? '' - ); - } - - return newInstance; - } - - export function cloneForScopeId( - type: TypeVarType, - scopeId: string, - scopeName: string | undefined, - scopeType: TypeVarScopeType | undefined - ): TypeVarType { - const newInstance = TypeBase.cloneType(type); - newInstance.priv.nameWithScope = makeNameWithScope(type.shared.name, scopeId, scopeName ?? ''); - newInstance.priv.scopeId = scopeId; - newInstance.priv.scopeName = scopeName; - newInstance.priv.scopeType = scopeType; - return newInstance; - } - - export function cloneForUnpacked(type: TypeVarType, isInUnion = false) { - const newInstance = TypeBase.cloneType(type); - newInstance.priv.isUnpacked = true; - - if (isTypeVarTuple(newInstance) && isInUnion) { - newInstance.priv.isInUnion = isInUnion; - } - - if (newInstance.priv.freeTypeVar) { - newInstance.priv.freeTypeVar = TypeVarType.cloneForUnpacked(newInstance.priv.freeTypeVar, isInUnion); - } - return newInstance; - } - - export function cloneForPacked(type: TypeVarType) { - const newInstance = TypeBase.cloneType(type); - newInstance.priv.isUnpacked = false; - - if (isTypeVarTuple(newInstance)) { - newInstance.priv.isInUnion = false; - } - - if (newInstance.priv.freeTypeVar) { - newInstance.priv.freeTypeVar = TypeVarType.cloneForPacked(newInstance.priv.freeTypeVar); - } - return newInstance; - } - - // Creates a "simplified" version of the TypeVar with invariance - // and no bound or constraints. ParamSpecs and TypeVarTuples are left - // unmodified. So are auto-variant type variables. - export function cloneAsInvariant(type: TypeVarType): TypeVarType { - if (isParamSpec(type) || isTypeVarTuple(type)) { - return type; - } - - if (type.shared.declaredVariance === Variance.Auto) { - return type; - } - - if (type.shared.declaredVariance === Variance.Invariant) { - if (!TypeVarType.hasBound(type) && !TypeVarType.hasConstraints(type)) { - return type; - } - } - - const newInstance = TypeBase.cloneType(type); - newInstance.shared = { ...newInstance.shared }; - newInstance.shared.declaredVariance = Variance.Invariant; - newInstance.shared.boundType = undefined; - newInstance.shared.constraints = []; - return newInstance; - } - - export function cloneForParamSpecAccess(type: ParamSpecType, access: ParamSpecAccess | undefined): ParamSpecType { - const newInstance = TypeBase.cloneType(type); - newInstance.priv.paramSpecAccess = access; - return newInstance; - } - - export function cloneAsSpecializedSelf(type: TypeVarType, specializedBoundType: Type): TypeVarType { - assert(TypeVarType.isSelf(type)); - const newInstance = TypeBase.cloneType(type); - newInstance.shared = { ...newInstance.shared }; - newInstance.shared.boundType = specializedBoundType; - return newInstance; - } - - export function cloneAsUnificationVar(type: TypeVarType, usageOffset?: number): TypeVarType { - if (TypeVarType.isUnification(type)) { - return type; - } - - // If the caller specified a usage offset, append it to the TypeVar - // internal name. This allows us to distinguish it from other uses - // of the same TypeVar. For example nested calls to a generic - // function like `foo(foo(1))`. - let newNameWithScope = type.priv.nameWithScope; - if (usageOffset) { - newNameWithScope = `${type.priv.nameWithScope}-${usageOffset}`; - } - - const newInstance = TypeBase.cloneType(type); - newInstance.priv.isUnificationVar = true; - newInstance.priv.scopeId = UnificationScopeId; - newInstance.priv.nameWithScope = newNameWithScope; - return newInstance; - } - - export function cloneWithComputedVariance(type: TypeVarType, computedVariance: Variance): TypeVarType { - const newInstance = TypeBase.cloneType(type); - newInstance.priv.computedVariance = computedVariance; - return newInstance; - } - - export function makeNameWithScope(name: string, scopeId: string, scopeName: string) { - // We include the scopeName here even though it's normally already part - // of the scopeId. There are cases where it can diverge, specifically - // in scenarios involving higher-order functions that return generic - // callable types. See adjustCallableReturnType for details. - return `${name}.${scopeId}.${scopeName}`; - } - - // When solving the TypeVars for a callable, we need to distinguish between - // the externally-visible "free" type vars and the internal "bound" type vars. - // The distinction is important for recursive calls (e.g. calling a constructor - // for a generic class within the class implementation). - export function makeBoundScopeId(scopeId: TypeVarScopeId): TypeVarScopeId; - export function makeBoundScopeId(scopeId: TypeVarScopeId | undefined): TypeVarScopeId | undefined; - export function makeBoundScopeId(scopeId: TypeVarScopeId | undefined): TypeVarScopeId | undefined { - if (!scopeId) { - return undefined; - } - - // Append an asterisk to denote a bound scope. - return `${scopeId}*`; - } - - export function cloneAsBound(type: TypeVarType): TypeVarType { - if (type.priv.scopeId === undefined || type.priv.freeTypeVar) { - return type; - } - - const clone = TypeVarType.cloneForScopeId( - type, - TypeVarType.makeBoundScopeId(type.priv.scopeId), - type.priv.scopeName, - type.priv.scopeType - ); - - clone.priv.freeTypeVar = type; - - return clone; - } - - // Indicates that the type var is a "free" or unbound type var. Free - // type variables can be solved whereas bound type vars are already bound - // to a value. - export function isBound(type: TypeVarType) { - // If the type var has an associated free type var, then it's - // considered bound. If it has no associated free var, then it's - // considered free. - return !!type.priv.freeTypeVar; - } - - export function isUnification(type: TypeVarType) { - return type.priv.isUnificationVar; - } - - function create(name: string, kind: TypeVarKind, typeFlags: TypeFlags): TypeVarType { - const newTypeVarType: TypeVarType = { - category: TypeCategory.TypeVar, - flags: typeFlags, - props: undefined, - cached: undefined, - shared: { - kind, - name, - constraints: [], - boundType: undefined, - isDefaultExplicit: false, - defaultType: UnknownType.create(), - declaredVariance: Variance.Invariant, - isSynthesized: false, - isSynthesizedSelf: false, - synthesizedIndex: undefined, - isExemptFromBoundCheck: false, - isTypeParamSyntax: false, - recursiveAlias: undefined, - }, - priv: {}, - }; - return newTypeVarType; - } - - export function addConstraint(type: TypeVarType, constraintType: Type) { - type.shared.constraints.push(constraintType); - } - - export function getNameWithScope(typeVarType: TypeVarType) { - // If there is no name with scope, fall back on the (unscoped) name. - return typeVarType.priv.nameWithScope || typeVarType.shared.name; - } - - export function getReadableName(type: TypeVarType, includeScope = true) { - if (type.priv.scopeName && includeScope) { - return `${type.shared.name}@${type.priv.scopeName}`; - } - - return type.shared.name; - } - - export function getVariance(type: TypeVarType) { - const variance = type.priv.computedVariance ?? type.shared.declaredVariance; - - // By this point, the variance should have been inferred. - assert(variance !== Variance.Auto, 'Expected variance to be inferred'); - - // If we're in the process of computing variance, it will still be - // unknown. Default to covariant in this case. - if (variance === Variance.Unknown) { - return Variance.Covariant; - } - - return variance; - } - - // Indicates whether the specified type is a recursive type alias - // placeholder that has not yet been resolved. - export function isTypeAliasPlaceholder(type: TypeVarType) { - return !!type.shared.recursiveAlias && !type.shared.boundType; - } - - export function isSelf(type: TypeVarType) { - return !!type.shared.isSynthesizedSelf; - } - - export function hasConstraints(type: TypeVarType) { - return type.shared.constraints.length > 0; - } - - export function hasBound(type: TypeVarType) { - return !!type.shared.boundType; - } -} - -export function isNever(type: Type): type is NeverType { - return type.category === TypeCategory.Never; -} - -// Viper 扩展:检测类型是否为 t.CDefine(编译时元指令类)。 -// 用于 combineTypes 中过滤联合类型里的 CDefine,实现 t.CDefine | T = T 语义。 -// 识别条件:ClassType 实例,类名为 'CDefine',定义在 t 模块中。 -export function isCDefineType(type: Type): boolean { - if (!isClass(type)) { - return false; - } - // 通过类名识别(CDefine 在 vipershed-fallback/stdlib/t.vpi 中定义) - return type.shared.name === 'CDefine' || type.shared.fullName === 't.CDefine'; -} - -// Viper 扩展:检测类型是否为 t.CArray(栈上固定数组类型)。 -// 用于 assignType 中实现 CArray 到 str/bytes 的自动降级 -// (C 字符数组 t.CArray[t.CChar, N] 可自动转换为 C 字符串 str)。 -export function isCArrayType(type: Type): boolean { - if (!isClass(type)) { - return false; - } - return type.shared.name === 'CArray' || type.shared.fullName === 't.CArray'; -} - -// Viper 扩展:检测类型是否为 t.CPtr 类(指针类型)。 -// 包括 CPtr 及其子类(CNeedPtr, CAutoPtr, CArrayPtr)。 -export function isCPtrType(type: Type): boolean { - if (!isClass(type)) { - return false; - } - const name = type.shared.name; - const fullName = type.shared.fullName; - return ( - name === 'CPtr' || fullName === 't.CPtr' || - name === 'CNeedPtr' || fullName === 't.CNeedPtr' || - name === 'CAutoPtr' || fullName === 't.CAutoPtr' || - name === 'CArrayPtr' || fullName === 't.CArrayPtr' - ); -} - -// Viper 扩展:检测类型是否为 t.CVoid(void 类型)。 -// void 在 Viper 中常作为指针的目标类型(CVoid | CPtr 表示 void*)。 -export function isCVoidType(type: Type): boolean { - if (!isClass(type)) { - return false; - } - return type.shared.name === 'CVoid' || type.shared.fullName === 't.CVoid'; -} - -// Viper 扩展:检测类型是否为 t.CConst(const 限定符类型)。 -// CConst 在联合类型中用于表示 const 修饰(如 CConst | CChar | CPtr 表示 const char*)。 -export function isCConstType(type: Type): boolean { - if (!isClass(type)) { - return false; - } - return type.shared.name === 'CConst' || type.shared.fullName === 't.CConst'; -} - -// Viper 扩展:检测类型是否为 t.CStatic(static 存储类)。 -// CStatic 在联合类型中用于表示 static 修饰(如 CStatic | CUInt64T 表示 static uint64_t)。 -// TypeSet 语义下,CStatic | T 赋值给 T 变量是合法的(CStatic 只是存储类修饰,值的类型仍是 T)。 -export function isCStaticType(type: Type): boolean { - if (!isClass(type)) { - return false; - } - return type.shared.name === 'CStatic' || type.shared.fullName === 't.CStatic'; -} - -// Viper 扩展:检测类型是否为 Viper 整数类型。 -// Viper 中所有 C 整数类型(CInt, CUnsignedLong, CSizeT 等)都是 int 的 TypeAlias, -// 在类型系统中解析为内置 int 类。此函数检测是否为内置 int/bytes 类 -// (bytes 也视为整数地址,因为 Viper 中指针地址可用 bytes 表示)。 -export function isViperIntegerType(type: Type): boolean { - if (!isClassInstance(type)) { - return false; - } - return ClassType.isBuiltIn(type, 'int'); -} - -// Viper 扩展:Viper C 整数类型名称白名单。 -// 这些类型在 vipershed-fallback/stdlib/t.vpi 中定义为 TypeAlias = int, -// 但在 Viper 语义中它们是独立的 C 整数类型,构造调用应支持指针强转语义, -// 而不是走 int.__new__ 的参数类型检查。 -const _viperCIntegerTypeNames = new Set([ - // 基本整数类型 - 'CChar', 'CShort', 'CInt', 'CLong', 'CLongLong', - 'CSignedChar', 'CUnsignedChar', 'CUnsignedShort', - 'CUnsignedInt', 'CUnsignedLong', 'CUnsignedLongLong', - // 固定宽度整数 - 'CInt8T', 'CInt16T', 'CInt32T', 'CInt64T', - 'CUInt8T', 'CUInt16T', 'CUInt32T', 'CUInt64T', - // 最小/最快宽度整数 - 'CIntLeast8T', 'CIntLeast16T', 'CIntLeast32T', 'CIntLeast64T', - 'CUIntLeast8T', 'CUIntLeast16T', 'CUIntLeast32T', 'CUIntLeast64T', - 'CIntFast8T', 'CIntFast16T', 'CIntFast32T', 'CIntFast64T', - 'CUIntFast8T', 'CUIntFast16T', 'CUIntFast32T', 'CUIntFast64T', - // 最大宽度整数 - 'CIntMaxT', 'CUIntMaxT', - // 平台相关整数 - 'CSizeT', 'CIntPtrT', 'CUIntPtrT', 'CPtrDiffT', - // 布尔 - 'CBool', - // 宽字符 - 'CWCharT', 'CChar8T', 'CChar16T', 'CChar32T', - // LLVM 简写 - 'i1', 'i8', 'i16', 'i32', 'i64', - 'u1', 'u8', 'u16', 'u32', 'u64', -]); - -// Viper 扩展:检测名称是否为 Viper C 整数类型名。 -// 用于在 getTypeOfCall 中识别 C 整数类型的构造调用(如 t.CUInt32T(addr, t.CPtr)), -// 并跳过 int.__new__ 的参数类型检查,实现 Viper 的指针强转语义。 -export function isViperCIntegerTypeName(name: string): boolean { - return _viperCIntegerTypeNames.has(name); -} - -export function isAny(type: Type): type is AnyType { - return type.category === TypeCategory.Any; -} - -export function isUnknown(type: Type): type is UnknownType { - return type.category === TypeCategory.Unknown; -} - -export function isAnyOrUnknown(type: Type): type is AnyType | UnknownType { - if (type.category === TypeCategory.Any || type.category === TypeCategory.Unknown) { - return true; - } - - if (isUnion(type)) { - return type.priv.subtypes.find((subtype) => !isAnyOrUnknown(subtype)) === undefined; - } - - return false; -} - -export function isUnbound(type: Type): type is UnboundType { - return type.category === TypeCategory.Unbound; -} - -export function isUnion(type: Type): type is UnionType { - return type.category === TypeCategory.Union; -} - -export function isPossiblyUnbound(type: Type): boolean { - if (isUnbound(type)) { - return true; - } - - if (isUnion(type)) { - return type.priv.subtypes.find((subtype) => isPossiblyUnbound(subtype)) !== undefined; - } - - return false; -} - -export function isClass(type: Type): type is ClassType { - return type.category === TypeCategory.Class; -} - -export function isInstantiableClass(type: Type): type is ClassType { - return type.category === TypeCategory.Class && TypeBase.isInstantiable(type); -} - -export function isClassInstance(type: Type): type is ClassType { - return type.category === TypeCategory.Class && TypeBase.isInstance(type); -} - -export function isModule(type: Type): type is ModuleType { - return type.category === TypeCategory.Module; -} - -export function isTypeVar(type: Type): type is TypeVarType { - return type.category === TypeCategory.TypeVar; -} - -export function isParamSpec(type: Type): type is ParamSpecType { - return type.category === TypeCategory.TypeVar && type.shared.kind === TypeVarKind.ParamSpec; -} - -export function isTypeVarTuple(type: Type): type is TypeVarTupleType { - return type.category === TypeCategory.TypeVar && type.shared.kind === TypeVarKind.TypeVarTuple; -} - -export function isUnpackedTypeVarTuple(type: Type): type is TypeVarTupleType { - return isTypeVarTuple(type) && !!type.priv.isUnpacked && !type.priv.isInUnion; -} - -export function isUnpackedTypeVar(type: Type): type is TypeVarTupleType { - return isTypeVar(type) && !isTypeVarTuple(type) && !!type.priv.isUnpacked; -} - -export function isUnpackedClass(type: Type): type is ClassType { - if (!isClass(type) || !type.priv.isUnpacked) { - return false; - } - - return true; -} - -export function isUnpacked(type: Type): boolean { - return isUnpackedTypeVarTuple(type) || isUnpackedTypeVar(type) || isUnpackedClass(type); -} - -export function isFunction(type: Type): type is FunctionType { - return type.category === TypeCategory.Function; -} - -export function isOverloaded(type: Type): type is OverloadedType { - return type.category === TypeCategory.Overloaded; -} - -export function isFunctionOrOverloaded(type: Type): type is FunctionType | OverloadedType { - return type.category === TypeCategory.Function || type.category === TypeCategory.Overloaded; -} - -export function isMethodType(type: FunctionType | OverloadedType): boolean { - let funcType: FunctionType | undefined; - - if (isFunction(type)) { - funcType = type; - } else { - if (type.priv._overloads.length === 0) { - return false; - } - funcType = type.priv._overloads[0]; - } - - // __new__ methods are never really bound at runtime. - if ( - funcType.priv.preBoundFlags !== undefined && - (funcType.priv.preBoundFlags & FunctionTypeFlags.ConstructorMethod) !== 0 - ) { - return false; - } - - // If the function type has a stripped first parameter type, it was - // bound to class or object and is therefore a MethodType rather - // a FunctionType. - return !!funcType.priv.strippedFirstParamType; -} - -export function getTypeAliasInfo(type: Type) { - if (type.props?.typeAliasInfo) { - return type.props.typeAliasInfo; - } - - if ( - isTypeVar(type) && - type.shared.recursiveAlias && - type.shared.boundType && - type.shared.boundType.props?.typeAliasInfo - ) { - return type.shared.boundType.props.typeAliasInfo; - } - - return undefined; -} - -// Determines whether two types are the same. If ignorePseudoGeneric is true, -// type arguments for "pseudo-generic" classes (non-generic classes whose init -// methods are not annotated and are therefore treated as generic) are ignored. -export function isTypeSame(type1: Type, type2: Type, options: TypeSameOptions = {}, recursionCount = 0): boolean { - if (type1 === type2) { - return true; - } - - if (type1.category !== type2.category) { - if (options.treatAnySameAsUnknown) { - if (type1.category === TypeCategory.Any && type2.category === TypeCategory.Unknown) { - return true; - } - if (type1.category === TypeCategory.Unknown && type2.category === TypeCategory.Any) { - return true; - } - } - - return false; - } - - if (!options.ignoreTypeFlags) { - if ((type1.flags & TypeFlags.TypeCompatibilityMask) !== (type2.flags & TypeFlags.TypeCompatibilityMask)) { - return false; - } - } - - if (recursionCount > maxTypeRecursionCount) { - return true; - } - recursionCount++; - - if (options.honorTypeForm) { - const typeForm1 = type1.props?.typeForm; - const typeForm2 = type2.props?.typeForm; - - if (typeForm1) { - if (!typeForm2) { - return false; - } - - if (!isTypeSame(typeForm1, typeForm2, options, recursionCount)) { - return false; - } - } else if (typeForm2) { - return false; - } - } - - switch (type1.category) { - case TypeCategory.Class: { - const classType2 = type2 as ClassType; - - // If the details are not the same it's not the same class. - if (!ClassType.isSameGenericClass(type1, classType2, recursionCount)) { - return false; - } - - if (!options.ignoreConditions && !TypeCondition.isSame(type1.props?.condition, type2.props?.condition)) { - return false; - } - - if (!options.ignorePseudoGeneric || !ClassType.isPseudoGenericClass(type1)) { - // Make sure the type args match. - if (type1.priv.tupleTypeArgs && classType2.priv.tupleTypeArgs) { - const type1TupleTypeArgs = type1.priv.tupleTypeArgs || []; - const type2TupleTypeArgs = classType2.priv.tupleTypeArgs || []; - if (type1TupleTypeArgs.length !== type2TupleTypeArgs.length) { - return false; - } - - for (let i = 0; i < type1TupleTypeArgs.length; i++) { - if ( - !isTypeSame( - type1TupleTypeArgs[i].type, - type2TupleTypeArgs[i].type, - { ...options, ignoreTypeFlags: false }, - recursionCount - ) - ) { - return false; - } - - if (type1TupleTypeArgs[i].isUnbounded !== type2TupleTypeArgs[i].isUnbounded) { - return false; - } - } - } else { - const type1TypeArgs = type1.priv.typeArgs || []; - const type2TypeArgs = classType2.priv.typeArgs || []; - const typeArgCount = Math.max(type1TypeArgs.length, type2TypeArgs.length); - - for (let i = 0; i < typeArgCount; i++) { - // Assume that missing type args are "Unknown". - const typeArg1 = i < type1TypeArgs.length ? type1TypeArgs[i] : UnknownType.create(); - const typeArg2 = i < type2TypeArgs.length ? type2TypeArgs[i] : UnknownType.create(); - - if (!isTypeSame(typeArg1, typeArg2, { ...options, ignoreTypeFlags: false }, recursionCount)) { - return false; - } - } - } - } - - if (!ClassType.isLiteralValueSame(type1, classType2)) { - return false; - } - - if (!type1.priv.isUnpacked !== !classType2.priv.isUnpacked) { - return false; - } - - if (!type1.priv.isTypedDictPartial !== !classType2.priv.isTypedDictPartial) { - return false; - } - - if (options.honorIsTypeArgExplicit) { - if (!!type1.priv.isTypeArgExplicit !== !!classType2.priv.isTypeArgExplicit) { - return false; - } - } - - if (!options.ignoreTypedDictNarrowEntries && !ClassType.isTypedDictNarrowedEntriesSame(type1, classType2)) { - return false; - } - - return true; - } - - case TypeCategory.Function: { - // Make sure the parameter counts match. - const functionType2 = type2 as FunctionType; - const params1 = type1.shared.parameters; - const params2 = functionType2.shared.parameters; - - if (params1.length !== params2.length) { - return false; - } - - // If one function is ... and the other is not, they are not the same. - if (FunctionType.isGradualCallableForm(type1) !== FunctionType.isGradualCallableForm(functionType2)) { - return false; - } - - const positionOnlyIndex1 = params1.findIndex((param) => isPositionOnlySeparator(param)); - const positionOnlyIndex2 = params2.findIndex((param) => isPositionOnlySeparator(param)); - - // Make sure the parameter details match. - for (let i = 0; i < params1.length; i++) { - const param1 = params1[i]; - const param2 = params2[i]; - - if (param1.category !== param2.category) { - return false; - } - - const isName1Relevant = positionOnlyIndex1 !== undefined && i > positionOnlyIndex1; - const isName2Relevant = positionOnlyIndex2 !== undefined && i > positionOnlyIndex2; - - if (isName1Relevant !== isName2Relevant) { - return false; - } - - if (isName1Relevant) { - if (param1.name !== param2.name) { - return false; - } - } else if (isPositionOnlySeparator(param1) && isPositionOnlySeparator(param2)) { - continue; - } else if (isKeywordOnlySeparator(param1) && isKeywordOnlySeparator(param2)) { - continue; - } - - const param1Type = FunctionType.getParamType(type1, i); - const param2Type = FunctionType.getParamType(functionType2, i); - if (!isTypeSame(param1Type, param2Type, { ...options, ignoreTypeFlags: false }, recursionCount)) { - return false; - } - } - - // Make sure the return types match. - let return1Type = type1.shared.declaredReturnType; - if (type1.priv.specializedTypes && type1.priv.specializedTypes.returnType) { - return1Type = type1.priv.specializedTypes.returnType; - } - if (!return1Type && type1.shared.inferredReturnType) { - return1Type = type1.shared.inferredReturnType?.type; - } - - let return2Type = functionType2.shared.declaredReturnType; - if (functionType2.priv.specializedTypes && functionType2.priv.specializedTypes.returnType) { - return2Type = functionType2.priv.specializedTypes.returnType; - } - if (!return2Type && functionType2.shared.inferredReturnType) { - return2Type = functionType2.shared.inferredReturnType?.type; - } - - if (return1Type || return2Type) { - if ( - !return1Type || - !return2Type || - !isTypeSame(return1Type, return2Type, { ...options, ignoreTypeFlags: false }, recursionCount) - ) { - return false; - } - } - - return true; - } - - case TypeCategory.Overloaded: { - // Make sure the overload counts match. - const functionType2 = type2 as OverloadedType; - if (type1.priv._overloads.length !== functionType2.priv._overloads.length) { - return false; - } - - // We assume here that overloaded functions always appear - // in the same order from one analysis pass to another. - for (let i = 0; i < type1.priv._overloads.length; i++) { - if (!isTypeSame(type1.priv._overloads[i], functionType2.priv._overloads[i], options, recursionCount)) { - return false; - } - } - - return true; - } - - case TypeCategory.Union: { - const unionType2 = type2 as UnionType; - const subtypes1 = type1.priv.subtypes; - const subtypes2 = unionType2.priv.subtypes; - - if (subtypes1.length !== subtypes2.length) { - return false; - } - - // The types do not have a particular order, so we need to - // do the comparison in an order-independent manner. - const exclusionSet = new Set(); - return ( - findSubtype( - type1, - (subtype) => !UnionType.containsType(unionType2, subtype, options, exclusionSet, recursionCount) - ) === undefined - ); - } - - case TypeCategory.TypeVar: { - const type2TypeVar = type2 as TypeVarType; - - if (type1.priv.scopeId !== type2TypeVar.priv.scopeId) { - return false; - } - - if (type1.priv.nameWithScope !== type2TypeVar.priv.nameWithScope) { - return false; - } - - // Handle the case where this is a generic recursive type alias. Make - // sure that the type argument types match. - if (type1.shared.recursiveAlias && type2TypeVar.shared.recursiveAlias) { - const type1TypeArgs = type1?.props?.typeAliasInfo?.typeArgs || []; - const type2TypeArgs = type2?.props?.typeAliasInfo?.typeArgs || []; - const typeArgCount = Math.max(type1TypeArgs.length, type2TypeArgs.length); - - for (let i = 0; i < typeArgCount; i++) { - // Assume that missing type args are "Any". - const typeArg1 = i < type1TypeArgs.length ? type1TypeArgs[i] : AnyType.create(); - const typeArg2 = i < type2TypeArgs.length ? type2TypeArgs[i] : AnyType.create(); - - if (!isTypeSame(typeArg1, typeArg2, { ...options, ignoreTypeFlags: false }, recursionCount)) { - return false; - } - } - } - - if (isTypeVarTuple(type1) && isTypeVarTuple(type2TypeVar)) { - if (!type1.priv.isInUnion !== !type2TypeVar.priv.isInUnion) { - return false; - } - } - - if (type1.shared === type2TypeVar.shared) { - return true; - } - - if (isParamSpec(type1) !== isParamSpec(type2TypeVar)) { - return false; - } - - if (isTypeVarTuple(type1) !== isTypeVarTuple(type2TypeVar)) { - return false; - } - - if ( - type1.shared.name !== type2TypeVar.shared.name || - type1.shared.isSynthesized !== type2TypeVar.shared.isSynthesized || - type1.shared.declaredVariance !== type2TypeVar.shared.declaredVariance || - type1.priv.scopeId !== type2TypeVar.priv.scopeId - ) { - return false; - } - - const boundType1 = type1.shared.boundType; - const boundType2 = type2TypeVar.shared.boundType; - if (boundType1) { - if ( - !boundType2 || - !isTypeSame(boundType1, boundType2, { ...options, ignoreTypeFlags: false }, recursionCount) - ) { - return false; - } - } else { - if (boundType2) { - return false; - } - } - - const constraints1 = type1.shared.constraints; - const constraints2 = type2TypeVar.shared.constraints; - if (constraints1.length !== constraints2.length) { - return false; - } - - for (let i = 0; i < constraints1.length; i++) { - if ( - !isTypeSame( - constraints1[i], - constraints2[i], - { ...options, ignoreTypeFlags: false }, - recursionCount - ) - ) { - return false; - } - } - - return true; - } - - case TypeCategory.Module: { - const type2Module = type2 as ModuleType; - - // Module types are the same if they share the same - // module symbol table. - if (type1.priv.fields === type2Module.priv.fields) { - return true; - } - - // If both symbol tables are empty, we can also assume - // they're equal. - if (type1.priv.fields.size === 0 && type2Module.priv.fields.size === 0) { - return true; - } - - return false; - } - - case TypeCategory.Unknown: { - const type2Unknown = type2 as UnknownType; - - return type1.priv.isIncomplete === type2Unknown.priv.isIncomplete; - } - } - - return true; -} - -// If the type is a union, remove an "unknown" type from the union, -// returning only the known types. -export function removeUnknownFromUnion(type: Type): Type { - return removeFromUnion(type, (t: Type) => isUnknown(t)); -} - -// If the type is a union, remove an "unbound" type from the union, -// returning only the known types. -export function removeUnbound(type: Type): Type { - if (isUnion(type)) { - return removeFromUnion(type, (t: Type) => isUnbound(t)); - } - - if (isUnbound(type)) { - return UnknownType.create(); - } - - return type; -} - -export function removeFromUnion(type: Type, removeFilter: (type: Type) => boolean) { - if (isUnion(type)) { - const remainingTypes = type.priv.subtypes.filter((t) => !removeFilter(t)); - if (remainingTypes.length < type.priv.subtypes.length) { - const newType = combineTypes(remainingTypes); - - if (isUnion(newType)) { - UnionType.addTypeAliasSource(newType, type); - } - - return newType; - } - } - - return type; -} - -export function findSubtype(type: Type, filter: (type: UnionableType | NeverType) => boolean) { - if (isUnion(type)) { - return type.priv.subtypes.find((subtype) => { - return filter(subtype); - }); - } - - return filter(type) ? type : undefined; -} - -export interface CombineTypesOptions { - // By default, literals are elided (removed) from a union if the non-literal - // subtype is present. Should this be skipped? - skipElideRedundantLiterals?: boolean; - - // If specified, the maximum number of subtypes that should be allowed - // in the union before it is converted to an "Any" type. - maxSubtypeCount?: number; -} - -// Combines multiple types into a single type. If the types are -// the same, only one is returned. If they differ, they -// are combined into a UnionType. NeverTypes are filtered out. -// If no types remain in the end, a NeverType is returned. -export function combineTypes(subtypes: Type[], options?: CombineTypesOptions): Type { - let neverTypes: NeverType[]; - - // Filter out any Never or NoReturn types. - [neverTypes, subtypes] = partition(subtypes, isNever); - - // Viper 扩展:过滤联合类型中的 t.CDefine。 - // 语义:t.CDefine | T = T(CDefine 是编译时元指令,在联合中按另一类型规则检查)。 - // 若所有子类型都是 CDefine,则保留原数组(后续退化为 AnyType, - // 实现"单独使用 t.CDefine 时为任意类型")。 - if (subtypes.length > 1) { - const nonCDefineTypes = subtypes.filter((t) => !isCDefineType(t)); - if (nonCDefineTypes.length > 0 && nonCDefineTypes.length < subtypes.length) { - subtypes = nonCDefineTypes; - } - } - - // Viper 扩展:将 Struct | CPtr 自动映射为 CPtr[Struct]。 - // 模拟 C 语言指针语义:结构体与指针的联合类型自动转换为指向该结构体的指针类型。 - // 例如:Scheduler | t.CPtr → CPtr[Scheduler],bootinfo.bootinfo | t.CPtr → CPtr[bootinfo.bootinfo]。 - // 识别条件:联合类型中同时存在 CPtr 子类型和"结构体"子类型(非 CPtr/CVoid/CArray/CConst/CDefine/整数/str/bytes)。 - if (subtypes.length > 1) { - let cptrSubtype: ClassType | undefined; - const structSubtypes: ClassType[] = []; - const otherSubtypes: Type[] = []; - - for (const subtype of subtypes) { - if (isClass(subtype) && isCPtrType(subtype)) { - // 仅记录第一个 CPtr 子类型(多个 CPtr 时取首个作为模板) - if (!cptrSubtype) { - cptrSubtype = subtype; - } - } else if ( - isClass(subtype) && - !isCVoidType(subtype) && - !isCArrayType(subtype) && - !isCConstType(subtype) && - !isCStaticType(subtype) && - !isCDefineType(subtype) && - !ClassType.isBuiltIn(subtype, 'int') && - !ClassType.isBuiltIn(subtype, 'str') && - !ClassType.isBuiltIn(subtype, 'bytes') && - !ClassType.isBuiltIn(subtype, 'object') - ) { - structSubtypes.push(subtype); - } else { - otherSubtypes.push(subtype); - } - } - - if (cptrSubtype && structSubtypes.length > 0) { - // 选择第一个结构体类型作为 CPtr 的类型参数 - // 保留输入子类型的形式:类型表达式上下文(type[Scheduler] | type[CPtr]) - // 应输出 instantiable 形式 type[CPtr[Scheduler]];实例上下文(Scheduler | CPtr) - // 应输出实例形式 CPtr[Scheduler]。否则 validateTypeIsInstantiable 会报 - // "Expected class but received CPtr[Scheduler]"。 - const structSubtype = structSubtypes[0]; - const isInstantiableForm = TypeBase.isInstantiable(structSubtype); - const structForSpecialize = isInstantiableForm - ? ClassType.cloneAsInstance(structSubtype) - : structSubtype; - const cptrForSpecialize = isInstantiableForm - ? ClassType.cloneAsInstance(cptrSubtype) - : cptrSubtype; - // 构造 CPtr[Struct] 类型(实例形式) - const specializedCPtrInstance = ClassType.specialize(cptrForSpecialize, [structForSpecialize]); - // 必要时转回 instantiable 形式以匹配输入上下文 - const specializedCPtr = isInstantiableForm - ? ClassType.cloneAsInstantiable(specializedCPtrInstance) - : specializedCPtrInstance; - - // 若无其他子类型,直接返回 CPtr[Struct];否则保留其他子类型组成新联合 - if (otherSubtypes.length === 0 && structSubtypes.length === 1) { - return specializedCPtr; - } - - subtypes = [specializedCPtr, ...otherSubtypes]; - } - } - - if (subtypes.length === 0) { - if (neverTypes.length > 0) { - // Prefer NoReturn over Never. This approach preserves type alias - // information if present. - return neverTypes.find((t) => t.priv.isNoReturn) ?? neverTypes[0]; - } - - // Viper 扩展:仅剩 CDefine 时返回 Any(任意类型语义) - return AnyType.create(); - } - - // Handle the common case where there is only one type. - // Also handle the common case where there are multiple copies of the same type. - let allSubtypesAreSame = true; - if (subtypes.length > 1) { - for (let index = 1; index < subtypes.length; index++) { - if (subtypes[index] !== subtypes[0]) { - allSubtypesAreSame = false; - break; - } - } - } - - if (allSubtypesAreSame) { - return subtypes[0]; - } - - // Expand all union types. - let expandedTypes: Type[] | undefined; - const typeAliasSources = new Set(); - - for (let i = 0; i < subtypes.length; i++) { - const subtype = subtypes[i]; - if (isUnion(subtype)) { - if (!expandedTypes) { - expandedTypes = subtypes.slice(0, i); - } - expandedTypes = expandedTypes.concat(subtype.priv.subtypes); - - if (subtype.props?.typeAliasInfo) { - typeAliasSources.add(subtype); - } else if (subtype.priv.typeAliasSources) { - subtype.priv.typeAliasSources.forEach((subtype) => { - typeAliasSources.add(subtype); - }); - } - } else if (expandedTypes) { - expandedTypes.push(subtype); - } - } - - expandedTypes = expandedTypes ?? subtypes; - - // Sort all of the literal and empty types to the end. - expandedTypes = expandedTypes.sort((type1, type2) => { - if (isClass(type1) && type1.priv.literalValue !== undefined) { - return 1; - } - - if (isClass(type2) && type2.priv.literalValue !== undefined) { - return -1; - } - - if (isClassInstance(type1) && type1.priv.isEmptyContainer) { - return 1; - } else if (isClassInstance(type2) && type2.priv.isEmptyContainer) { - return -1; - } - - return 0; - }); - - // If removing all NoReturn types results in no remaining types, - // convert it to an unknown. - if (expandedTypes.length === 0) { - return UnknownType.create(); - } - - const newUnionType = UnionType.create(); - if (typeAliasSources.size > 0) { - newUnionType.priv.typeAliasSources = typeAliasSources; - } - - let hitMaxSubtypeCount = false; - - expandedTypes.forEach((subtype, index) => { - if (index === 0) { - UnionType.addType(newUnionType, subtype as UnionableType); - } else { - if (options?.maxSubtypeCount === undefined || newUnionType.priv.subtypes.length < options.maxSubtypeCount) { - _addTypeIfUnique(newUnionType, subtype as UnionableType, !options?.skipElideRedundantLiterals); - } else { - hitMaxSubtypeCount = true; - } - } - }); - - if (hitMaxSubtypeCount) { - return AnyType.create(); - } - - // If only one type remains, convert it from a union to a simple type. - if (newUnionType.priv.subtypes.length === 1) { - return newUnionType.priv.subtypes[0]; - } - - return newUnionType; -} - -// Determines whether the dest type is the same as the source type with -// the possible exception that the source type has a literal value when -// the dest does not. -export function isSameWithoutLiteralValue(destType: Type, srcType: Type): boolean { - // If it's the same with literals, great. - if (isTypeSame(destType, srcType)) { - return true; - } - - if (isInstantiableClass(srcType) && srcType.priv.literalValue !== undefined) { - // Strip the literal. - srcType = ClassType.cloneWithLiteral(srcType, /* value */ undefined); - return isTypeSame(destType, srcType); - } - - if (isClassInstance(srcType) && srcType.priv.literalValue !== undefined) { - // Strip the literal. - srcType = ClassType.cloneWithLiteral(srcType, /* value */ undefined); - return isTypeSame(destType, srcType, { ignoreConditions: true }); - } - - return false; -} - -function _addTypeIfUnique(unionType: UnionType, typeToAdd: UnionableType, elideRedundantLiterals: boolean) { - // Handle the addition of a string literal in a special manner to - // avoid n^2 behavior in unions that contain hundreds of string - // literal types. Skip this for constrained types. - if (isClass(typeToAdd) && !typeToAdd.props?.condition) { - const literalMaps = isClassInstance(typeToAdd) - ? unionType.priv.literalInstances - : unionType.priv.literalClasses; - - if ( - ClassType.isBuiltIn(typeToAdd, 'str') && - typeToAdd.priv.literalValue !== undefined && - literalMaps.literalStrMap !== undefined - ) { - if (!literalMaps.literalStrMap.has(typeToAdd.priv.literalValue as string)) { - UnionType.addType(unionType, typeToAdd); - } - return; - } else if ( - ClassType.isBuiltIn(typeToAdd, 'int') && - typeToAdd.priv.literalValue !== undefined && - literalMaps.literalIntMap !== undefined - ) { - if (!literalMaps.literalIntMap.has(typeToAdd.priv.literalValue as number | bigint)) { - UnionType.addType(unionType, typeToAdd); - } - return; - } else if ( - ClassType.isEnumClass(typeToAdd) && - typeToAdd.priv.literalValue !== undefined && - literalMaps.literalEnumMap !== undefined - ) { - const enumLiteral = typeToAdd.priv.literalValue as EnumLiteral; - if (!literalMaps.literalEnumMap.has(enumLiteral.getName())) { - UnionType.addType(unionType, typeToAdd); - } - return; - } - } - - const isPseudoGeneric = isClass(typeToAdd) && ClassType.isPseudoGenericClass(typeToAdd); - - for (let i = 0; i < unionType.priv.subtypes.length; i++) { - const type = unionType.priv.subtypes[i]; - - // Does this type already exist in the types array? - if (isTypeSame(type, typeToAdd, { honorTypeForm: true })) { - return; - } - - // Handle the case where pseudo-generic classes with different - // type arguments are being combined. Rather than add multiple - // specialized types, we will replace them with a single specialized - // type that is specialized with Unknowns. This is important because - // we can hit recursive cases (where a pseudo-generic class is - // parameterized with its own class) ad infinitum. - if (isPseudoGeneric) { - if (isTypeSame(type, typeToAdd, { ignorePseudoGeneric: true, honorTypeForm: true })) { - unionType.priv.subtypes[i] = ClassType.specialize( - typeToAdd, - typeToAdd.shared.typeParams.map(() => UnknownType.create()) - ); - return; - } - } - - if (isClassInstance(type) && isClassInstance(typeToAdd)) { - // If the typeToAdd is a literal value and there's already - // a non-literal type that matches, don't add the literal value. - if (elideRedundantLiterals && isSameWithoutLiteralValue(type, typeToAdd)) { - if (type.priv.literalValue === undefined) { - return; - } - } - - // If we're adding Literal[False] or Literal[True] to its - // opposite, combine them into a non-literal 'bool' type. - if ( - ClassType.isBuiltIn(type, 'bool') && - !type.props?.condition && - ClassType.isBuiltIn(typeToAdd, 'bool') && - !typeToAdd.props?.condition - ) { - if ( - typeToAdd.priv.literalValue !== undefined && - !typeToAdd.priv.literalValue === type.priv.literalValue - ) { - unionType.priv.subtypes[i] = ClassType.cloneWithLiteral(type, /* value */ undefined); - return; - } - } - - // If the typeToAdd is a TypedDict that is the same class as the - // existing type, see if one of them is a proper subset of the other. - if (ClassType.isTypedDictClass(type) && ClassType.isSameGenericClass(type, typeToAdd)) { - // Do not proceed if the TypedDicts are generic and have different type arguments. - if (!type.priv.typeArgs && !typeToAdd.priv.typeArgs) { - if (ClassType.isTypedDictNarrower(typeToAdd, type)) { - return; - } else if (ClassType.isTypedDictNarrower(type, typeToAdd)) { - unionType.priv.subtypes[i] = typeToAdd; - return; - } - } - } - } - - // If the typeToAdd is an empty container and there's already - // non-empty container of the same type, don't add the empty container. - if (isClassInstance(typeToAdd) && typeToAdd.priv.isEmptyContainer) { - if (isClassInstance(type) && ClassType.isSameGenericClass(type, typeToAdd)) { - return; - } - } - } - - UnionType.addType(unionType, typeToAdd); -} +/* + * types.ts + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * Author: Eric Traut + * + * Representation of types used during type analysis within Python. + */ + +import { partition } from '../common/collectionUtils'; +import { assert } from '../common/debug'; +import { Uri } from '../common/uri/uri'; +import { ArgumentNode, ExpressionNode, NameNode, ParamCategory, TypeAnnotationNode } from '../parser/parseNodes'; +import { ClassDeclaration, FunctionDeclaration, SpecialBuiltInClassDeclaration } from './declaration'; +import { Symbol, SymbolTable } from './symbol'; + +export const enum TypeCategory { + // Name is not bound to a value of any type + Unbound, + + // Implicit Any type + Unknown, + + // Type can be anything + Any, + + // The bottom type, equivalent to an empty union + Never, + + // Callable type + Function, + + // Functions defined with @overload decorator + Overloaded, + + // Class definition + Class, + + // Module instance + Module, + + // Union of two or more other types + Union, + + // Type variable + TypeVar, +} + +export const enum TypeFlags { + None = 0, + + // This type refers to something that can be instantiated. + Instantiable = 1 << 0, + + // This type refers to something that has been instantiated. + Instance = 1 << 1, + + // This type is inferred within a py.typed source file and could be + // inferred differently by other type checkers. + Ambiguous = 1 << 2, + + // This mask indicates which flags should be considered significant + // when comparing two types for equivalence. + TypeCompatibilityMask = Instantiable | Instance, +} + +export type UnionableType = + | UnboundType + | UnknownType + | AnyType + | FunctionType + | OverloadedType + | ClassType + | ModuleType + | TypeVarType; + +export type Type = UnionableType | NeverType | UnionType; + +// A string that uniquely identifies a TypeVar that is bound to a scope +// (a generic class, function, or type alias). +export type TypeVarScopeId = string; +export const UnificationScopeId: TypeVarScopeId = '-'; + +// Information about an enum member that can be used within a Literal +// type annotation. +export class EnumLiteral { + constructor( + public classFullName: string, + public className: string, + public itemName: string, + public itemType: Type, + public isReprEnum: boolean + ) {} + + getName() { + return `${this.classFullName}.${this.itemName}`; + } +} + +export class SentinelLiteral { + constructor(public classFullName: string, public className: string) {} + + getName() { + return this.className; + } +} + +export type LiteralValue = number | bigint | boolean | string | EnumLiteral | SentinelLiteral; + +export type TypeSourceId = number; + +// This constant controls the maximum number of nested types (i.e. types +// used as type arguments or parameter types in other types) before we +// give up. This constant was previously set to 32, but there were certain +// pathological recursive types where this resulted in a hang. It was also +// previously lowered to 10, but this caused some legitimate failures in +// code that used numpy. Even at 16, there are some legitimate failures in +// numpy. +export const maxTypeRecursionCount = 20; + +export type InheritanceChain = (ClassType | UnknownType)[]; + +// Options used with the isTypeSame function +export interface TypeSameOptions { + ignorePseudoGeneric?: boolean; + ignoreTypeFlags?: boolean; + ignoreConditions?: boolean; + ignoreTypedDictNarrowEntries?: boolean; + honorTypeForm?: boolean; + honorIsTypeArgExplicit?: boolean; + treatAnySameAsUnknown?: boolean; +} + +export interface TypeAliasSharedInfo { + name: string; + fullName: string; + moduleName: string; + fileUri: Uri; + + typeVarScopeId: TypeVarScopeId; + + // Is the type alias a PEP 695 TypeAliasType instance? + isTypeAliasType: boolean; + + // Type parameters, if type alias is generic + typeParams: TypeVarType[] | undefined; + + // Lazily-evaluated variance of type parameters based on how + // they are used in the type alias + computedVariance: Variance[] | undefined; +} + +export interface TypeAliasInfo { + shared: TypeAliasSharedInfo; + + // Type argument, if type alias is specialized + typeArgs: Type[] | undefined; +} + +interface CachedTypeInfo { + // Type converted to instantiable and instance by convertToInstance + // and convertToInstantiable (cached) + instantiableType?: Type; + instanceType?: Type; + + // Type converted to instantiable and instance by TypeBase methods (cached) + typeBaseInstantiableType?: Type; + typeBaseInstanceType?: Type; + + // Requires specialization flag (cached) + requiresSpecialization?: boolean; +} + +export interface TypeBaseProps { + // Used to handle nested references to instantiable classes + // (e.g. type[type[type[T]]]). If the field isn't present, + // it is assumed to be zero + instantiableDepth: number | undefined; + + // Used in cases where the type is a special form when used in a + // value expression such as UnionType, Literal, or Required + specialForm: ClassType | undefined; + + // Used for "type form" objects, the evaluated form + // of a type expression in a value expression context + typeForm: Type | undefined; + + // Used only for type aliases + typeAliasInfo: TypeAliasInfo | undefined; + + // Used only for types that are conditioned on a TypeVar + condition: TypeCondition[] | undefined; +} + +export interface TypeBase { + category: T; + flags: TypeFlags; + + // Optional properties common to all types. + props: TypeBaseProps | undefined; + + // Optional cached values are not cloned. + cached: CachedTypeInfo | undefined; + + // Fields that are specific to a particular type category. + // These are shared between type instances and are not + // cloned. + shared: object | undefined; + + // Fields that are specific to a particular type category. + // These are private to each type instances and are + // cloned. + priv: object | undefined; +} + +export namespace TypeBase { + export function isInstantiable(type: TypeBase) { + return (type.flags & TypeFlags.Instantiable) !== 0; + } + + export function isInstance(type: TypeBase) { + return (type.flags & TypeFlags.Instance) !== 0; + } + + export function isAmbiguous(type: TypeBase) { + return (type.flags & TypeFlags.Ambiguous) !== 0; + } + + export function addProps(type: TypeBase): TypeBaseProps { + if (!type.props) { + type.props = { + instantiableDepth: undefined, + specialForm: undefined, + typeForm: undefined, + typeAliasInfo: undefined, + condition: undefined, + }; + } + return type.props; + } + + export function getInstantiableDepth(type: TypeBase) { + return type.props?.instantiableDepth ?? 0; + } + + export function setSpecialForm(type: TypeBase, specialForm: ClassType | undefined) { + TypeBase.addProps(type).specialForm = specialForm; + } + + export function setInstantiableDepth(type: TypeBase, depth: number | undefined) { + TypeBase.addProps(type).instantiableDepth = depth; + } + + export function setTypeAliasInfo(type: TypeBase, typeAliasInfo: TypeAliasInfo | undefined) { + TypeBase.addProps(type).typeAliasInfo = typeAliasInfo; + } + + export function setTypeForm(type: TypeBase, typeForm: Type | undefined) { + TypeBase.addProps(type).typeForm = typeForm; + } + + export function setCondition(type: TypeBase, condition: TypeCondition[] | undefined) { + TypeBase.addProps(type).condition = condition; + } + + export function cloneType>(type: T): T { + const clone = { ...type }; + if (type.props) { + clone.props = { ...type.props }; + } + if (type.priv) { + clone.priv = { ...type.priv }; + } + clone.cached = undefined; + return clone; + } + + export function cloneAsSpecialForm>(type: T, specialForm: ClassType | undefined): T { + const clone = TypeBase.cloneType(type); + TypeBase.setSpecialForm(clone, specialForm); + return clone; + } + + export function cloneTypeAsInstance(type: T, cache: boolean): T { + assert(TypeBase.isInstantiable(type)); + + const newInstance = TypeBase.cloneType(type); + + // Remove type form information from the type. + if (newInstance.props?.typeForm) { + TypeBase.setTypeForm(newInstance, undefined); + } + + const depth = newInstance.props?.instantiableDepth; + if (depth === undefined) { + newInstance.flags &= ~TypeFlags.Instantiable; + newInstance.flags |= TypeFlags.Instance; + } else if (depth <= 1) { + TypeBase.setInstantiableDepth(newInstance, undefined); + } else { + TypeBase.setInstantiableDepth(newInstance, depth - 1); + } + + // Should we cache it for next time? + if (cache) { + if (!type.cached) { + type.cached = {}; + } + + type.cached.typeBaseInstanceType = newInstance; + } + + return newInstance; + } + + export function cloneTypeAsInstantiable(type: T, cache: boolean): T { + const newInstance: T = TypeBase.cloneType(type); + + if (TypeBase.isInstance(type)) { + newInstance.flags &= ~TypeFlags.Instance; + newInstance.flags |= TypeFlags.Instantiable; + } else { + const oldDepth = type.props?.instantiableDepth; + TypeBase.setInstantiableDepth(newInstance, oldDepth === undefined ? 1 : oldDepth + 1); + } + + // Remove type alias information because the type will no longer match + // that of the type alias definition. + if (newInstance.props?.typeAliasInfo) { + TypeBase.setTypeAliasInfo(newInstance, undefined); + } + + // Remove type form information from the type. + if (newInstance.props?.typeForm) { + TypeBase.setTypeForm(newInstance, undefined); + } + + // Should we cache it for next time? + if (cache) { + if (!type.cached) { + type.cached = {}; + } + + type.cached.typeBaseInstantiableType = newInstance; + } + + return newInstance; + } + + export function cloneForTypeAlias(type: T, aliasInfo: TypeAliasInfo): T { + const typeClone = cloneType(type); + + TypeBase.setTypeAliasInfo(typeClone, aliasInfo); + + return typeClone; + } + + export function cloneWithTypeForm(type: T, typeForm: Type | undefined): T { + const typeClone = cloneType(type); + + TypeBase.setTypeForm(typeClone, typeForm); + + return typeClone; + } + + export function cloneForCondition(type: T, condition: TypeCondition[] | undefined): T { + // Handle the common case where there are no conditions. In this case, + // cloning isn't necessary. + if (type.props?.condition === undefined && condition === undefined) { + return type; + } + + const typeClone = cloneType(type); + TypeBase.setCondition(typeClone, condition); + return typeClone; + } + + export function cloneForAmbiguousType(type: Type) { + if (TypeBase.isAmbiguous(type)) { + return type; + } + + const typeClone = cloneType(type); + typeClone.flags |= TypeFlags.Ambiguous; + return typeClone; + } +} + +export interface UnboundType extends TypeBase {} + +export namespace UnboundType { + const _instance: UnboundType = { + category: TypeCategory.Unbound, + flags: TypeFlags.Instantiable | TypeFlags.Instance, + props: undefined, + cached: undefined, + shared: undefined, + priv: undefined, + }; + + export function create() { + // All Unbound objects are the same, so use a shared instance. + return _instance; + } + + export function convertToInstance(type: UnboundType): UnboundType { + // Remove the "special form" if present. Otherwise return the existing type. + return type.props?.specialForm ? UnboundType.create() : type; + } +} + +export interface UnknownDetailsPriv { + // Flag that indicates whether the type is a placeholder for an incomplete + // type during code flow analysis. + isIncomplete: boolean; + + // A "possible type" is a form of a "weak union" where the actual + // type is unknown, but it could be one of the subtypes in the union. + // This is used for overload matching in cases where more than one + // overload matches due to an argument that evaluates to Any or Unknown. + possibleType: Type | undefined; +} + +export interface UnknownType extends TypeBase { + priv: UnknownDetailsPriv; +} + +export namespace UnknownType { + const _instance: UnknownType = { + category: TypeCategory.Unknown, + flags: TypeFlags.Instantiable | TypeFlags.Instance, + props: undefined, + cached: undefined, + shared: undefined, + priv: { + isIncomplete: false, + possibleType: undefined, + }, + }; + const _incompleteInstance: UnknownType = { + category: TypeCategory.Unknown, + flags: TypeFlags.Instantiable | TypeFlags.Instance, + props: undefined, + cached: undefined, + shared: undefined, + priv: { + isIncomplete: true, + possibleType: undefined, + }, + }; + + export function create(isIncomplete = false) { + return isIncomplete ? _incompleteInstance : _instance; + } + + export function createPossibleType(possibleType: Type, isIncomplete: boolean) { + const unknownWithPossibleType: UnknownType = { + category: TypeCategory.Unknown, + flags: TypeFlags.Instantiable | TypeFlags.Instance, + props: undefined, + cached: undefined, + shared: undefined, + priv: { + isIncomplete, + possibleType, + }, + }; + + return unknownWithPossibleType; + } + + export function convertToInstance(type: UnknownType): UnknownType { + // Remove the "special form" if present. Otherwise return the existing type. + return type.props?.specialForm ? UnknownType.create(type.priv.isIncomplete) : type; + } +} + +export interface ModuleDetailsPriv { + fields: SymbolTable; + docString: string | undefined; + + // If a field lookup isn't found, should the type of the + // resulting field be Any/Unknown or treated as an error? + notPresentFieldType: AnyType | UnknownType | undefined; + + // A "loader" module includes symbols that were injected by + // the module loader. We keep these separate so we don't + // pollute the symbols exported by the module itself. + loaderFields: SymbolTable; + + // The period-delimited import name of this module. + moduleName: string; + + fileUri: Uri; +} + +export interface ModuleType extends TypeBase { + priv: ModuleDetailsPriv; +} + +export namespace ModuleType { + export function create(moduleName: string, fileUri: Uri, symbolTable?: SymbolTable) { + const newModuleType: ModuleType = { + category: TypeCategory.Module, + flags: TypeFlags.Instantiable | TypeFlags.Instantiable, + props: undefined, + cached: undefined, + shared: undefined, + priv: { + fields: symbolTable || new Map(), + docString: undefined, + notPresentFieldType: undefined, + loaderFields: new Map(), + moduleName, + fileUri, + }, + }; + return newModuleType; + } + + export function getField(moduleType: ModuleType, name: string): Symbol | undefined { + // Always look for the symbol in the module's fields before consulting + // the loader fields. The loader runs before the module, so its values + // will be overwritten by the module. + let symbol = moduleType.priv.fields.get(name); + + if (moduleType.priv.loaderFields) { + if (!symbol) { + symbol = moduleType.priv.loaderFields.get(name); + } else if (symbol.getDeclarations().length === 1) { + // If the symbol is hidden when accessed via the module but is + // also accessible through a loader field, use the latter so it + // isn't flagged as an error. + const loaderSymbol = moduleType.priv.loaderFields.get(name); + if (loaderSymbol && !loaderSymbol.isExternallyHidden()) { + symbol = loaderSymbol; + } + } + } + return symbol; + } +} + +export interface DataClassEntry { + name: string; + classType: ClassType; + mroClass?: ClassType; + isClassVar: boolean; + isKeywordOnly: boolean; + alias?: string | undefined; + hasDefault?: boolean | undefined; + isDefaultFactory?: boolean | undefined; + nameNode: NameNode | undefined; + typeAnnotationNode: TypeAnnotationNode | undefined; + defaultExpr?: ExpressionNode | undefined; + includeInInit: boolean; + type: Type; + converter?: ArgumentNode | undefined; +} + +export interface TypedDictEntry { + valueType: Type; + isRequired: boolean; + isReadOnly: boolean; + isProvided: boolean; +} + +export interface TypedDictEntries { + knownItems: Map; + extraItems?: TypedDictEntry | undefined; +} + +export const enum ClassTypeFlags { + None = 0, + + // Class is defined in the "builtins" or "typing" file. + BuiltIn = 1 << 0, + + // Class requires special-case handling because it + // exhibits non-standard behavior or is not defined + // formally as a class. Examples include 'Optional' + // and 'Union'. + SpecialBuiltIn = 1 << 1, + + // Introduced in PEP 589, TypedDict classes provide a way + // to specify type hints for dictionaries with different + // value types and a limited set of static keys. + TypedDictClass = 1 << 2, + + // Used in conjunction with TypedDictClass, indicates that + // the TypedDict class is marked "closed". + TypedDictMarkedClosed = 1 << 3, + + // Used in conjunction with TypedDictClass, indicates that + // the TypedDict class is marked "closed" or one or more of + // its superclasses is marked "closed". + TypedDictEffectivelyClosed = 1 << 4, + + // Used in conjunction with TypedDictClass, indicates that + // the dictionary values can be omitted. + CanOmitDictValues = 1 << 5, + + // The class derives from a class that has the ABCMeta + // metaclass. Such classes are allowed to contain + // @abstractmethod decorators. + SupportsAbstractMethods = 1 << 6, + + // Derives from property class and has the semantics of + // a property (with optional setter, deleter). + PropertyClass = 1 << 7, + + // The class is decorated with a "@final" decorator + // indicating that it cannot be subclassed. + Final = 1 << 8, + + // The class derives directly from "Protocol". + ProtocolClass = 1 << 9, + + // A class whose constructor (__init__ method) does not have + // annotated types and is treated as though each parameter + // is a generic type for purposes of type inference. + PseudoGenericClass = 1 << 10, + + // A protocol class that is "runtime checkable" can be used + // in an isinstance call. + RuntimeCheckable = 1 << 11, + + // The type is defined in the typing_extensions.pyi file. + TypingExtensionClass = 1 << 12, + + // The class type is in the process of being evaluated and + // is not yet complete. This allows us to detect cases where + // the class refers to itself (e.g. uses itself as a type + // argument to one of its generic base classes). + PartiallyEvaluated = 1 << 13, + + // The class or one of its ancestors defines a __class_getitem__ + // method that is used for subscripting. This is not set if the + // class is generic, and therefore supports standard subscripting + // semantics. + HasCustomClassGetItem = 1 << 14, + + // The tuple class requires special-case handling for its type arguments. + TupleClass = 1 << 15, + + // The class has a metaclass of EnumMeta or derives from + // a class that has this metaclass. + EnumClass = 1 << 16, + + // Properties that are defined using the @classmethod decorator. + ClassProperty = 1 << 17, + + // Class is declared within a type stub file. + DefinedInStub = 1 << 18, + + // Decorated with @type_check_only. + TypeCheckOnly = 1 << 20, + + // Created with the NewType call. + NewTypeClass = 1 << 21, + + // Class is allowed to be used as an implicit type alias even + // though it is not defined using a `class` statement. + ValidTypeAliasClass = 1 << 22, + + // A special form is not compatible with type[T] and cannot + // be directly instantiated. + SpecialFormClass = 1 << 23, + + // This class is rejected when used as the second argument to + // an isinstance or issubclass call. + IllegalIsinstanceClass = 1 << 24, +} + +export interface DataClassBehaviors { + skipGenerateInit?: boolean; + skipGenerateEq?: boolean; + generateOrder?: boolean; + generateSlots?: boolean; + generateHash?: boolean; + matchArgs?: boolean; + keywordOnly?: boolean; + frozen?: boolean; + frozenDefault?: boolean; + fieldDescriptorNames: string[]; +} + +export interface ClassDetailsShared { + name: string; + fullName: string; + moduleName: string; + fileUri: Uri; + flags: ClassTypeFlags; + typeSourceId: TypeSourceId; + baseClasses: Type[]; + mro: (ClassType | AnyType | UnknownType)[]; + declaration?: ClassDeclaration | SpecialBuiltInClassDeclaration | undefined; + declaredMetaclass?: ClassType | UnknownType | undefined; + effectiveMetaclass?: ClassType | UnknownType | undefined; + fields: SymbolTable; + typeParams: TypeVarType[]; + typeVarScopeId?: TypeVarScopeId | undefined; + docString?: string | undefined; + dataClassEntries?: DataClassEntry[] | undefined; + dataClassBehaviors?: DataClassBehaviors | undefined; + namedTupleEntries?: Set | undefined; + typedDictEntries?: TypedDictEntries | undefined; + typedDictExtraItemsExpr?: ExpressionNode | undefined; + localSlotsNames?: string[]; + + // If the class is decorated with a @deprecated decorator, this + // string provides the message to be displayed when the class + // is used. + deprecatedMessage?: string | undefined; + + // A cache of protocol classes (indexed by the class full name) + // that have been determined to be compatible or incompatible + // with this class. We use "object" here to avoid a circular dependency. + // It's actually a map of ProtocolCompatibility objects. + protocolCompatibility?: object; + + // Transforms to apply if this class is used as a metaclass + // or a base class. + classDataClassTransform?: DataClassBehaviors | undefined; + + // Indicates that one or more type parameters has an + // autovariance, so variance must be inferred. + requiresVarianceInference?: boolean; + + // A cached value that indicates whether an instance of this class + // is hashable (i.e. does not override "__hash__" with None). + isInstanceHashable?: boolean; + + // Callback for deferred synthesis of methods in symbol table. + synthesizeMethodsDeferred?: () => void; + + // Callback for calculating inherited slots names. + calculateInheritedSlotsNamesDeferred?: () => void; + inheritedSlotsNamesCached?: string[]; +} + +export interface TupleTypeArg { + type: Type; + + // Does the type argument represent a single value or + // an "unbounded" (zero or more) arguments? + isUnbounded: boolean; + + // For tuples captured from a callable, this indicates + // the corresponding positional parameter has a default + // argument and can therefore be omitted. + isOptional?: boolean; +} + +export interface PropertyMethodInfo { + // The decorated function (fget, fset, fdel) for a property. This is + // normally a single function, but it can be an OverloadedType if the + // accessor (currently only the setter) is overloaded. + methodType: FunctionType | OverloadedType; + + // The class that declared this function + classType: ClassType | undefined; +} + +export interface ClassDetailsPriv { + // A generic class that has been completely or partially + // specialized will have type arguments that correspond to + // some or all of the type parameters. + typeArgs?: Type[] | undefined; + + // If a generic container class (like a list or dict) is known + // to contain no elements, its type arguments may be "Unknown". + // This value allows us to elide the Unknown when it's safe to + // do so. + isEmptyContainer?: boolean | undefined; + + // For tuples, the class definition calls for a single type parameter but + // the spec allows the programmer to provide an arbitrary number of + // type arguments. This field holds the individual type arguments + // while the "typeArgs" field holds the derived non-variadic + // type argument, which is the union of the tuple type arguments. + tupleTypeArgs?: TupleTypeArg[] | undefined; + + // We sometimes package multiple types into a tuple internally + // for matching against a variadic type variable or another unpacked + // tuple. We need to be able to distinguish this case from normal tuples. + isUnpacked?: boolean | undefined; + + // If type arguments are present, were they explicit (i.e. + // provided explicitly in the code)? + isTypeArgExplicit?: boolean | undefined; + + // This class type represents the class and any classes that + // derive from it, as opposed to the original class only. This + // distinction is important in certain scenarios like instantiation + // of abstract or protocol classes. + includeSubclasses?: boolean; + + // This class type represents the class and any auto-promotion + // types that PEP 484 indicates should be treated as subclasses + // when the type appears within a type annotation. + includePromotions?: boolean; + + // Some types can be further constrained to have + // literal types (e.g. true or 'string' or 3). + literalValue?: LiteralValue | undefined; + + // The typing module defines aliases for builtin types + // (e.g. Tuple, List, Dict). This field holds the alias + // name. + aliasName?: string | undefined; + + // Used for "narrowing" of typed dicts where some entries + // that are not required have been confirmed to be present + // through the use of a guard expression. + typedDictNarrowedEntries?: Map | undefined; + + // Indicates that the typed dict class should be considered "partial", + // i.e. all of its entries are effectively NotRequired and only + // writable entries are considered present, and they are marked read-only. + // This is used for the TypedDict "update" method. + isTypedDictPartial?: boolean; + + // Indicates whether the class is an asymmetric descriptor + // or property - one where the __get__ and __set__ types differ. + // If undefined, it hasn't been tested yet for asymmetry. + isAsymmetricDescriptor?: boolean; + + // Indicates whether the class has an asymmetric __getattr__ and + // __setattr__ signature. + isAsymmetricAttributeAccessor?: boolean; + + // Special-case fields for property classes. + fgetInfo?: PropertyMethodInfo | undefined; + fsetInfo?: PropertyMethodInfo | undefined; + fdelInfo?: PropertyMethodInfo | undefined; + + // Provides the deprecated message specifically for instances of + // the "deprecated" class. This allows these instances to be used + // as decorators for other classes or functions. + deprecatedInstanceMessage?: string | undefined; + + // Special-case fields for partial class. + partialCallType?: Type | undefined; +} + +export interface ClassType extends TypeBase { + shared: ClassDetailsShared; + priv: ClassDetailsPriv; +} + +export namespace ClassType { + export function createInstantiable( + name: string, + fullName: string, + moduleName: string, + fileUri: Uri, + flags: ClassTypeFlags, + typeSourceId: TypeSourceId, + declaredMetaclass: ClassType | UnknownType | undefined, + effectiveMetaclass: ClassType | UnknownType | undefined, + docString?: string + ) { + const newClass: ClassType = { + category: TypeCategory.Class, + flags: TypeFlags.Instantiable, + props: undefined, + cached: undefined, + shared: { + name, + fullName, + moduleName, + fileUri, + flags, + typeSourceId, + baseClasses: [], + declaredMetaclass, + effectiveMetaclass, + mro: [], + fields: new Map(), + typeParams: [], + docString, + }, + priv: {}, + }; + + return newClass; + } + + export function cloneAsInstance(type: ClassType, includeSubclasses = true): ClassType { + if (TypeBase.isInstance(type)) { + return type; + } + + if (includeSubclasses && type.cached?.typeBaseInstanceType) { + return type.cached.typeBaseInstanceType as ClassType; + } + + const newInstance = TypeBase.cloneTypeAsInstance(type, /* cache */ includeSubclasses); + if (newInstance.props?.specialForm) { + TypeBase.setSpecialForm(newInstance, undefined); + } + + if (includeSubclasses) { + newInstance.priv.includeSubclasses = true; + } + + return newInstance; + } + + export function cloneAsInstantiable(type: ClassType, includeSubclasses = true): ClassType { + if (includeSubclasses && type.cached?.typeBaseInstantiableType) { + return type.cached.typeBaseInstantiableType as ClassType; + } + + const newInstance = TypeBase.cloneTypeAsInstantiable(type, includeSubclasses); + if (includeSubclasses) { + newInstance.priv.includeSubclasses = true; + } + + return newInstance; + } + + export function specialize( + classType: ClassType, + typeArgs: Type[] | undefined, + isTypeArgExplicit?: boolean, + includeSubclasses = false, + tupleTypeArgs?: TupleTypeArg[], + isEmptyContainer?: boolean + ): ClassType { + const newClassType = TypeBase.cloneType(classType); + + newClassType.priv.typeArgs = typeArgs?.length === 0 ? undefined : typeArgs; + + // If the user passed undefined for this argument, infer it + // based on whether typeArgs was provided. + if (isTypeArgExplicit === undefined) { + isTypeArgExplicit = !!typeArgs; + } + + newClassType.priv.isTypeArgExplicit = isTypeArgExplicit; + + if (includeSubclasses) { + newClassType.priv.includeSubclasses = true; + } + + newClassType.priv.tupleTypeArgs = tupleTypeArgs ? [...tupleTypeArgs] : undefined; + + if (isEmptyContainer !== undefined) { + newClassType.priv.isEmptyContainer = isEmptyContainer; + } + + return newClassType; + } + + export function cloneIncludeSubclasses(classType: ClassType, includeSubclasses = true) { + if (!!classType.priv.includeSubclasses === includeSubclasses) { + return classType; + } + + const newClassType = TypeBase.cloneType(classType); + newClassType.priv.includeSubclasses = includeSubclasses; + return newClassType; + } + + export function cloneWithLiteral(classType: ClassType, value: LiteralValue | undefined): ClassType { + const newClassType = TypeBase.cloneType(classType); + newClassType.priv.literalValue = value; + + // Remove type alias information because the type will no longer match + // that of the type alias definition if we change the literal type. + if (newClassType.props?.typeAliasInfo) { + TypeBase.setTypeAliasInfo(newClassType, undefined); + } + + return newClassType; + } + + export function cloneForDeprecatedInstance(type: ClassType, deprecatedMessage?: string): ClassType { + const newClassType = TypeBase.cloneType(type); + newClassType.priv.deprecatedInstanceMessage = deprecatedMessage; + return newClassType; + } + + export function cloneForTypingAlias(classType: ClassType, aliasName: string): ClassType { + const newClassType = TypeBase.cloneType(classType); + newClassType.priv.aliasName = aliasName; + return newClassType; + } + + export function cloneForNarrowedTypedDictEntries( + classType: ClassType, + narrowedEntries?: Map + ): ClassType { + const newClassType = TypeBase.cloneType(classType); + newClassType.priv.typedDictNarrowedEntries = narrowedEntries; + return newClassType; + } + + export function cloneForPartialTypedDict(classType: ClassType): ClassType { + const newClassType = TypeBase.cloneType(classType); + newClassType.priv.isTypedDictPartial = true; + return newClassType; + } + + export function cloneRemoveTypePromotions(classType: ClassType): ClassType { + if (!classType.priv.includePromotions) { + return classType; + } + + const newClassType = TypeBase.cloneType(classType); + if (newClassType.priv.includePromotions !== undefined) { + newClassType.priv.includePromotions = undefined; + } + return newClassType; + } + + export function cloneForPartial(classType: ClassType, partialCallType: Type): ClassType { + const newClassType = TypeBase.cloneType(classType); + newClassType.priv.partialCallType = partialCallType; + return newClassType; + } + + export function cloneForUnpacked(classType: ClassType): ClassType { + if (classType.priv.isUnpacked) { + return classType; + } + + const newClassType = TypeBase.cloneType(classType); + newClassType.priv.isUnpacked = true; + return newClassType; + } + + export function cloneForPacked(classType: ClassType): ClassType { + if (!classType.priv.isUnpacked) { + return classType; + } + + const newClassType = TypeBase.cloneType(classType); + newClassType.priv.isUnpacked = false; + return newClassType; + } + + export function cloneWithNewFlags(classType: ClassType, newFlags: ClassTypeFlags): ClassType { + const newClassType = TypeBase.cloneType(classType); + newClassType.shared = { ...newClassType.shared }; + newClassType.shared.flags = newFlags; + return newClassType; + } + + export function isLiteralValueSame(type1: ClassType, type2: ClassType): boolean { + if (type1.priv.literalValue === undefined) { + return type2.priv.literalValue === undefined; + } else if (type2.priv.literalValue === undefined) { + return false; + } + + if (type1.priv.literalValue instanceof EnumLiteral) { + if (type2.priv.literalValue instanceof EnumLiteral) { + return type1.priv.literalValue.itemName === type2.priv.literalValue.itemName; + } + return false; + } + + if (type1.priv.literalValue instanceof SentinelLiteral) { + if (type2.priv.literalValue instanceof SentinelLiteral) { + return type1.priv.literalValue.classFullName === type2.priv.literalValue.classFullName; + } + return false; + } + + return type1.priv.literalValue === type2.priv.literalValue; + } + + // Determines whether two typed dict classes are equivalent given + // that one or both have narrowed entries (i.e. entries that are + // guaranteed to be present). + export function isTypedDictNarrowedEntriesSame(type1: ClassType, type2: ClassType): boolean { + if (type1.priv.typedDictNarrowedEntries) { + if (!type2.priv.typedDictNarrowedEntries) { + return false; + } + + const tdEntries1 = type1.priv.typedDictNarrowedEntries; + const tdEntries2 = type2.priv.typedDictNarrowedEntries; + + if (tdEntries1.size !== tdEntries2.size) { + return false; + } + + let key: string; + let entry1: TypedDictEntry; + for ([key, entry1] of tdEntries1.entries()) { + const entry2 = tdEntries2.get(key); + if (!entry2) { + return false; + } + if (entry1.isProvided !== entry2.isProvided) { + return false; + } + } + } else if (type2.priv.typedDictNarrowedEntries) { + return false; + } + + return true; + } + + // Determines whether typed dict class type1 is a narrower form of type2, + // i.e. all of the "narrowed entries" found within type2 are also found + // within type1. + export function isTypedDictNarrower(type1: ClassType, type2: ClassType): boolean { + const tdEntries2 = type2.priv.typedDictNarrowedEntries; + if (!tdEntries2) { + return true; + } + + const tdEntries1 = type1.priv.typedDictNarrowedEntries ?? new Map(); + + let key: string; + let entry2: TypedDictEntry; + for ([key, entry2] of tdEntries2.entries()) { + if (entry2.isProvided) { + const entry1 = tdEntries1.get(key); + if (!entry1?.isProvided) { + return false; + } + } + } + + return true; + } + + // Is the class generic but not specialized? + export function isUnspecialized(classType: ClassType) { + return classType.shared.typeParams.length > 0 && classType.priv.typeArgs === undefined; + } + + export function isSpecialBuiltIn(classType: ClassType, className?: string) { + if (!(classType.shared.flags & ClassTypeFlags.SpecialBuiltIn) && !classType.priv.aliasName) { + return false; + } + + if (className !== undefined) { + return classType.shared.name === className; + } + + return true; + } + + export function isBuiltIn(classType: ClassType, className?: string | string[]) { + if (!(classType.shared.flags & ClassTypeFlags.BuiltIn)) { + return false; + } + + if (className !== undefined) { + const classArray = Array.isArray(className) ? className : [className]; + return classArray.some( + (name) => + name === classType.shared.name || + name === classType.shared.fullName || + name === classType.priv.aliasName + ); + } + + return true; + } + + export function supportsAbstractMethods(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.SupportsAbstractMethods); + } + + export function isDataClass(classType: ClassType) { + return !!classType.shared.dataClassBehaviors; + } + + export function isDataClassSkipGenerateInit(classType: ClassType) { + return !!classType.shared.dataClassBehaviors?.skipGenerateInit; + } + + export function isDataClassSkipGenerateEq(classType: ClassType) { + return !!classType.shared.dataClassBehaviors?.skipGenerateEq; + } + + export function isDataClassFrozen(classType: ClassType) { + return !!classType.shared.dataClassBehaviors?.frozen; + } + + export function isDataClassGenerateOrder(classType: ClassType) { + return !!classType.shared.dataClassBehaviors?.generateOrder; + } + + export function isDataClassKeywordOnly(classType: ClassType) { + return !!classType.shared.dataClassBehaviors?.keywordOnly; + } + + export function isDataClassGenerateSlots(classType: ClassType) { + return !!classType.shared.dataClassBehaviors?.generateSlots; + } + + export function isDataClassGenerateHash(classType: ClassType) { + return !!classType.shared.dataClassBehaviors?.generateHash; + } + + export function isTypeCheckOnly(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.TypeCheckOnly); + } + + export function isNewTypeClass(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.NewTypeClass); + } + + export function isValidTypeAliasClass(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.ValidTypeAliasClass); + } + + export function isSpecialFormClass(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.SpecialFormClass); + } + + export function isIllegalIsinstanceClass(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.IllegalIsinstanceClass); + } + + export function isTypedDictClass(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.TypedDictClass); + } + + export function isCanOmitDictValues(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.CanOmitDictValues); + } + + export function isTypedDictMarkedClosed(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.TypedDictMarkedClosed); + } + + export function isTypedDictEffectivelyClosed(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.TypedDictEffectivelyClosed); + } + + export function isEnumClass(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.EnumClass); + } + + export function isPropertyClass(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.PropertyClass); + } + + export function isClassProperty(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.ClassProperty); + } + + export function isFinal(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.Final); + } + + export function isProtocolClass(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.ProtocolClass); + } + + export function isDefinedInStub(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.DefinedInStub); + } + + export function isPseudoGenericClass(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.PseudoGenericClass); + } + + export function getDataClassEntries(classType: ClassType): DataClassEntry[] { + classType.shared.synthesizeMethodsDeferred?.(); + + return classType.shared.dataClassEntries || []; + } + + export function isRuntimeCheckable(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.RuntimeCheckable); + } + + export function isTypingExtensionClass(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.TypingExtensionClass); + } + + export function isPartiallyEvaluated(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.PartiallyEvaluated); + } + + export function hasCustomClassGetItem(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.HasCustomClassGetItem); + } + + export function isTupleClass(classType: ClassType) { + return !!(classType.shared.flags & ClassTypeFlags.TupleClass); + } + + export function getTypeParams(classType: ClassType) { + return classType.shared.typeParams; + } + + export function derivesFromAnyOrUnknown(classType: ClassType) { + return classType.shared.mro.some((baseClass) => isAnyOrUnknown(baseClass)); + } + + export function getSymbolTable(classType: ClassType) { + classType.shared.synthesizeMethodsDeferred?.(); + + return classType.shared.fields; + } + + export function getInheritedSlotsNames(classType: ClassType) { + // First synthesize methods if needed. The slots entries + // can depend on synthesized methods. + classType.shared.synthesizeMethodsDeferred?.(); + + classType.shared.calculateInheritedSlotsNamesDeferred?.(); + + return classType.shared.inheritedSlotsNamesCached; + } + + // Similar to isPartiallyEvaluated except that it also looks at all of the + // classes in the MRO list for this class to see if any of them are still + // partially evaluated. + export function isHierarchyPartiallyEvaluated(classType: ClassType) { + return ( + ClassType.isPartiallyEvaluated(classType) || + classType.shared.mro.some((mroClass) => isClass(mroClass) && ClassType.isPartiallyEvaluated(mroClass)) + ); + } + + export function hasNamedTupleEntry(classType: ClassType, name: string): boolean { + if (!classType.shared.namedTupleEntries) { + return false; + } + + return classType.shared.namedTupleEntries.has(name); + } + + // Same as isTypeSame except that it doesn't compare type arguments. + export function isSameGenericClass(classType: ClassType, type2: ClassType, recursionCount = 0) { + if (!classType.priv.isTypedDictPartial !== !type2.priv.isTypedDictPartial) { + return false; + } + + if (TypeBase.isInstance(classType) !== TypeBase.isInstance(type2)) { + return false; + } + + if (TypeBase.getInstantiableDepth(classType) !== TypeBase.getInstantiableDepth(type2)) { + return false; + } + + const class1Details = classType.shared; + const class2Details = type2.shared; + + if (class1Details === class2Details) { + return true; + } + + // Compare most of the details fields. We intentionally skip the isAbstractClass + // flag because it gets set dynamically. + if ( + class1Details.fullName !== class2Details.fullName || + class1Details.flags !== class2Details.flags || + class1Details.typeSourceId !== class2Details.typeSourceId || + class1Details.baseClasses.length !== class2Details.baseClasses.length || + class1Details.typeParams.length !== class2Details.typeParams.length + ) { + return false; + } + + if (recursionCount > maxTypeRecursionCount) { + return true; + } + recursionCount++; + + // Special-case NamedTuple and Tuple classes because we rewrite the base classes + // in these cases. + if (ClassType.isBuiltIn(classType, 'NamedTuple') && ClassType.isBuiltIn(type2, 'NamedTuple')) { + return true; + } + if (ClassType.isBuiltIn(classType, 'tuple') && ClassType.isBuiltIn(type2, 'tuple')) { + return true; + } + + // Make sure the base classes match. + for (let i = 0; i < class1Details.baseClasses.length; i++) { + if ( + !isTypeSame( + class1Details.baseClasses[i], + class2Details.baseClasses[i], + { ignorePseudoGeneric: true }, + recursionCount + ) + ) { + return false; + } + } + + if (class1Details.declaredMetaclass || class2Details.declaredMetaclass) { + if ( + !class1Details.declaredMetaclass || + !class2Details.declaredMetaclass || + !isTypeSame( + class1Details.declaredMetaclass, + class2Details.declaredMetaclass, + { ignorePseudoGeneric: true }, + recursionCount + ) + ) { + return false; + } + } + + for (let i = 0; i < class1Details.typeParams.length; i++) { + if ( + !isTypeSame( + class1Details.typeParams[i], + class2Details.typeParams[i], + { ignorePseudoGeneric: true }, + recursionCount + ) + ) { + return false; + } + } + + return true; + } + + // Determines whether this is a subclass (derived class) + // of the specified class. If the caller passes an empty + // array to inheritanceChain, it will be filled in by + // the call to include the chain of inherited classes starting + // with type2 and ending with this type. + export function isDerivedFrom( + subclassType: ClassType, + parentClassType: ClassType, + inheritanceChain?: InheritanceChain + ): boolean { + // Is it the exact same class? + if (isSameGenericClass(subclassType, parentClassType)) { + // Handle literal types. + if (parentClassType.priv.literalValue !== undefined) { + if ( + subclassType.priv.literalValue === undefined || + !ClassType.isLiteralValueSame(parentClassType, subclassType) + ) { + return false; + } + } + + if (inheritanceChain) { + inheritanceChain.push(subclassType); + } + return true; + } + + // Handle built-in types like 'dict' and 'list', which are all + // subclasses of object even though they are not explicitly declared + // that way. + if (isBuiltIn(subclassType) && isBuiltIn(parentClassType, 'object')) { + if (inheritanceChain) { + inheritanceChain.push(parentClassType); + } + return true; + } + + // Handle the case where the subclass is a type[type[T]] and the parent + // class is type. + const subclassDepth = TypeBase.getInstantiableDepth(subclassType); + if (subclassDepth > 0) { + if (isBuiltIn(parentClassType, 'type') && TypeBase.getInstantiableDepth(parentClassType) < subclassDepth) { + if (inheritanceChain) { + inheritanceChain.push(parentClassType); + } + return true; + } + } + + // Handle the case where both source and dest are property objects. This + // special case is needed because we synthesize a new class for each + // property declaration. + if (ClassType.isBuiltIn(subclassType, 'property') && ClassType.isBuiltIn(parentClassType, 'property')) { + if (inheritanceChain) { + inheritanceChain.push(subclassType); + } + return true; + } + + for (const baseClass of subclassType.shared.baseClasses) { + if (isInstantiableClass(baseClass)) { + if (isDerivedFrom(baseClass, parentClassType, inheritanceChain)) { + if (inheritanceChain) { + inheritanceChain.push(subclassType); + } + return true; + } + } else if (isAnyOrUnknown(baseClass)) { + if (inheritanceChain) { + inheritanceChain.push(UnknownType.create()); + } + return true; + } + } + + return false; + } + + export function getReverseMro(classType: ClassType): (ClassType | UnknownType | AnyType)[] { + return classType.shared.mro.slice(0).reverse(); + } +} + +export enum FunctionParamFlags { + None = 0, + + // Is the name of the parameter synthesize internally? + NameSynthesized = 1 << 0, + + // Does the parameter have an explicitly-declared type? + TypeDeclared = 1 << 1, + + // Is the type of the parameter inferred? + TypeInferred = 1 << 2, +} + +export interface FunctionParam { + category: ParamCategory; + flags: FunctionParamFlags; + name: string | undefined; + + // Use getParamType to access this field. + // eslint-disable-next-line @typescript-eslint/naming-convention + _type: Type; + + // Use getParamDefaultType to access this field. + // eslint-disable-next-line @typescript-eslint/naming-convention + _defaultType: Type | undefined; + + defaultExpr: ExpressionNode | undefined; +} + +export namespace FunctionParam { + export function create( + category: ParamCategory, + type: Type, + flags = FunctionParamFlags.None, + name?: string, + defaultType?: Type, + defaultExpr?: ExpressionNode + ): FunctionParam { + return { category, flags, name, _type: type, _defaultType: defaultType, defaultExpr }; + } + + export function isNameSynthesized(param: FunctionParam) { + return !!(param.flags & FunctionParamFlags.NameSynthesized); + } + + export function isTypeDeclared(param: FunctionParam) { + return !!(param.flags & FunctionParamFlags.TypeDeclared); + } + + export function isTypeInferred(param: FunctionParam) { + return !!(param.flags & FunctionParamFlags.TypeInferred); + } +} + +export function isPositionOnlySeparator(param: FunctionParam) { + // A simple parameter with no name is treated as a "/" separator. + return param.category === ParamCategory.Simple && !param.name; +} + +export function isKeywordOnlySeparator(param: FunctionParam) { + // An *args parameter with no name is treated as a "*" separator. + return param.category === ParamCategory.ArgsList && !param.name; +} + +export const enum FunctionTypeFlags { + None = 0, + + // Function is a __new__ method; first parameter is "cls" + ConstructorMethod = 1 << 0, + + // Function is decorated with @classmethod; first parameter is "cls"; + // can be bound to associated class + ClassMethod = 1 << 1, + + // Function is decorated with @staticmethod; cannot be bound to class + StaticMethod = 1 << 2, + + // Function is decorated with @abstractmethod + AbstractMethod = 1 << 3, + + // Function contains "yield" or "yield from" statements + Generator = 1 << 4, + + // Skip check that validates that all parameters without default + // value expressions have corresponding arguments; used for + // named tuples in some cases + DisableDefaultChecks = 1 << 5, + + // Method has no declaration in user code, it's synthesized; used + // for implied methods such as those used in namedtuple, dataclass, etc. + SynthesizedMethod = 1 << 6, + + // Decorated with @type_check_only. + TypeCheckOnly = 1 << 7, + + // Function is decorated with @overload + Overloaded = 1 << 8, + + // Function is declared with async keyword + Async = 1 << 9, + + // Function is declared within a type stub fille + StubDefinition = 1 << 11, + + // Function is declared within a module that claims to be fully typed + // (i.e. a "py.typed" file is present). + PyTypedDefinition = 1 << 12, + + // Function is decorated with @final + Final = 1 << 13, + + // Function has one or more parameters that are missing type annotations + UnannotatedParams = 1 << 14, + + // The *args and **kwargs parameters do not need to be present for this + // function to be compatible. This is used for Callable[..., x] and + // ... type arguments to ParamSpec and Concatenate. + GradualCallableForm = 1 << 15, + + // This function represents the value bound to a ParamSpec, so its return + // type is not meaningful. + ParamSpecValue = 1 << 16, + + // The function type is in the process of being evaluated and + // is not yet complete. This allows us to detect cases where + // the function refers to itself (e.g. uses a type annotation + // that contains a forward reference that requires the function + // type itself to be evaluated first). + PartiallyEvaluated = 1 << 17, + + // Decorated with @override as defined in PEP 698. + Overridden = 1 << 18, + + // Decorated with @no_type_check. + NoTypeCheck = 1 << 19, + + // Function defined in one of the core stdlib modules. + BuiltIn = 1 << 20, +} + +interface FunctionDetailsShared { + name: string; + fullName: string; + moduleName: string; + flags: FunctionTypeFlags; + typeParams: TypeVarType[]; + parameters: FunctionParam[]; + declaredReturnType: Type | undefined; + declaration: FunctionDeclaration | undefined; + typeVarScopeId: TypeVarScopeId | undefined; + docString: string | undefined; + deprecatedMessage: string | undefined; + + // If this is a method, this refers to the class that contains it. + methodClass: ClassType | undefined; + + // Transforms to apply if this function is used + // as a decorator. + decoratorDataClassBehaviors: DataClassBehaviors | undefined; + + // Inferred return type. Filled in lazily. + inferredReturnType?: { + type: Type; + isIncomplete?: boolean; + evaluationCount?: number; + }; +} + +export interface SpecializedFunctionTypes { + // Specialized types for each of the parameters in the "parameters" array. + parameterTypes: Type[]; + + // Specialized types of default arguments for each parameter in + // the "parameters" array. If an entry is undefined or the entire array + // is missing, there is no specialized type, and the original "defaultType" + // should be used. + parameterDefaultTypes: (Type | undefined)[] | undefined; + + // Specialized type of the declared return type. Undefined if there is + // no declared return type. + returnType: Type | undefined; +} + +export interface CallSiteInferenceTypeCacheEntry { + paramTypes: Type[]; + returnType: Type; +} + +export interface SignatureWithOffsets { + type: FunctionType | OverloadedType; + expressionOffsets: number[]; +} + +export interface FunctionDetailsPriv { + // For __new__ and __init__ methods, the TypeVar scope ID of the + // associated class. + constructorTypeVarScopeId?: TypeVarScopeId | undefined; + + // A function type can be specialized (i.e. generic type + // variables replaced by a concrete type). + specializedTypes?: SpecializedFunctionTypes | undefined; + + // Call-site return type inference cache. + callSiteReturnTypeCache?: CallSiteInferenceTypeCacheEntry[]; + + // If this is a bound function where the first parameter + // was stripped from the original unbound function, the + // (specialized) type of that stripped parameter. + strippedFirstParamType?: Type | undefined; + + // If this is a bound function where the first parameter + // was stripped from the original unbound function, + // the class or object to which the function was bound. + boundToType?: ClassType | undefined; + + // The flags for the function prior to binding + preBoundFlags?: FunctionTypeFlags; + + // If this function is part of an overloaded function, this + // refers back to the overloaded function type. + overloaded?: OverloadedType; + + // If this function is created with a "Callable" annotation with + // type arguments? This allows us to detect and report an error + // when this is used in an isinstance call. + isCallableWithTypeArgs?: boolean; +} + +export interface FunctionType extends TypeBase { + shared: FunctionDetailsShared; + priv: FunctionDetailsPriv; +} + +export namespace FunctionType { + export function createInstance( + name: string, + fullName: string, + moduleName: string, + functionFlags: FunctionTypeFlags, + docString?: string + ) { + return create(name, fullName, moduleName, functionFlags, TypeFlags.Instance, docString); + } + + export function createInstantiable(functionFlags: FunctionTypeFlags, docString?: string) { + return create('', '', '', functionFlags, TypeFlags.Instantiable, docString); + } + + export function createSynthesizedInstance(name: string, additionalFlags = FunctionTypeFlags.None) { + return create(name, name, '', additionalFlags | FunctionTypeFlags.SynthesizedMethod, TypeFlags.Instance); + } + + function create( + name: string, + fullName: string, + moduleName: string, + functionFlags: FunctionTypeFlags, + typeFlags: TypeFlags, + docString?: string + ) { + const newFunctionType: FunctionType = { + category: TypeCategory.Function, + flags: typeFlags, + props: undefined, + cached: undefined, + shared: { + name, + fullName, + moduleName, + flags: functionFlags, + typeParams: [], + parameters: [], + declaredReturnType: undefined, + declaration: undefined, + typeVarScopeId: undefined, + docString, + deprecatedMessage: undefined, + methodClass: undefined, + decoratorDataClassBehaviors: undefined, + }, + priv: {}, + }; + return newFunctionType; + } + + // Creates a deep copy of the function type, including a fresh + // version of _functionDetails. + export function clone(type: FunctionType, stripFirstParam = false, boundToType?: ClassType): FunctionType { + const newFunction = TypeBase.cloneType(type); + + newFunction.shared = { ...type.shared }; + newFunction.priv.preBoundFlags = newFunction.shared.flags; + newFunction.priv.boundToType = boundToType; + + if (boundToType) { + if (type.shared.name === '__new__' || type.shared.name === '__init__') { + newFunction.priv.constructorTypeVarScopeId = boundToType.shared.typeVarScopeId; + } + } + + if (stripFirstParam) { + if (type.shared.parameters.length > 0) { + if (type.shared.parameters[0].category === ParamCategory.Simple) { + if (type.shared.parameters.length > 0) { + // Stash away the effective type of the first parameter or + // Any if it was inferred. + newFunction.priv.strippedFirstParamType = FunctionParam.isTypeInferred( + type.shared.parameters[0] + ) + ? AnyType.create() + : getParamType(type, 0); + } + newFunction.shared.parameters = type.shared.parameters.slice(1); + } + } else { + stripFirstParam = false; + } + } + + if (type.props?.typeAliasInfo) { + TypeBase.setTypeAliasInfo(newFunction, type.props.typeAliasInfo); + } + + if (type.priv.specializedTypes) { + newFunction.priv.specializedTypes = { + parameterTypes: stripFirstParam + ? type.priv.specializedTypes.parameterTypes.slice(1) + : type.priv.specializedTypes.parameterTypes, + parameterDefaultTypes: stripFirstParam + ? type.priv.specializedTypes.parameterDefaultTypes?.slice(1) + : type.priv.specializedTypes.parameterDefaultTypes, + returnType: type.priv.specializedTypes.returnType, + }; + } + + newFunction.shared.inferredReturnType = type.shared.inferredReturnType; + + return newFunction; + } + + export function cloneAsInstance(type: FunctionType): FunctionType { + if (type.cached?.typeBaseInstanceType) { + return type.cached.typeBaseInstanceType as FunctionType; + } + + const newInstance = TypeBase.cloneTypeAsInstance(type, /* cache */ true); + if (newInstance.props?.specialForm) { + TypeBase.setSpecialForm(newInstance, undefined); + } + return newInstance; + } + + export function cloneAsInstantiable(type: FunctionType): FunctionType { + if (type.cached?.typeBaseInstantiableType) { + return type.cached.typeBaseInstantiableType as FunctionType; + } + + const newInstance = TypeBase.cloneTypeAsInstantiable(type, /* cache */ true); + return newInstance; + } + + // Creates a shallow copy of the function type with new + // specialized types. The clone shares the _functionDetails + // with the object being cloned. + export function specialize(type: FunctionType, specializedTypes: SpecializedFunctionTypes): FunctionType { + const newFunction = TypeBase.cloneType(type); + + assert(specializedTypes.parameterTypes.length === type.shared.parameters.length); + if (specializedTypes.parameterDefaultTypes) { + assert(specializedTypes.parameterDefaultTypes.length === type.shared.parameters.length); + } + + newFunction.priv.specializedTypes = specializedTypes; + return newFunction; + } + + // Creates a new function based on the parameters of another function. + export function applyParamSpecValue(type: FunctionType, paramSpecValue: FunctionType): FunctionType { + const hasPositionalOnly = paramSpecValue.shared.parameters.some((param) => isPositionOnlySeparator(param)); + const newFunction = FunctionType.cloneRemoveParamSpecArgsKwargs(TypeBase.cloneType(type), hasPositionalOnly); + const paramSpec = FunctionType.getParamSpecFromArgsKwargs(type); + assert(paramSpec !== undefined); + + // Make a shallow clone of the details. + newFunction.shared = { ...newFunction.shared }; + + newFunction.shared.typeParams = newFunction.shared.typeParams.filter((t) => !isTypeSame(t, paramSpec)); + + const prevParams = Array.from(newFunction.shared.parameters); + + newFunction.shared.parameters = [ + ...prevParams, + ...paramSpecValue.shared.parameters.map((param, index) => { + return FunctionParam.create( + param.category, + FunctionType.getParamType(paramSpecValue, index), + (param.flags & FunctionParamFlags.NameSynthesized) | FunctionParamFlags.TypeDeclared, + param.name, + FunctionType.getParamDefaultType(paramSpecValue, index), + param.defaultExpr + ); + }), + ]; + + if (newFunction.shared.docString === undefined) { + newFunction.shared.docString = paramSpecValue.shared.docString; + } + + if (newFunction.shared.deprecatedMessage === undefined) { + newFunction.shared.deprecatedMessage = paramSpecValue.shared.deprecatedMessage; + } + + const origFlagsMask = FunctionTypeFlags.Overloaded | FunctionTypeFlags.ParamSpecValue; + newFunction.shared.flags = type.shared.flags & origFlagsMask; + + const methodFlagsMask = + FunctionTypeFlags.ClassMethod | FunctionTypeFlags.StaticMethod | FunctionTypeFlags.ConstructorMethod; + + // If the original function was a method, use its method type. Otherwise + // use the method type of the param spec. + if (type.shared.methodClass) { + newFunction.shared.flags |= type.shared.flags & methodFlagsMask; + } else { + newFunction.shared.flags |= paramSpecValue.shared.flags & methodFlagsMask; + } + + // Use the "..." flag from the param spec. + newFunction.shared.flags |= paramSpecValue.shared.flags & FunctionTypeFlags.GradualCallableForm; + + // Mark the function as synthesized since there is no user-defined declaration for it. + newFunction.shared.flags |= FunctionTypeFlags.SynthesizedMethod; + if (newFunction.shared.declaration) { + newFunction.shared.declaration = undefined; + } + + // Update the specialized parameter types as well. + const specializedTypes = newFunction.priv.specializedTypes; + if (specializedTypes) { + paramSpecValue.shared.parameters.forEach((_, index) => { + specializedTypes.parameterTypes.push(FunctionType.getParamType(paramSpecValue, index)); + + if (specializedTypes.parameterDefaultTypes) { + specializedTypes.parameterDefaultTypes?.push( + FunctionType.getParamDefaultType(paramSpecValue, index) + ); + } + }); + } + + newFunction.priv.constructorTypeVarScopeId = paramSpecValue.priv.constructorTypeVarScopeId; + + if (!newFunction.shared.methodClass && paramSpecValue.shared.methodClass) { + newFunction.shared.methodClass = paramSpecValue.shared.methodClass; + } + + return newFunction; + } + + export function cloneWithNewFlags(type: FunctionType, flags: FunctionTypeFlags): FunctionType { + const newFunction = TypeBase.cloneType(type); + + // Make a shallow clone of the details. + newFunction.shared = { ...type.shared }; + newFunction.shared.flags = flags; + + return newFunction; + } + + export function cloneWithNewTypeVarScopeId( + type: FunctionType, + newScopeId: TypeVarScopeId | undefined, + newConstructorScopeId: TypeVarScopeId | undefined, + typeParams: TypeVarType[] + ): FunctionType { + const newFunction = TypeBase.cloneType(type); + + // Make a shallow clone of the details. + newFunction.shared = { ...type.shared }; + newFunction.shared.typeVarScopeId = newScopeId; + newFunction.priv.constructorTypeVarScopeId = newConstructorScopeId; + newFunction.shared.typeParams = typeParams; + + return newFunction; + } + + export function cloneWithDocString(type: FunctionType, docString?: string): FunctionType { + const newFunction = TypeBase.cloneType(type); + + // Make a shallow clone of the details. + newFunction.shared = { ...type.shared }; + + newFunction.shared.docString = docString; + + return newFunction; + } + + export function cloneWithDeprecatedMessage(type: FunctionType, deprecatedMessage?: string): FunctionType { + const newFunction = TypeBase.cloneType(type); + + // Make a shallow clone of the details. + newFunction.shared = { ...type.shared }; + + newFunction.shared.deprecatedMessage = deprecatedMessage; + + return newFunction; + } + + // If the function ends with "*args: P.args, **kwargs: P.kwargs", this function + // returns a new function that is a clone of the input function with the + // *args and **kwargs parameters removed. If stripPositionOnlySeparator is true, + // a trailing positional-only separator will be removed. + export function cloneRemoveParamSpecArgsKwargs( + type: FunctionType, + stripPositionOnlySeparator = false + ): FunctionType { + const paramCount = type.shared.parameters.length; + if (paramCount < 2) { + return type; + } + + const argsParam = type.shared.parameters[paramCount - 2]; + const kwargsParam = type.shared.parameters[paramCount - 1]; + + if (argsParam.category !== ParamCategory.ArgsList || kwargsParam.category !== ParamCategory.KwargsDict) { + return type; + } + + const argsType = FunctionType.getParamType(type, paramCount - 2); + const kwargsType = FunctionType.getParamType(type, paramCount - 1); + if (!isParamSpec(argsType) || !isParamSpec(kwargsType) || !isTypeSame(argsType, kwargsType)) { + return type; + } + + const newFunction = TypeBase.cloneType(type); + + // Make a shallow clone of the details. + newFunction.shared = { ...type.shared }; + const details = newFunction.shared; + + let paramsToDrop = 2; + + // If the last remaining parameter is a position-only separator, remove it as well. + // Always remove it if it's the only remaining parameter. + if (paramCount >= 3 && isPositionOnlySeparator(details.parameters[paramCount - 3])) { + if (paramCount === 3 || stripPositionOnlySeparator) { + paramsToDrop = 3; + } + } + + // Remove the last parameters, which are the *args and **kwargs. + details.parameters = details.parameters.slice(0, details.parameters.length - paramsToDrop); + + if (type.priv.specializedTypes) { + newFunction.priv.specializedTypes = { ...type.priv.specializedTypes }; + newFunction.priv.specializedTypes.parameterTypes = newFunction.priv.specializedTypes.parameterTypes.slice( + 0, + newFunction.priv.specializedTypes.parameterTypes.length - paramsToDrop + ); + if (newFunction.priv.specializedTypes.parameterDefaultTypes) { + newFunction.priv.specializedTypes.parameterDefaultTypes = + newFunction.priv.specializedTypes.parameterDefaultTypes.slice( + 0, + newFunction.priv.specializedTypes.parameterDefaultTypes.length - paramsToDrop + ); + } + } + + if (type.shared.inferredReturnType) { + newFunction.shared.inferredReturnType = type.shared.inferredReturnType; + } + + return newFunction; + } + + // If the function ends with "*args: P.args, **kwargs: P.kwargs", this function + // returns P. Otherwise, it returns undefined. + export function getParamSpecFromArgsKwargs(type: FunctionType): ParamSpecType | undefined { + const params = type.shared.parameters; + if (params.length < 2) { + return undefined; + } + + const secondLastParam = params[params.length - 2]; + const secondLastParamType = FunctionType.getParamType(type, params.length - 2); + const lastParam = params[params.length - 1]; + const lastParamType = FunctionType.getParamType(type, params.length - 1); + + if ( + secondLastParam.category === ParamCategory.ArgsList && + isParamSpec(secondLastParamType) && + secondLastParamType.priv.paramSpecAccess === 'args' && + lastParam.category === ParamCategory.KwargsDict && + isParamSpec(lastParamType) && + lastParamType.priv.paramSpecAccess === 'kwargs' + ) { + return TypeVarType.cloneForParamSpecAccess(secondLastParamType, /* access */ undefined); + } + + return undefined; + } + + export function addParamSpecVariadics(type: FunctionType, paramSpec: ParamSpecType) { + FunctionType.addParam( + type, + FunctionParam.create( + ParamCategory.ArgsList, + TypeVarType.cloneForParamSpecAccess(paramSpec, 'args'), + FunctionParamFlags.TypeDeclared, + 'args' + ) + ); + + FunctionType.addParam( + type, + FunctionParam.create( + ParamCategory.KwargsDict, + TypeVarType.cloneForParamSpecAccess(paramSpec, 'kwargs'), + FunctionParamFlags.TypeDeclared, + 'kwargs' + ) + ); + } + + export function addDefaultParams(type: FunctionType, useUnknown = false) { + getDefaultParams(useUnknown).forEach((param) => { + FunctionType.addParam(type, param); + }); + } + + export function getDefaultParams(useUnknown = false): FunctionParam[] { + return [ + FunctionParam.create( + ParamCategory.ArgsList, + useUnknown ? UnknownType.create() : AnyType.create(), + useUnknown ? FunctionParamFlags.None : FunctionParamFlags.TypeDeclared, + 'args' + ), + FunctionParam.create( + ParamCategory.KwargsDict, + useUnknown ? UnknownType.create() : AnyType.create(), + useUnknown ? FunctionParamFlags.None : FunctionParamFlags.TypeDeclared, + 'kwargs' + ), + ]; + } + + // Indicates whether the input signature consists of (*args: Any, **kwargs: Any). + export function hasDefaultParams(functionType: FunctionType): boolean { + let sawArgs = false; + let sawKwargs = false; + + for (let i = 0; i < functionType.shared.parameters.length; i++) { + const param = functionType.shared.parameters[i]; + + // Ignore nameless separator parameters. + if (!param.name) { + continue; + } + + if (param.category === ParamCategory.Simple) { + return false; + } else if (param.category === ParamCategory.ArgsList) { + sawArgs = true; + } else if (param.category === ParamCategory.KwargsDict) { + sawKwargs = true; + } + + if (!isAnyOrUnknown(FunctionType.getParamType(functionType, i))) { + return false; + } + } + + return sawArgs && sawKwargs; + } + + export function isInstanceMethod(type: FunctionType): boolean { + return ( + (type.shared.flags & + (FunctionTypeFlags.ConstructorMethod | + FunctionTypeFlags.StaticMethod | + FunctionTypeFlags.ClassMethod)) === + 0 + ); + } + + export function isConstructorMethod(type: FunctionType): boolean { + return (type.shared.flags & FunctionTypeFlags.ConstructorMethod) !== 0; + } + + export function isStaticMethod(type: FunctionType): boolean { + return (type.shared.flags & FunctionTypeFlags.StaticMethod) !== 0; + } + + export function isClassMethod(type: FunctionType): boolean { + return (type.shared.flags & FunctionTypeFlags.ClassMethod) !== 0; + } + + export function isAbstractMethod(type: FunctionType): boolean { + return (type.shared.flags & FunctionTypeFlags.AbstractMethod) !== 0; + } + + export function isGenerator(type: FunctionType): boolean { + return (type.shared.flags & FunctionTypeFlags.Generator) !== 0; + } + + export function isSynthesizedMethod(type: FunctionType): boolean { + return (type.shared.flags & FunctionTypeFlags.SynthesizedMethod) !== 0; + } + + export function isTypeCheckOnly(type: FunctionType): boolean { + return (type.shared.flags & FunctionTypeFlags.TypeCheckOnly) !== 0; + } + + export function isOverloaded(type: FunctionType): boolean { + return (type.shared.flags & FunctionTypeFlags.Overloaded) !== 0; + } + + export function isDefaultParamCheckDisabled(type: FunctionType) { + return (type.shared.flags & FunctionTypeFlags.DisableDefaultChecks) !== 0; + } + + export function isAsync(type: FunctionType) { + return (type.shared.flags & FunctionTypeFlags.Async) !== 0; + } + + export function isStubDefinition(type: FunctionType) { + return (type.shared.flags & FunctionTypeFlags.StubDefinition) !== 0; + } + + export function isPyTypedDefinition(type: FunctionType) { + return (type.shared.flags & FunctionTypeFlags.PyTypedDefinition) !== 0; + } + + export function isFinal(type: FunctionType) { + return (type.shared.flags & FunctionTypeFlags.Final) !== 0; + } + + export function hasUnannotatedParams(type: FunctionType) { + return (type.shared.flags & FunctionTypeFlags.UnannotatedParams) !== 0; + } + + export function isGradualCallableForm(type: FunctionType) { + return (type.shared.flags & FunctionTypeFlags.GradualCallableForm) !== 0; + } + + export function isParamSpecValue(type: FunctionType) { + return (type.shared.flags & FunctionTypeFlags.ParamSpecValue) !== 0; + } + + export function isPartiallyEvaluated(type: FunctionType) { + return !!(type.shared.flags & FunctionTypeFlags.PartiallyEvaluated); + } + + export function isOverridden(type: FunctionType) { + return !!(type.shared.flags & FunctionTypeFlags.Overridden); + } + + export function isBuiltIn(type: FunctionType, name?: string | string[]) { + if (!(type.shared.flags & FunctionTypeFlags.BuiltIn)) { + return false; + } + + if (name !== undefined) { + const functionArray = Array.isArray(name) ? name : [name]; + return functionArray.some((name) => name === type.shared.name || name === type.shared.fullName); + } + + return true; + } + + export function getDeclaredParamType(type: FunctionType, index: number): Type { + return type.shared.parameters[index]._type; + } + + export function getParamType(type: FunctionType, index: number): Type { + assert(index < type.shared.parameters.length, 'Parameter types array overflow'); + + if (type.priv.specializedTypes && index < type.priv.specializedTypes.parameterTypes.length) { + return type.priv.specializedTypes.parameterTypes[index]; + } + + return type.shared.parameters[index]._type; + } + + export function getParamDefaultType(type: FunctionType, index: number): Type | undefined { + assert(index < type.shared.parameters.length, 'Parameter types array overflow'); + + if ( + type.priv.specializedTypes?.parameterDefaultTypes && + index < type.priv.specializedTypes.parameterDefaultTypes.length + ) { + const defaultArgType = type.priv.specializedTypes.parameterDefaultTypes[index]; + if (defaultArgType) { + return defaultArgType; + } + } + + return type.shared.parameters[index]._defaultType; + } + + export function addParam(type: FunctionType, param: FunctionParam) { + type.shared.parameters.push(param); + + if (type.priv.specializedTypes) { + type.priv.specializedTypes.parameterTypes.push(param._type); + } + } + + export function addPositionOnlyParamSeparator(type: FunctionType) { + addParam(type, FunctionParam.create(ParamCategory.Simple, AnyType.create())); + } + + export function addKeywordOnlyParamSeparator(type: FunctionType) { + addParam(type, FunctionParam.create(ParamCategory.ArgsList, AnyType.create())); + } + + export function getEffectiveReturnType(type: FunctionType, includeInferred = true): Type | undefined { + if (type.priv.specializedTypes?.returnType) { + return type.priv.specializedTypes.returnType; + } + + if (type.shared.declaredReturnType) { + return type.shared.declaredReturnType; + } + + if (includeInferred) { + return type.shared.inferredReturnType?.type; + } + + return undefined; + } +} + +export interface OverloadedDetailsPriv { + // eslint-disable-next-line @typescript-eslint/naming-convention + _overloads: FunctionType[]; + + // eslint-disable-next-line @typescript-eslint/naming-convention + _implementation: Type | undefined; +} + +export interface OverloadedType extends TypeBase { + priv: OverloadedDetailsPriv; +} + +export namespace OverloadedType { + export function create(overloads: FunctionType[], implementation?: Type): OverloadedType { + const newType: OverloadedType = { + category: TypeCategory.Overloaded, + flags: TypeFlags.Instance, + props: undefined, + cached: undefined, + shared: undefined, + priv: { + _overloads: [], + _implementation: implementation, + }, + }; + + overloads.forEach((overload) => { + OverloadedType.addOverload(newType, overload); + }); + + if (implementation && isFunction(implementation)) { + implementation.priv.overloaded = newType; + } + + return newType; + } + + // Adds a new overload or an implementation. + export function addOverload(type: OverloadedType, functionType: FunctionType) { + functionType.priv.overloaded = type; + type.priv._overloads.push(functionType); + } + + export function getOverloads(type: OverloadedType): FunctionType[] { + return type.priv._overloads; + } + + export function getImplementation(type: OverloadedType): Type | undefined { + return type.priv._implementation; + } +} + +export interface NeverDetailsPriv { + isNoReturn: boolean; +} + +export interface NeverType extends TypeBase { + priv: NeverDetailsPriv; +} + +export namespace NeverType { + const _neverInstance: NeverType = { + category: TypeCategory.Never, + flags: TypeFlags.Instance | TypeFlags.Instantiable, + props: undefined, + cached: undefined, + shared: undefined, + priv: { isNoReturn: false }, + }; + + const _noReturnInstance: NeverType = { + category: TypeCategory.Never, + flags: TypeFlags.Instance | TypeFlags.Instantiable, + props: undefined, + cached: undefined, + shared: undefined, + priv: { isNoReturn: true }, + }; + + export function createNever() { + return _neverInstance; + } + + export function createNoReturn() { + return _noReturnInstance; + } + + export function convertToInstance(type: NeverType): NeverType { + // Remove the specialForm or typeForm if present. Otherwise return the existing type. + if (!type.props?.specialForm && !type.props?.typeForm) { + return type; + } + + return type.priv.isNoReturn ? NeverType.createNoReturn() : NeverType.createNever(); + } +} + +export interface AnyDetailsPriv { + isEllipsis: boolean; +} + +export interface AnyType extends TypeBase { + priv: AnyDetailsPriv; +} + +export namespace AnyType { + const _anyInstanceSpecialForm: AnyType = { + category: TypeCategory.Any, + flags: TypeFlags.Instance | TypeFlags.Instantiable, + props: undefined, + cached: undefined, + shared: undefined, + priv: { isEllipsis: false }, + }; + + const _anyInstance: AnyType = { + category: TypeCategory.Any, + flags: TypeFlags.Instance | TypeFlags.Instantiable, + props: undefined, + cached: undefined, + shared: undefined, + priv: { isEllipsis: false }, + }; + + const _ellipsisInstance: AnyType = { + category: TypeCategory.Any, + flags: TypeFlags.Instance | TypeFlags.Instantiable, + props: undefined, + cached: undefined, + shared: undefined, + priv: { isEllipsis: true }, + }; + + export function create(isEllipsis = false) { + return isEllipsis ? _ellipsisInstance : _anyInstance; + } + + export function createSpecialForm() { + return _anyInstanceSpecialForm; + } +} + +export namespace AnyType { + export function convertToInstance(type: AnyType): AnyType { + // Remove the "special form" if present. Otherwise return the existing type. + return type.props?.specialForm ? AnyType.create() : type; + } +} + +// References a single condition associated with a constrained TypeVar. +export interface TypeCondition { + typeVar: TypeVarType; + constraintIndex: number; +} + +export namespace TypeCondition { + export function combine( + conditions1: TypeCondition[] | undefined, + conditions2: TypeCondition[] | undefined + ): TypeCondition[] | undefined { + if (!conditions1) { + return conditions2; + } + + if (!conditions2) { + return conditions1; + } + + // Deduplicate the lists. + const combined = Array.from(conditions1); + conditions2.forEach((c1) => { + if (!combined.some((c2) => _compare(c1, c2) === 0)) { + combined.push(c1); + } + }); + + // Always keep the conditions sorted for easier comparison. + return combined.sort(_compare); + } + + function _compare(c1: TypeCondition, c2: TypeCondition) { + if (c1.typeVar.shared.name < c2.typeVar.shared.name) { + return -1; + } else if (c1.typeVar.shared.name > c2.typeVar.shared.name) { + return 1; + } + if (c1.constraintIndex < c2.constraintIndex) { + return -1; + } else if (c1.constraintIndex > c2.constraintIndex) { + return 1; + } + return 0; + } + + export function isSame( + conditions1: TypeCondition[] | undefined, + conditions2: TypeCondition[] | undefined + ): boolean { + if (!conditions1) { + return !conditions2; + } + + if (!conditions2 || conditions1.length !== conditions2.length) { + return false; + } + + return ( + conditions1.find( + (c1, index) => + c1.typeVar.priv.nameWithScope !== conditions2[index].typeVar.priv.nameWithScope || + c1.constraintIndex !== conditions2[index].constraintIndex + ) === undefined + ); + } + + // Determines if the two conditions can be used at the same time. If + // one constraint list contains a constraint for a type variable, and the + // same constraint is not in the other constraint list, the two are considered + // incompatible. + export function isCompatible( + conditions1: TypeCondition[] | undefined, + conditions2: TypeCondition[] | undefined + ): boolean { + if (!conditions1 || !conditions2) { + return true; + } + + for (const c1 of conditions1) { + let foundTypeVarMatch = false; + const exactMatch = conditions2.find((c2) => { + if (c1.typeVar.priv.nameWithScope === c2.typeVar.priv.nameWithScope) { + foundTypeVarMatch = true; + return c1.constraintIndex === c2.constraintIndex; + } + return false; + }); + + if (foundTypeVarMatch && !exactMatch) { + return false; + } + } + + return true; + } +} + +export interface LiteralTypes { + literalStrMap: Map | undefined; + literalIntMap: Map | undefined; + literalEnumMap: Map | undefined; +} + +export interface UnionDetailsPriv { + subtypes: UnionableType[]; + literalInstances: LiteralTypes; + literalClasses: LiteralTypes; + typeAliasSources: Set | undefined; + includesRecursiveTypeAlias: boolean; +} + +export interface UnionType extends TypeBase { + priv: UnionDetailsPriv; +} + +export namespace UnionType { + export function create() { + const newUnionType: UnionType = { + category: TypeCategory.Union, + flags: TypeFlags.Instance | TypeFlags.Instantiable, + props: undefined, + cached: undefined, + shared: undefined, + priv: { + subtypes: [], + literalInstances: { + literalStrMap: undefined, + literalIntMap: undefined, + literalEnumMap: undefined, + }, + literalClasses: { + literalStrMap: undefined, + literalIntMap: undefined, + literalEnumMap: undefined, + }, + typeAliasSources: undefined, + includesRecursiveTypeAlias: false, + }, + }; + + return newUnionType; + } + + export function addType(unionType: UnionType, newType: UnionableType) { + // If we're adding a string, integer or enum literal, add it to the + // corresponding literal map to speed up some operations. It's not + // uncommon for unions to contain hundreds of literals. + if (isClass(newType) && newType.priv.literalValue !== undefined && !newType.props?.condition) { + const literalMaps = isClassInstance(newType) + ? unionType.priv.literalInstances + : unionType.priv.literalClasses; + + if (ClassType.isBuiltIn(newType, 'str')) { + if (literalMaps.literalStrMap === undefined) { + literalMaps.literalStrMap = new Map(); + } + literalMaps.literalStrMap.set(newType.priv.literalValue as string, newType); + } else if (ClassType.isBuiltIn(newType, 'int')) { + if (literalMaps.literalIntMap === undefined) { + literalMaps.literalIntMap = new Map(); + } + literalMaps.literalIntMap.set(newType.priv.literalValue as number | bigint, newType); + } else if (ClassType.isEnumClass(newType)) { + if (literalMaps.literalEnumMap === undefined) { + literalMaps.literalEnumMap = new Map(); + } + const enumLiteral = newType.priv.literalValue as EnumLiteral; + literalMaps.literalEnumMap.set(enumLiteral.getName(), newType); + } + } + + unionType.flags &= newType.flags; + unionType.priv.subtypes.push(newType); + + if (isTypeVar(newType) && newType.shared.recursiveAlias?.name) { + // Note that at least one recursive type alias was included in + // this union. We'll need to expand it before the union is used. + unionType.priv.includesRecursiveTypeAlias = true; + } + } + + // Determines whether the union contains a specified subtype. If exclusionSet is passed, + // the method skips any subtype indexes that are in the set and adds a found index to + // the exclusion set. This speeds up union type comparisons. + export function containsType( + unionType: UnionType, + subtype: Type, + options: TypeSameOptions = {}, + exclusionSet?: Set, + recursionCount = 0 + ): boolean { + // Handle string literals as a special case because unions can sometimes + // contain hundreds of string literal types. + if (isClass(subtype) && subtype.props?.condition === undefined && subtype.priv.literalValue !== undefined) { + const literalMaps = isClassInstance(subtype) + ? unionType.priv.literalInstances + : unionType.priv.literalClasses; + + if (ClassType.isBuiltIn(subtype, 'str') && literalMaps.literalStrMap !== undefined) { + return literalMaps.literalStrMap.has(subtype.priv.literalValue as string); + } else if (ClassType.isBuiltIn(subtype, 'int') && literalMaps.literalIntMap !== undefined) { + return literalMaps.literalIntMap.has(subtype.priv.literalValue as number | bigint); + } else if (ClassType.isEnumClass(subtype) && literalMaps.literalEnumMap !== undefined) { + const enumLiteral = subtype.priv.literalValue as EnumLiteral; + return literalMaps.literalEnumMap.has(enumLiteral.getName()); + } + } + + const foundIndex = unionType.priv.subtypes.findIndex((t, i) => { + if (exclusionSet?.has(i)) { + return false; + } + + return isTypeSame(t, subtype, options, recursionCount); + }); + + if (foundIndex < 0) { + return false; + } + + exclusionSet?.add(foundIndex); + return true; + } + + export function addTypeAliasSource(unionType: UnionType, typeAliasSource: Type) { + if (typeAliasSource.category === TypeCategory.Union) { + const sourcesToAdd = typeAliasSource.props?.typeAliasInfo + ? [typeAliasSource] + : typeAliasSource.priv.typeAliasSources; + + if (sourcesToAdd) { + if (!unionType.priv.typeAliasSources) { + unionType.priv.typeAliasSources = new Set(); + } + + sourcesToAdd.forEach((source) => { + unionType.priv.typeAliasSources!.add(source); + }); + } + } + } +} + +export const enum Variance { + Auto, + Unknown, + Invariant, + Covariant, + Contravariant, +} + +export interface RecursiveAliasInfo { + // Used for recursive type aliases. + name: string; + scopeId: TypeVarScopeId; + isPep695Syntax: boolean; + + // Type parameters for a recursive type alias. + typeParams: TypeVarType[] | undefined; +} + +export enum TypeVarKind { + TypeVar, + TypeVarTuple, + ParamSpec, +} + +export interface TypeVarDetailsShared { + kind: TypeVarKind; + name: string; + constraints: Type[]; + boundType: Type | undefined; + isDefaultExplicit: boolean; + defaultType: Type; + + declaredVariance: Variance; + + // Internally created (e.g. for pseudo-generic classes) + isSynthesized: boolean; + isSynthesizedSelf: boolean; + synthesizedIndex: number | undefined; + isExemptFromBoundCheck: boolean; + + // Does this type variable originate from PEP 695 type parameter syntax? + isTypeParamSyntax: boolean; + + // Information about recursive type aliases. + recursiveAlias: TypeAliasSharedInfo | undefined; +} + +export type ParamSpecAccess = 'args' | 'kwargs'; + +export const enum TypeVarScopeType { + Class, + Function, + TypeAlias, +} + +export interface TypeVarDetailsPriv { + // An ID that uniquely identifies the scope to which this TypeVar is bound + scopeId?: TypeVarScopeId | undefined; + + // A human-readable name of the function, class, or type alias that + // provides the scope to which this type variable is bound. Unlike the + // scopeId, this might not be unique, so it should be used only for error + // messages. + scopeName?: string | undefined; + + // If the TypeVar is bound to a scope, this is the scope type + scopeType?: TypeVarScopeType; + + // String formatted as . + nameWithScope?: string | undefined; + + // May be different from declaredVariance if declared as Auto + computedVariance?: Variance; + + // When a TypeVar appears within an expected type during bidirectional + // type inference, it needs to be solved along with the in-scope TypeVars. + // This is done by cloning the TypeVar and making it a "unification" + // variable. + isUnificationVar?: boolean; + + // If the TypeVar is bound form of a TypeVar, this refers to + // the corresponding free TypeVar. + freeTypeVar?: TypeVarType | undefined; + + // Is this TypeVar or TypeVarTuple unpacked (i.e. Unpack or * operator applied)? + isUnpacked?: boolean | undefined; +} + +export interface TypeVarType extends TypeBase { + shared: TypeVarDetailsShared; + priv: TypeVarDetailsPriv; +} + +export interface ParamSpecDetailsPriv extends TypeVarDetailsPriv { + // Represents access to "args" or "kwargs" of a ParamSpec + paramSpecAccess?: ParamSpecAccess; + + freeTypeVar?: ParamSpecType | undefined; +} + +export interface ParamSpecType extends TypeVarType { + shared: TypeVarDetailsShared & { kind: TypeVarKind.ParamSpec }; + priv: ParamSpecDetailsPriv; +} + +export namespace ParamSpecType { + // Returns the "Unknown" equivalent for a ParamSpec. + export function getUnknown(): FunctionType { + const newFunction = FunctionType.createInstance( + '', + '', + '', + FunctionTypeFlags.ParamSpecValue | FunctionTypeFlags.GradualCallableForm + ); + FunctionType.addDefaultParams(newFunction); + return newFunction; + } +} + +export interface TypeVarTupleDetailsPriv extends TypeVarDetailsPriv { + // Is this TypeVarTuple included in a Union[]? This allows us to + // differentiate between Unpack[Vs] and Union[Unpack[Vs]]. + isInUnion?: boolean | undefined; + + freeTypeVar?: TypeVarTupleType | undefined; +} + +export interface TypeVarTupleType extends TypeVarType { + shared: TypeVarDetailsShared & { kind: TypeVarKind.TypeVarTuple }; + priv: TypeVarTupleDetailsPriv; +} + +export namespace TypeVarType { + export function createInstance(name: string, kind: TypeVarKind = TypeVarKind.TypeVar) { + return create(name, kind, TypeFlags.Instance); + } + + export function createInstantiable(name: string, kind: TypeVarKind = TypeVarKind.TypeVar) { + return create(name, kind, TypeFlags.Instantiable); + } + + export function cloneAsInstance(type: TypeVarType): TypeVarType { + assert(TypeBase.isInstantiable(type)); + + if (type.cached?.typeBaseInstanceType) { + return type.cached.typeBaseInstanceType as TypeVarType; + } + + const newInstance = TypeBase.cloneTypeAsInstance(type, /* cache */ true); + if (newInstance.props?.specialForm) { + TypeBase.setSpecialForm(newInstance, undefined); + } + + if (newInstance.priv.freeTypeVar) { + newInstance.priv.freeTypeVar = TypeVarType.cloneAsInstance(newInstance.priv.freeTypeVar); + } + + return newInstance; + } + + export function cloneAsInstantiable(type: TypeVarType): TypeVarType { + if (type.cached?.typeBaseInstantiableType) { + return type.cached.typeBaseInstantiableType as TypeVarType; + } + + const newInstance = TypeBase.cloneTypeAsInstantiable(type, /* cache */ true); + + if (newInstance.priv.freeTypeVar) { + newInstance.priv.freeTypeVar = TypeVarType.cloneAsInstantiable(newInstance.priv.freeTypeVar); + } + + return newInstance; + } + + export function cloneForNewName(type: TypeVarType, name: string): TypeVarType { + const newInstance = TypeBase.cloneType(type); + newInstance.shared = { ...type.shared }; + newInstance.shared.name = name; + + if (newInstance.priv.scopeId) { + newInstance.priv.nameWithScope = makeNameWithScope( + name, + newInstance.priv.scopeId, + newInstance.priv.scopeName ?? '' + ); + } + + return newInstance; + } + + export function cloneForScopeId( + type: TypeVarType, + scopeId: string, + scopeName: string | undefined, + scopeType: TypeVarScopeType | undefined + ): TypeVarType { + const newInstance = TypeBase.cloneType(type); + newInstance.priv.nameWithScope = makeNameWithScope(type.shared.name, scopeId, scopeName ?? ''); + newInstance.priv.scopeId = scopeId; + newInstance.priv.scopeName = scopeName; + newInstance.priv.scopeType = scopeType; + return newInstance; + } + + export function cloneForUnpacked(type: TypeVarType, isInUnion = false) { + const newInstance = TypeBase.cloneType(type); + newInstance.priv.isUnpacked = true; + + if (isTypeVarTuple(newInstance) && isInUnion) { + newInstance.priv.isInUnion = isInUnion; + } + + if (newInstance.priv.freeTypeVar) { + newInstance.priv.freeTypeVar = TypeVarType.cloneForUnpacked(newInstance.priv.freeTypeVar, isInUnion); + } + return newInstance; + } + + export function cloneForPacked(type: TypeVarType) { + const newInstance = TypeBase.cloneType(type); + newInstance.priv.isUnpacked = false; + + if (isTypeVarTuple(newInstance)) { + newInstance.priv.isInUnion = false; + } + + if (newInstance.priv.freeTypeVar) { + newInstance.priv.freeTypeVar = TypeVarType.cloneForPacked(newInstance.priv.freeTypeVar); + } + return newInstance; + } + + // Creates a "simplified" version of the TypeVar with invariance + // and no bound or constraints. ParamSpecs and TypeVarTuples are left + // unmodified. So are auto-variant type variables. + export function cloneAsInvariant(type: TypeVarType): TypeVarType { + if (isParamSpec(type) || isTypeVarTuple(type)) { + return type; + } + + if (type.shared.declaredVariance === Variance.Auto) { + return type; + } + + if (type.shared.declaredVariance === Variance.Invariant) { + if (!TypeVarType.hasBound(type) && !TypeVarType.hasConstraints(type)) { + return type; + } + } + + const newInstance = TypeBase.cloneType(type); + newInstance.shared = { ...newInstance.shared }; + newInstance.shared.declaredVariance = Variance.Invariant; + newInstance.shared.boundType = undefined; + newInstance.shared.constraints = []; + return newInstance; + } + + export function cloneForParamSpecAccess(type: ParamSpecType, access: ParamSpecAccess | undefined): ParamSpecType { + const newInstance = TypeBase.cloneType(type); + newInstance.priv.paramSpecAccess = access; + return newInstance; + } + + export function cloneAsSpecializedSelf(type: TypeVarType, specializedBoundType: Type): TypeVarType { + assert(TypeVarType.isSelf(type)); + const newInstance = TypeBase.cloneType(type); + newInstance.shared = { ...newInstance.shared }; + newInstance.shared.boundType = specializedBoundType; + return newInstance; + } + + export function cloneAsUnificationVar(type: TypeVarType, usageOffset?: number): TypeVarType { + if (TypeVarType.isUnification(type)) { + return type; + } + + // If the caller specified a usage offset, append it to the TypeVar + // internal name. This allows us to distinguish it from other uses + // of the same TypeVar. For example nested calls to a generic + // function like `foo(foo(1))`. + let newNameWithScope = type.priv.nameWithScope; + if (usageOffset) { + newNameWithScope = `${type.priv.nameWithScope}-${usageOffset}`; + } + + const newInstance = TypeBase.cloneType(type); + newInstance.priv.isUnificationVar = true; + newInstance.priv.scopeId = UnificationScopeId; + newInstance.priv.nameWithScope = newNameWithScope; + return newInstance; + } + + export function cloneWithComputedVariance(type: TypeVarType, computedVariance: Variance): TypeVarType { + const newInstance = TypeBase.cloneType(type); + newInstance.priv.computedVariance = computedVariance; + return newInstance; + } + + export function makeNameWithScope(name: string, scopeId: string, scopeName: string) { + // We include the scopeName here even though it's normally already part + // of the scopeId. There are cases where it can diverge, specifically + // in scenarios involving higher-order functions that return generic + // callable types. See adjustCallableReturnType for details. + return `${name}.${scopeId}.${scopeName}`; + } + + // When solving the TypeVars for a callable, we need to distinguish between + // the externally-visible "free" type vars and the internal "bound" type vars. + // The distinction is important for recursive calls (e.g. calling a constructor + // for a generic class within the class implementation). + export function makeBoundScopeId(scopeId: TypeVarScopeId): TypeVarScopeId; + export function makeBoundScopeId(scopeId: TypeVarScopeId | undefined): TypeVarScopeId | undefined; + export function makeBoundScopeId(scopeId: TypeVarScopeId | undefined): TypeVarScopeId | undefined { + if (!scopeId) { + return undefined; + } + + // Append an asterisk to denote a bound scope. + return `${scopeId}*`; + } + + export function cloneAsBound(type: TypeVarType): TypeVarType { + if (type.priv.scopeId === undefined || type.priv.freeTypeVar) { + return type; + } + + const clone = TypeVarType.cloneForScopeId( + type, + TypeVarType.makeBoundScopeId(type.priv.scopeId), + type.priv.scopeName, + type.priv.scopeType + ); + + clone.priv.freeTypeVar = type; + + return clone; + } + + // Indicates that the type var is a "free" or unbound type var. Free + // type variables can be solved whereas bound type vars are already bound + // to a value. + export function isBound(type: TypeVarType) { + // If the type var has an associated free type var, then it's + // considered bound. If it has no associated free var, then it's + // considered free. + return !!type.priv.freeTypeVar; + } + + export function isUnification(type: TypeVarType) { + return type.priv.isUnificationVar; + } + + function create(name: string, kind: TypeVarKind, typeFlags: TypeFlags): TypeVarType { + const newTypeVarType: TypeVarType = { + category: TypeCategory.TypeVar, + flags: typeFlags, + props: undefined, + cached: undefined, + shared: { + kind, + name, + constraints: [], + boundType: undefined, + isDefaultExplicit: false, + defaultType: UnknownType.create(), + declaredVariance: Variance.Invariant, + isSynthesized: false, + isSynthesizedSelf: false, + synthesizedIndex: undefined, + isExemptFromBoundCheck: false, + isTypeParamSyntax: false, + recursiveAlias: undefined, + }, + priv: {}, + }; + return newTypeVarType; + } + + export function addConstraint(type: TypeVarType, constraintType: Type) { + type.shared.constraints.push(constraintType); + } + + export function getNameWithScope(typeVarType: TypeVarType) { + // If there is no name with scope, fall back on the (unscoped) name. + return typeVarType.priv.nameWithScope || typeVarType.shared.name; + } + + export function getReadableName(type: TypeVarType, includeScope = true) { + if (type.priv.scopeName && includeScope) { + return `${type.shared.name}@${type.priv.scopeName}`; + } + + return type.shared.name; + } + + export function getVariance(type: TypeVarType) { + const variance = type.priv.computedVariance ?? type.shared.declaredVariance; + + // By this point, the variance should have been inferred. + assert(variance !== Variance.Auto, 'Expected variance to be inferred'); + + // If we're in the process of computing variance, it will still be + // unknown. Default to covariant in this case. + if (variance === Variance.Unknown) { + return Variance.Covariant; + } + + return variance; + } + + // Indicates whether the specified type is a recursive type alias + // placeholder that has not yet been resolved. + export function isTypeAliasPlaceholder(type: TypeVarType) { + return !!type.shared.recursiveAlias && !type.shared.boundType; + } + + export function isSelf(type: TypeVarType) { + return !!type.shared.isSynthesizedSelf; + } + + export function hasConstraints(type: TypeVarType) { + return type.shared.constraints.length > 0; + } + + export function hasBound(type: TypeVarType) { + return !!type.shared.boundType; + } +} + +export function isNever(type: Type): type is NeverType { + return type.category === TypeCategory.Never; +} + +// Viper 扩展:检测类型是否为 t.CDefine(编译时元指令类)。 +// 用于 combineTypes 中过滤联合类型里的 CDefine,实现 t.CDefine | T = T 语义。 +// 识别条件:ClassType 实例,类名为 'CDefine',定义在 t 模块中。 +export function isCDefineType(type: Type): boolean { + if (!isClass(type)) { + return false; + } + // 通过类名识别(CDefine 在 vipershed-fallback/stdlib/t.vpi 中定义) + return type.shared.name === 'CDefine' || type.shared.fullName === 't.CDefine'; +} + +// Viper 扩展:检测类型是否为 t.CArray(栈上固定数组类型)。 +// 用于 assignType 中实现 CArray 到 str/bytes 的自动降级 +// (C 字符数组 t.CArray[t.CChar, N] 可自动转换为 C 字符串 str)。 +export function isCArrayType(type: Type): boolean { + if (!isClass(type)) { + return false; + } + return type.shared.name === 'CArray' || type.shared.fullName === 't.CArray'; +} + +// Viper 扩展:检测类型是否为 t.CPtr 类(指针类型)。 +// 包括 CPtr 及其子类(CNeedPtr, CAutoPtr, CArrayPtr)。 +export function isCPtrType(type: Type): boolean { + if (!isClass(type)) { + return false; + } + const name = type.shared.name; + const fullName = type.shared.fullName; + return ( + name === 'CPtr' || fullName === 't.CPtr' || + name === 'CNeedPtr' || fullName === 't.CNeedPtr' || + name === 'CAutoPtr' || fullName === 't.CAutoPtr' || + name === 'CArrayPtr' || fullName === 't.CArrayPtr' + ); +} + +// Viper 扩展:计算类型的指针级数(仅对 CPtr 实例类型有效)。 +// 语义: +// - 非 CPtr 实例(值类型/整数/CVoid 等)→ 0 级 +// - CPtr 实例(无 typeArgs 或 typeArgs[0] 非 CPtr)→ 1 级(如 CPtr、CPtr[CInt]) +// - CPtr[CPtr[...]] → 递归 +1(如 CPtr[CPtr[CInt]] 为 2 级,即 int**) +// 用于 assignType 中的跨级指针隐式转换检查:禁止 src 级数 > dest 最大级数。 +// 注意:仅处理 instance 形式(构造调用返回与赋值比较的都是 instance)。 +export function getCPtrLevel(type: Type): number { + if (!isClassInstance(type) || !isCPtrType(type)) { + return 0; + } + const typeArgs = type.priv.typeArgs; + if (!typeArgs || typeArgs.length === 0) { + return 1; + } + const inner = typeArgs[0]; + // 内层是 CPtr 实例则递归累加,否则为 1 级(CPtr[T],T 为非指针目标类型) + if (isClassInstance(inner) && isCPtrType(inner)) { + return 1 + getCPtrLevel(inner); + } + return 1; +} + +// Viper 扩展:计算类型(可能为联合类型)的最大可容纳指针级数。 +// 对联合类型(如 CVoid | CPtr)取所有子类型级数的最大值: +// - CVoid(0) | CPtr(1) → 最大 1 级(语义为 void*,一级指针) +// - CPtr[CInt](1) | CPtr[CPtr[CInt]](2) → 最大 2 级 +// 用于判断赋值目标能否容纳源的多级指针,跨级则报错。 +export function getMaxCPtrLevel(type: Type): number { + if (isUnion(type)) { + let max = 0; + for (const subtype of type.priv.subtypes) { + const lvl = getMaxCPtrLevel(subtype); + if (lvl > max) { + max = lvl; + } + } + return max; + } + return getCPtrLevel(type); +} + +// Viper 扩展:检测类型是否为 t.CVoid(void 类型)。 +// void 在 Viper 中常作为指针的目标类型(CVoid | CPtr 表示 void*)。 +export function isCVoidType(type: Type): boolean { + if (!isClass(type)) { + return false; + } + return type.shared.name === 'CVoid' || type.shared.fullName === 't.CVoid'; +} + +// Viper 扩展:检测类型是否为 t.CConst(const 限定符类型)。 +// CConst 在联合类型中用于表示 const 修饰(如 CConst | CChar | CPtr 表示 const char*)。 +export function isCConstType(type: Type): boolean { + if (!isClass(type)) { + return false; + } + return type.shared.name === 'CConst' || type.shared.fullName === 't.CConst'; +} + +// Viper 扩展:检测类型是否为 t.CStatic(static 存储类)。 +// CStatic 在联合类型中用于表示 static 修饰(如 CStatic | CUInt64T 表示 static uint64_t)。 +// TypeSet 语义下,CStatic | T 赋值给 T 变量是合法的(CStatic 只是存储类修饰,值的类型仍是 T)。 +export function isCStaticType(type: Type): boolean { + if (!isClass(type)) { + return false; + } + return type.shared.name === 'CStatic' || type.shared.fullName === 't.CStatic'; +} + +// Viper 扩展:检测类型是否为 C 修饰符/描述符(编译时元指令,非数据类型)。 +// 这些类型在联合类型中应被过滤,只保留实际数据类型进行类型检查。 +// 依据 t.py 中 position 属性:含 PREFIX 的为修饰符(存储类/类型限定符/前缀)。 +// 特例:CDefine/CPass 虽 position=BASE,但语义是编译时元指令,也归为修饰符。 +// 注意:CUnsigned/CUnsignedChar 等是数据类型(position=BASE),不是修饰符。 +// 修饰符列表:CInline, CStatic, CConst, CExtern, CVolatile, CRegister, +// CExport, CAuto, CPass, CDefine, State(State 等价 CExport | CExtern) +export function isCModifierType(type: Type): boolean { + if (!isClass(type)) { + return false; + } + const name = type.shared.name; + return ( + name === 'CInline' || + name === 'CStatic' || + name === 'CConst' || + name === 'CExtern' || + name === 'CVolatile' || + name === 'CRegister' || + name === 'CExport' || + name === 'CAuto' || + name === 'CPass' || + name === 'CDefine' || + name === 'State' + ); +} + +// Viper 扩展:检测返回类型是否包含 C 声明标记(t.State 等修饰符)。 +// 语义:t.State 等价 CExport | CExtern,作为函数返回类型(或其一部分)时表示 +// "声明性函数"(C extern 声明),函数体为 pass,隐式返回 None,不要求返回值。 +// 判断:单类型为 isCModifierType,或联合类型包含任意一个修饰符子类型。 +// 用 some 而非 every,使 BOOL | t.State 这类"返回 BOOL 的声明性函数" +// 也被识别为声明标记(w32 外部函数声明的常见形式)。 +// 用于 checker 跳过返回值检查(如 def CloseHandle() -> BOOL | t.State: pass 合法), +// 及 typeEvaluator 允许 None 赋值给包含声明标记的返回类型。 +export function isPureCModifierType(type: Type): boolean { + if (isUnion(type)) { + // 联合类型只要包含一个修饰符(如 BOOL | t.State 中的 State), + // 即视为声明性函数返回类型,允许 pass/None。 + return type.priv.subtypes.some((subtype) => isPureCModifierType(subtype)); + } + return isCModifierType(type); +} + +// Viper 扩展:检测类型是否为 t.CEnum(enum 命名类型基类)。 +// CEnum 不是 Python 的 enum.Enum,但语义上等价:继承 CEnum 的类(如 DRESULT) +// 应被标记为 EnumClass,使枚举成员访问(如 DRESULT.RES_PARERR)返回 DRESULT 类型, +// 而不是成员的声明类型。注意:不能用 ClassType.isBuiltIn 判断,因为 t.CEnum +// 来自 vipershed-fallback,没有 BuiltIn 标志。 +export function isCEnumType(type: Type): boolean { + if (!isClass(type)) { + return false; + } + return type.shared.name === 'CEnum' || type.shared.fullName === 't.CEnum'; +} + +// Viper 扩展:检测类型是否为 t.CEnum 的子类(包括 CEnum 本身)。 +// 通过检查 MRO 中是否包含 CEnum 来判断。用于枚举到 int 的隐式转换。 +export function isCEnumSubclassType(type: Type): boolean { + if (!isClass(type)) { + return false; + } + if (isCEnumType(type)) { + return true; + } + // 检查 MRO 中是否包含 CEnum + return type.shared.mro.some((mroType) => isClass(mroType) && isCEnumType(mroType)); +} + +// Viper 扩展:检测类型是否为 t.CType(C 类型基类)。 +export function isCTypeType(type: Type): boolean { + if (!isClass(type)) { + return false; + } + return type.shared.name === 'CType' || type.shared.fullName === 't.CType'; +} + +// Viper 扩展:检测类型是否为 t.CType 的子类(包括 CType 本身)。 +// 通过检查 MRO 中是否包含 CType 来判断。用于允许 int/float/str/bool 值 +// 赋给 CType 子类(如 `k: t.CInt = 1`),因为 C 类型在底层等价于 Python 内置类型。 +export function isCTypeSubclassType(type: Type): boolean { + if (!isClass(type)) { + return false; + } + if (isCTypeType(type)) { + return true; + } + return type.shared.mro.some((mroType) => isClass(mroType) && isCTypeType(mroType)); +} + +// Viper 扩展:检测类型是否为 Viper 整数类型。 +// Viper 中所有 C 整数类型(CInt, CUnsignedLong, CSizeT 等)都是 int 的 TypeAlias, +// 在类型系统中解析为内置 int 类。此函数检测是否为内置 int/bytes 类 +// (bytes 也视为整数地址,因为 Viper 中指针地址可用 bytes 表示)。 +export function isViperIntegerType(type: Type): boolean { + if (!isClassInstance(type)) { + return false; + } + if (ClassType.isBuiltIn(type, 'int')) { + return true; + } + if (isCTypeSubclassType(type) && _viperCIntegerTypeNames.has(type.shared.name)) { + return true; + } + return false; +} + +// Viper 扩展:Viper C 整数类型名称白名单。 +// 这些类型在 vipershed-fallback/stdlib/t.vpi 中定义为 CType 子类, +// 在 Viper 语义中它们是独立的 C 整数类型,构造调用应支持指针强转语义, +// 而不是走 int.__new__ 的参数类型检查。 +const _viperCIntegerTypeNames = new Set([ + // 基本整数类型 + 'CChar', 'CShort', 'CInt', 'CLong', 'CLongLong', + 'CSignedChar', 'CUnsignedChar', 'CUnsignedShort', + 'CUnsignedInt', 'CUnsignedLong', 'CUnsignedLongLong', + // 固定宽度整数 + 'CInt8T', 'CInt16T', 'CInt32T', 'CInt64T', + 'CUInt8T', 'CUInt16T', 'CUInt32T', 'CUInt64T', + // 最小/最快宽度整数 + 'CIntLeast8T', 'CIntLeast16T', 'CIntLeast32T', 'CIntLeast64T', + 'CUIntLeast8T', 'CUIntLeast16T', 'CUIntLeast32T', 'CUIntLeast64T', + 'CIntFast8T', 'CIntFast16T', 'CIntFast32T', 'CIntFast64T', + 'CUIntFast8T', 'CUIntFast16T', 'CUIntFast32T', 'CUIntFast64T', + // 最大宽度整数 + 'CIntMaxT', 'CUIntMaxT', + // 平台相关整数 + 'CSizeT', 'CIntPtrT', 'CUIntPtrT', 'CPtrDiffT', + // 布尔 + 'CBool', + // 宽字符 + 'CWCharT', 'CChar8T', 'CChar16T', 'CChar32T', + // LLVM 简写 + 'i1', 'i8', 'i16', 'i32', 'i64', + 'u1', 'u8', 'u16', 'u32', 'u64', +]); + +// Viper 扩展:检测名称是否为 Viper C 整数类型名。 +// 用于在 getTypeOfCall 中识别 C 整数类型的构造调用(如 t.CUInt32T(addr, t.CPtr)), +// 并跳过 int.__new__ 的参数类型检查,实现 Viper 的指针强转语义。 +export function isViperCIntegerTypeName(name: string): boolean { + return _viperCIntegerTypeNames.has(name); +} + +// Viper 扩展:Viper C 浮点类型名称白名单。 +const _viperCFloatTypeNames = new Set([ + 'CFloat', 'CDouble', 'CFloat8T', 'CFloat16T', 'CFloat32T', 'CFloat64T', 'CFloat128T', +]); + +// Viper 扩展:判断类型名是否为 Viper C 浮点类型。 +// 用于协议匹配等场景,让 CType 浮点子类满足 SupportsFloat 等数值协议。 +export function isViperCFloatTypeName(name: string): boolean { + return _viperCFloatTypeNames.has(name); +} + +// Viper 扩展:C 类型位宽映射表(单位:位)。 +// 依据 t.py 中各类型的 Size 属性。平台相关类型按 64 位默认值。 +// 用于实现 CType 子类之间的精确隐式转换规则: +// - 低向高(srcSize <= destSize)或同级:允许 +// - 高向低(srcSize > destSize):不允许 +const _viperCTypeSizeMap = new Map([ + // 基本整数 + ['CChar', 8], ['CShort', 16], ['CInt', 32], ['CLong', 64], ['CLongLong', 64], + ['CSignedChar', 8], ['CUnsignedChar', 8], ['CUnsignedShort', 16], + ['CUnsignedInt', 32], ['CUnsignedLong', 64], ['CUnsignedLongLong', 64], + // 固定宽度整数 + ['CInt8T', 8], ['CInt16T', 16], ['CInt32T', 32], ['CInt64T', 64], + ['CUInt8T', 8], ['CUInt16T', 16], ['CUInt32T', 32], ['CUInt64T', 64], + // 最小/最快宽度整数 + ['CIntLeast8T', 8], ['CIntLeast16T', 16], ['CIntLeast32T', 32], ['CIntLeast64T', 64], + ['CUIntLeast8T', 8], ['CUIntLeast16T', 16], ['CUIntLeast32T', 32], ['CUIntLeast64T', 64], + ['CIntFast8T', 8], ['CIntFast16T', 16], ['CIntFast32T', 32], ['CIntFast64T', 64], + ['CUIntFast8T', 8], ['CUIntFast16T', 16], ['CUIntFast32T', 32], ['CUIntFast64T', 64], + // 最大宽度整数 + ['CIntMaxT', 64], ['CUIntMaxT', 64], + // 平台相关整数 + ['CSizeT', 64], ['CIntPtrT', 64], ['CUIntPtrT', 64], ['CPtrDiffT', 64], + // 布尔 + ['CBool', 8], + // 宽字符 + ['CWCharT', 32], ['CChar8T', 8], ['CChar16T', 16], ['CChar32T', 32], + // LLVM 简写 + ['i1', 1], ['i8', 8], ['i16', 16], ['i32', 32], ['i64', 64], + ['u1', 1], ['u8', 8], ['u16', 16], ['u32', 32], ['u64', 64], + // 浮点 + ['CFloat', 32], ['CDouble', 64], + ['CFloat8T', 8], ['CFloat16T', 16], ['CFloat32T', 32], ['CFloat64T', 64], ['CFloat128T', 128], + // void + ['CVoid', 0], +]); + +// Viper 扩展:判断 CType 子类之间的隐式转换是否合法。 +// 规则: +// 1. 整数 → 整数:srcSize <= destSize 允许(低向高或同级),否则不允许 +// 2. 浮点 → 浮点:srcSize <= destSize 允许 +// 3. 整数 → 浮点:destSize >= srcSize 允许(无损) +// 4. 浮点 → 整数:不允许(有损) +// 5. 同级指针(都是 CPtr 子类):允许不同类型转换 +// 6. 不同级(一个是指针,另一个不是):不允许 +// 7. 未知位宽:保守允许(避免误报) +export function canImplicitlyConvertCType(srcType: Type, destType: Type): boolean { + if (!isClassInstance(srcType) || !isClassInstance(destType)) { + return false; + } + + const srcName = srcType.shared.name; + const destName = destType.shared.name; + + // 同类型,允许 + if (srcName === destName) { + return true; + } + + // 指针规则:同级指针(都是 CPtr 子类)允许,不同级不允许 + const srcIsPtr = isCPtrType(srcType); + const destIsPtr = isCPtrType(destType); + if (srcIsPtr && destIsPtr) { + return true; + } + if (srcIsPtr !== destIsPtr) { + return false; + } + + const srcSize = _viperCTypeSizeMap.get(srcName); + const destSize = _viperCTypeSizeMap.get(destName); + + // 未知位宽,保守允许 + if (srcSize === undefined || destSize === undefined) { + return true; + } + + const srcIsInt = _viperCIntegerTypeNames.has(srcName); + const destIsInt = _viperCIntegerTypeNames.has(destName); + const srcIsFloat = _viperCFloatTypeNames.has(srcName); + const destIsFloat = _viperCFloatTypeNames.has(destName); + + // 整数 → 整数:允许所有方向(C 语义,高向低可安全降级) + // Viper 中所有 C 整数类型在底层等价于 int,整数间的隐式转换 + // 无论位宽大小都可安全进行(如 CUInt64T → CInt 高向低降级)。 + if (srcIsInt && destIsInt) { + return true; + } + + // 浮点 → 浮点:允许所有方向(C 语义,高向低可安全降级) + if (srcIsFloat && destIsFloat) { + return true; + } + + // 整数 → 浮点:浮点位宽 >= 整数位宽时允许(无损) + if (srcIsInt && destIsFloat) { + return destSize >= srcSize; + } + + // 浮点 → 整数:不允许(有损) + if (srcIsFloat && destIsInt) { + return false; + } + + // 其他情况(void 等),保守允许 + return true; +} + +export function isAny(type: Type): type is AnyType { + return type.category === TypeCategory.Any; +} + +export function isUnknown(type: Type): type is UnknownType { + return type.category === TypeCategory.Unknown; +} + +export function isAnyOrUnknown(type: Type): type is AnyType | UnknownType { + if (type.category === TypeCategory.Any || type.category === TypeCategory.Unknown) { + return true; + } + + if (isUnion(type)) { + return type.priv.subtypes.find((subtype) => !isAnyOrUnknown(subtype)) === undefined; + } + + return false; +} + +export function isUnbound(type: Type): type is UnboundType { + return type.category === TypeCategory.Unbound; +} + +export function isUnion(type: Type): type is UnionType { + return type.category === TypeCategory.Union; +} + +export function isPossiblyUnbound(type: Type): boolean { + if (isUnbound(type)) { + return true; + } + + if (isUnion(type)) { + return type.priv.subtypes.find((subtype) => isPossiblyUnbound(subtype)) !== undefined; + } + + return false; +} + +export function isClass(type: Type): type is ClassType { + return type.category === TypeCategory.Class; +} + +export function isInstantiableClass(type: Type): type is ClassType { + return type.category === TypeCategory.Class && TypeBase.isInstantiable(type); +} + +export function isClassInstance(type: Type): type is ClassType { + return type.category === TypeCategory.Class && TypeBase.isInstance(type); +} + +export function isModule(type: Type): type is ModuleType { + return type.category === TypeCategory.Module; +} + +export function isTypeVar(type: Type): type is TypeVarType { + return type.category === TypeCategory.TypeVar; +} + +export function isParamSpec(type: Type): type is ParamSpecType { + return type.category === TypeCategory.TypeVar && type.shared.kind === TypeVarKind.ParamSpec; +} + +export function isTypeVarTuple(type: Type): type is TypeVarTupleType { + return type.category === TypeCategory.TypeVar && type.shared.kind === TypeVarKind.TypeVarTuple; +} + +export function isUnpackedTypeVarTuple(type: Type): type is TypeVarTupleType { + return isTypeVarTuple(type) && !!type.priv.isUnpacked && !type.priv.isInUnion; +} + +export function isUnpackedTypeVar(type: Type): type is TypeVarTupleType { + return isTypeVar(type) && !isTypeVarTuple(type) && !!type.priv.isUnpacked; +} + +export function isUnpackedClass(type: Type): type is ClassType { + if (!isClass(type) || !type.priv.isUnpacked) { + return false; + } + + return true; +} + +export function isUnpacked(type: Type): boolean { + return isUnpackedTypeVarTuple(type) || isUnpackedTypeVar(type) || isUnpackedClass(type); +} + +export function isFunction(type: Type): type is FunctionType { + return type.category === TypeCategory.Function; +} + +export function isOverloaded(type: Type): type is OverloadedType { + return type.category === TypeCategory.Overloaded; +} + +export function isFunctionOrOverloaded(type: Type): type is FunctionType | OverloadedType { + return type.category === TypeCategory.Function || type.category === TypeCategory.Overloaded; +} + +export function isMethodType(type: FunctionType | OverloadedType): boolean { + let funcType: FunctionType | undefined; + + if (isFunction(type)) { + funcType = type; + } else { + if (type.priv._overloads.length === 0) { + return false; + } + funcType = type.priv._overloads[0]; + } + + // __new__ methods are never really bound at runtime. + if ( + funcType.priv.preBoundFlags !== undefined && + (funcType.priv.preBoundFlags & FunctionTypeFlags.ConstructorMethod) !== 0 + ) { + return false; + } + + // If the function type has a stripped first parameter type, it was + // bound to class or object and is therefore a MethodType rather + // a FunctionType. + return !!funcType.priv.strippedFirstParamType; +} + +export function getTypeAliasInfo(type: Type) { + if (type.props?.typeAliasInfo) { + return type.props.typeAliasInfo; + } + + if ( + isTypeVar(type) && + type.shared.recursiveAlias && + type.shared.boundType && + type.shared.boundType.props?.typeAliasInfo + ) { + return type.shared.boundType.props.typeAliasInfo; + } + + return undefined; +} + +// Determines whether two types are the same. If ignorePseudoGeneric is true, +// type arguments for "pseudo-generic" classes (non-generic classes whose init +// methods are not annotated and are therefore treated as generic) are ignored. +export function isTypeSame(type1: Type, type2: Type, options: TypeSameOptions = {}, recursionCount = 0): boolean { + if (type1 === type2) { + return true; + } + + if (type1.category !== type2.category) { + if (options.treatAnySameAsUnknown) { + if (type1.category === TypeCategory.Any && type2.category === TypeCategory.Unknown) { + return true; + } + if (type1.category === TypeCategory.Unknown && type2.category === TypeCategory.Any) { + return true; + } + } + + return false; + } + + if (!options.ignoreTypeFlags) { + if ((type1.flags & TypeFlags.TypeCompatibilityMask) !== (type2.flags & TypeFlags.TypeCompatibilityMask)) { + return false; + } + } + + if (recursionCount > maxTypeRecursionCount) { + return true; + } + recursionCount++; + + if (options.honorTypeForm) { + const typeForm1 = type1.props?.typeForm; + const typeForm2 = type2.props?.typeForm; + + if (typeForm1) { + if (!typeForm2) { + return false; + } + + if (!isTypeSame(typeForm1, typeForm2, options, recursionCount)) { + return false; + } + } else if (typeForm2) { + return false; + } + } + + switch (type1.category) { + case TypeCategory.Class: { + const classType2 = type2 as ClassType; + + // If the details are not the same it's not the same class. + if (!ClassType.isSameGenericClass(type1, classType2, recursionCount)) { + return false; + } + + if (!options.ignoreConditions && !TypeCondition.isSame(type1.props?.condition, type2.props?.condition)) { + return false; + } + + if (!options.ignorePseudoGeneric || !ClassType.isPseudoGenericClass(type1)) { + // Make sure the type args match. + if (type1.priv.tupleTypeArgs && classType2.priv.tupleTypeArgs) { + const type1TupleTypeArgs = type1.priv.tupleTypeArgs || []; + const type2TupleTypeArgs = classType2.priv.tupleTypeArgs || []; + if (type1TupleTypeArgs.length !== type2TupleTypeArgs.length) { + return false; + } + + for (let i = 0; i < type1TupleTypeArgs.length; i++) { + if ( + !isTypeSame( + type1TupleTypeArgs[i].type, + type2TupleTypeArgs[i].type, + { ...options, ignoreTypeFlags: false }, + recursionCount + ) + ) { + return false; + } + + if (type1TupleTypeArgs[i].isUnbounded !== type2TupleTypeArgs[i].isUnbounded) { + return false; + } + } + } else { + const type1TypeArgs = type1.priv.typeArgs || []; + const type2TypeArgs = classType2.priv.typeArgs || []; + const typeArgCount = Math.max(type1TypeArgs.length, type2TypeArgs.length); + + for (let i = 0; i < typeArgCount; i++) { + // Assume that missing type args are "Unknown". + const typeArg1 = i < type1TypeArgs.length ? type1TypeArgs[i] : UnknownType.create(); + const typeArg2 = i < type2TypeArgs.length ? type2TypeArgs[i] : UnknownType.create(); + + if (!isTypeSame(typeArg1, typeArg2, { ...options, ignoreTypeFlags: false }, recursionCount)) { + return false; + } + } + } + } + + if (!ClassType.isLiteralValueSame(type1, classType2)) { + return false; + } + + if (!type1.priv.isUnpacked !== !classType2.priv.isUnpacked) { + return false; + } + + if (!type1.priv.isTypedDictPartial !== !classType2.priv.isTypedDictPartial) { + return false; + } + + if (options.honorIsTypeArgExplicit) { + if (!!type1.priv.isTypeArgExplicit !== !!classType2.priv.isTypeArgExplicit) { + return false; + } + } + + if (!options.ignoreTypedDictNarrowEntries && !ClassType.isTypedDictNarrowedEntriesSame(type1, classType2)) { + return false; + } + + return true; + } + + case TypeCategory.Function: { + // Make sure the parameter counts match. + const functionType2 = type2 as FunctionType; + const params1 = type1.shared.parameters; + const params2 = functionType2.shared.parameters; + + if (params1.length !== params2.length) { + return false; + } + + // If one function is ... and the other is not, they are not the same. + if (FunctionType.isGradualCallableForm(type1) !== FunctionType.isGradualCallableForm(functionType2)) { + return false; + } + + const positionOnlyIndex1 = params1.findIndex((param) => isPositionOnlySeparator(param)); + const positionOnlyIndex2 = params2.findIndex((param) => isPositionOnlySeparator(param)); + + // Make sure the parameter details match. + for (let i = 0; i < params1.length; i++) { + const param1 = params1[i]; + const param2 = params2[i]; + + if (param1.category !== param2.category) { + return false; + } + + const isName1Relevant = positionOnlyIndex1 !== undefined && i > positionOnlyIndex1; + const isName2Relevant = positionOnlyIndex2 !== undefined && i > positionOnlyIndex2; + + if (isName1Relevant !== isName2Relevant) { + return false; + } + + if (isName1Relevant) { + if (param1.name !== param2.name) { + return false; + } + } else if (isPositionOnlySeparator(param1) && isPositionOnlySeparator(param2)) { + continue; + } else if (isKeywordOnlySeparator(param1) && isKeywordOnlySeparator(param2)) { + continue; + } + + const param1Type = FunctionType.getParamType(type1, i); + const param2Type = FunctionType.getParamType(functionType2, i); + if (!isTypeSame(param1Type, param2Type, { ...options, ignoreTypeFlags: false }, recursionCount)) { + return false; + } + } + + // Make sure the return types match. + let return1Type = type1.shared.declaredReturnType; + if (type1.priv.specializedTypes && type1.priv.specializedTypes.returnType) { + return1Type = type1.priv.specializedTypes.returnType; + } + if (!return1Type && type1.shared.inferredReturnType) { + return1Type = type1.shared.inferredReturnType?.type; + } + + let return2Type = functionType2.shared.declaredReturnType; + if (functionType2.priv.specializedTypes && functionType2.priv.specializedTypes.returnType) { + return2Type = functionType2.priv.specializedTypes.returnType; + } + if (!return2Type && functionType2.shared.inferredReturnType) { + return2Type = functionType2.shared.inferredReturnType?.type; + } + + if (return1Type || return2Type) { + if ( + !return1Type || + !return2Type || + !isTypeSame(return1Type, return2Type, { ...options, ignoreTypeFlags: false }, recursionCount) + ) { + return false; + } + } + + return true; + } + + case TypeCategory.Overloaded: { + // Make sure the overload counts match. + const functionType2 = type2 as OverloadedType; + if (type1.priv._overloads.length !== functionType2.priv._overloads.length) { + return false; + } + + // We assume here that overloaded functions always appear + // in the same order from one analysis pass to another. + for (let i = 0; i < type1.priv._overloads.length; i++) { + if (!isTypeSame(type1.priv._overloads[i], functionType2.priv._overloads[i], options, recursionCount)) { + return false; + } + } + + return true; + } + + case TypeCategory.Union: { + const unionType2 = type2 as UnionType; + const subtypes1 = type1.priv.subtypes; + const subtypes2 = unionType2.priv.subtypes; + + if (subtypes1.length !== subtypes2.length) { + return false; + } + + // The types do not have a particular order, so we need to + // do the comparison in an order-independent manner. + const exclusionSet = new Set(); + return ( + findSubtype( + type1, + (subtype) => !UnionType.containsType(unionType2, subtype, options, exclusionSet, recursionCount) + ) === undefined + ); + } + + case TypeCategory.TypeVar: { + const type2TypeVar = type2 as TypeVarType; + + if (type1.priv.scopeId !== type2TypeVar.priv.scopeId) { + return false; + } + + if (type1.priv.nameWithScope !== type2TypeVar.priv.nameWithScope) { + return false; + } + + // Handle the case where this is a generic recursive type alias. Make + // sure that the type argument types match. + if (type1.shared.recursiveAlias && type2TypeVar.shared.recursiveAlias) { + const type1TypeArgs = type1?.props?.typeAliasInfo?.typeArgs || []; + const type2TypeArgs = type2?.props?.typeAliasInfo?.typeArgs || []; + const typeArgCount = Math.max(type1TypeArgs.length, type2TypeArgs.length); + + for (let i = 0; i < typeArgCount; i++) { + // Assume that missing type args are "Any". + const typeArg1 = i < type1TypeArgs.length ? type1TypeArgs[i] : AnyType.create(); + const typeArg2 = i < type2TypeArgs.length ? type2TypeArgs[i] : AnyType.create(); + + if (!isTypeSame(typeArg1, typeArg2, { ...options, ignoreTypeFlags: false }, recursionCount)) { + return false; + } + } + } + + if (isTypeVarTuple(type1) && isTypeVarTuple(type2TypeVar)) { + if (!type1.priv.isInUnion !== !type2TypeVar.priv.isInUnion) { + return false; + } + } + + if (type1.shared === type2TypeVar.shared) { + return true; + } + + if (isParamSpec(type1) !== isParamSpec(type2TypeVar)) { + return false; + } + + if (isTypeVarTuple(type1) !== isTypeVarTuple(type2TypeVar)) { + return false; + } + + if ( + type1.shared.name !== type2TypeVar.shared.name || + type1.shared.isSynthesized !== type2TypeVar.shared.isSynthesized || + type1.shared.declaredVariance !== type2TypeVar.shared.declaredVariance || + type1.priv.scopeId !== type2TypeVar.priv.scopeId + ) { + return false; + } + + const boundType1 = type1.shared.boundType; + const boundType2 = type2TypeVar.shared.boundType; + if (boundType1) { + if ( + !boundType2 || + !isTypeSame(boundType1, boundType2, { ...options, ignoreTypeFlags: false }, recursionCount) + ) { + return false; + } + } else { + if (boundType2) { + return false; + } + } + + const constraints1 = type1.shared.constraints; + const constraints2 = type2TypeVar.shared.constraints; + if (constraints1.length !== constraints2.length) { + return false; + } + + for (let i = 0; i < constraints1.length; i++) { + if ( + !isTypeSame( + constraints1[i], + constraints2[i], + { ...options, ignoreTypeFlags: false }, + recursionCount + ) + ) { + return false; + } + } + + return true; + } + + case TypeCategory.Module: { + const type2Module = type2 as ModuleType; + + // Module types are the same if they share the same + // module symbol table. + if (type1.priv.fields === type2Module.priv.fields) { + return true; + } + + // If both symbol tables are empty, we can also assume + // they're equal. + if (type1.priv.fields.size === 0 && type2Module.priv.fields.size === 0) { + return true; + } + + return false; + } + + case TypeCategory.Unknown: { + const type2Unknown = type2 as UnknownType; + + return type1.priv.isIncomplete === type2Unknown.priv.isIncomplete; + } + } + + return true; +} + +// If the type is a union, remove an "unknown" type from the union, +// returning only the known types. +export function removeUnknownFromUnion(type: Type): Type { + return removeFromUnion(type, (t: Type) => isUnknown(t)); +} + +// If the type is a union, remove an "unbound" type from the union, +// returning only the known types. +export function removeUnbound(type: Type): Type { + if (isUnion(type)) { + return removeFromUnion(type, (t: Type) => isUnbound(t)); + } + + if (isUnbound(type)) { + return UnknownType.create(); + } + + return type; +} + +export function removeFromUnion(type: Type, removeFilter: (type: Type) => boolean) { + if (isUnion(type)) { + const remainingTypes = type.priv.subtypes.filter((t) => !removeFilter(t)); + if (remainingTypes.length < type.priv.subtypes.length) { + const newType = combineTypes(remainingTypes); + + if (isUnion(newType)) { + UnionType.addTypeAliasSource(newType, type); + } + + return newType; + } + } + + return type; +} + +export function findSubtype(type: Type, filter: (type: UnionableType | NeverType) => boolean) { + if (isUnion(type)) { + return type.priv.subtypes.find((subtype) => { + return filter(subtype); + }); + } + + return filter(type) ? type : undefined; +} + +export interface CombineTypesOptions { + // By default, literals are elided (removed) from a union if the non-literal + // subtype is present. Should this be skipped? + skipElideRedundantLiterals?: boolean; + + // If specified, the maximum number of subtypes that should be allowed + // in the union before it is converted to an "Any" type. + maxSubtypeCount?: number; +} + +// Combines multiple types into a single type. If the types are +// the same, only one is returned. If they differ, they +// are combined into a UnionType. NeverTypes are filtered out. +// If no types remain in the end, a NeverType is returned. +export function combineTypes(subtypes: Type[], options?: CombineTypesOptions): Type { + let neverTypes: NeverType[]; + + // Filter out any Never or NoReturn types. + [neverTypes, subtypes] = partition(subtypes, isNever); + + // Viper 扩展:过滤联合类型中的 t.CDefine。 + // 语义:t.CDefine | T = T(CDefine 是编译时元指令,在联合中按另一类型规则检查)。 + // 若所有子类型都是 CDefine,则保留原数组(后续退化为 AnyType, + // 实现"单独使用 t.CDefine 时为任意类型")。 + if (subtypes.length > 1) { + const nonCDefineTypes = subtypes.filter((t) => !isCDefineType(t)); + if (nonCDefineTypes.length > 0 && nonCDefineTypes.length < subtypes.length) { + subtypes = nonCDefineTypes; + } + } + + // Viper 扩展:过滤联合类型中的 C 修饰符/描述符(编译时元指令)。 + // 语义:CInline | CInt = CInt(修饰符是编译时元指令,按数据类型规则检查)。 + // 覆盖修饰符:CInline, CStatic, CConst, CExtern, CVolatile, CRegister, + // CExport, CAuto, CPass(CDefine 已由上方专门逻辑处理)。 + // 例外:State 不过滤——State 是声明标记(C extern 语义),非类型修饰符。 + // 保留 State 使 isPureCModifierType 能识别声明性函数返回类型 + // (如 BOOL | t.State: pass 的 extern 声明,过滤后仍为 BOOL | State 联合类型)。 + // 若过滤后无剩余类型,则保留原数组(避免空数组异常)。 + if (subtypes.length > 1) { + const nonModifierTypes = subtypes.filter( + (t) => !isCModifierType(t) || (isClass(t) && t.shared.name === 'State') + ); + if (nonModifierTypes.length > 0 && nonModifierTypes.length < subtypes.length) { + subtypes = nonModifierTypes; + } + } + + // Viper 扩展:将 Struct | CPtr 自动映射为 CPtr[Struct]。 + // 模拟 C 语言指针语义:结构体与指针的联合类型自动转换为指向该结构体的指针类型。 + // 例如:Scheduler | t.CPtr → CPtr[Scheduler],bootinfo.bootinfo | t.CPtr → CPtr[bootinfo.bootinfo]。 + // 识别条件:联合类型中同时存在 CPtr 子类型和"结构体"子类型(非 CPtr/CVoid/CArray/CConst/CDefine/整数/str/bytes)。 + if (subtypes.length > 1) { + let cptrSubtype: ClassType | undefined; + const structSubtypes: ClassType[] = []; + const otherSubtypes: Type[] = []; + + for (const subtype of subtypes) { + if (isClass(subtype) && isCPtrType(subtype)) { + // 仅记录第一个 CPtr 子类型(多个 CPtr 时取首个作为模板) + if (!cptrSubtype) { + cptrSubtype = subtype; + } + } else if ( + isClass(subtype) && + !isCVoidType(subtype) && + !isCArrayType(subtype) && + !isCConstType(subtype) && + !isCStaticType(subtype) && + !isCDefineType(subtype) && + !ClassType.isBuiltIn(subtype, 'int') && + !ClassType.isBuiltIn(subtype, 'str') && + !ClassType.isBuiltIn(subtype, 'bytes') && + !ClassType.isBuiltIn(subtype, 'object') + ) { + structSubtypes.push(subtype); + } else { + otherSubtypes.push(subtype); + } + } + + if (cptrSubtype && structSubtypes.length > 0) { + // 选择第一个结构体类型作为 CPtr 的类型参数 + // 保留输入子类型的形式:类型表达式上下文(type[Scheduler] | type[CPtr]) + // 应输出 instantiable 形式 type[CPtr[Scheduler]];实例上下文(Scheduler | CPtr) + // 应输出实例形式 CPtr[Scheduler]。否则 validateTypeIsInstantiable 会报 + // "Expected class but received CPtr[Scheduler]"。 + const structSubtype = structSubtypes[0]; + const isInstantiableForm = TypeBase.isInstantiable(structSubtype); + const structForSpecialize = isInstantiableForm + ? ClassType.cloneAsInstance(structSubtype) + : structSubtype; + const cptrForSpecialize = isInstantiableForm + ? ClassType.cloneAsInstance(cptrSubtype) + : cptrSubtype; + // 构造 CPtr[Struct] 类型(实例形式) + const specializedCPtrInstance = ClassType.specialize(cptrForSpecialize, [structForSpecialize]); + // 必要时转回 instantiable 形式以匹配输入上下文 + const specializedCPtr = isInstantiableForm + ? ClassType.cloneAsInstantiable(specializedCPtrInstance) + : specializedCPtrInstance; + + // 若无其他子类型,直接返回 CPtr[Struct];否则保留其他子类型组成新联合 + if (otherSubtypes.length === 0 && structSubtypes.length === 1) { + return specializedCPtr; + } + + subtypes = [specializedCPtr, ...otherSubtypes]; + } + } + + if (subtypes.length === 0) { + if (neverTypes.length > 0) { + // Prefer NoReturn over Never. This approach preserves type alias + // information if present. + return neverTypes.find((t) => t.priv.isNoReturn) ?? neverTypes[0]; + } + + // Viper 扩展:仅剩 CDefine 时返回 Any(任意类型语义) + return AnyType.create(); + } + + // Handle the common case where there is only one type. + // Also handle the common case where there are multiple copies of the same type. + let allSubtypesAreSame = true; + if (subtypes.length > 1) { + for (let index = 1; index < subtypes.length; index++) { + if (subtypes[index] !== subtypes[0]) { + allSubtypesAreSame = false; + break; + } + } + } + + if (allSubtypesAreSame) { + return subtypes[0]; + } + + // Expand all union types. + let expandedTypes: Type[] | undefined; + const typeAliasSources = new Set(); + + for (let i = 0; i < subtypes.length; i++) { + const subtype = subtypes[i]; + if (isUnion(subtype)) { + if (!expandedTypes) { + expandedTypes = subtypes.slice(0, i); + } + expandedTypes = expandedTypes.concat(subtype.priv.subtypes); + + if (subtype.props?.typeAliasInfo) { + typeAliasSources.add(subtype); + } else if (subtype.priv.typeAliasSources) { + subtype.priv.typeAliasSources.forEach((subtype) => { + typeAliasSources.add(subtype); + }); + } + } else if (expandedTypes) { + expandedTypes.push(subtype); + } + } + + expandedTypes = expandedTypes ?? subtypes; + + // Sort all of the literal and empty types to the end. + expandedTypes = expandedTypes.sort((type1, type2) => { + if (isClass(type1) && type1.priv.literalValue !== undefined) { + return 1; + } + + if (isClass(type2) && type2.priv.literalValue !== undefined) { + return -1; + } + + if (isClassInstance(type1) && type1.priv.isEmptyContainer) { + return 1; + } else if (isClassInstance(type2) && type2.priv.isEmptyContainer) { + return -1; + } + + return 0; + }); + + // If removing all NoReturn types results in no remaining types, + // convert it to an unknown. + if (expandedTypes.length === 0) { + return UnknownType.create(); + } + + const newUnionType = UnionType.create(); + if (typeAliasSources.size > 0) { + newUnionType.priv.typeAliasSources = typeAliasSources; + } + + let hitMaxSubtypeCount = false; + + expandedTypes.forEach((subtype, index) => { + if (index === 0) { + UnionType.addType(newUnionType, subtype as UnionableType); + } else { + if (options?.maxSubtypeCount === undefined || newUnionType.priv.subtypes.length < options.maxSubtypeCount) { + _addTypeIfUnique(newUnionType, subtype as UnionableType, !options?.skipElideRedundantLiterals); + } else { + hitMaxSubtypeCount = true; + } + } + }); + + if (hitMaxSubtypeCount) { + return AnyType.create(); + } + + // If only one type remains, convert it from a union to a simple type. + if (newUnionType.priv.subtypes.length === 1) { + return newUnionType.priv.subtypes[0]; + } + + return newUnionType; +} + +// Determines whether the dest type is the same as the source type with +// the possible exception that the source type has a literal value when +// the dest does not. +export function isSameWithoutLiteralValue(destType: Type, srcType: Type): boolean { + // If it's the same with literals, great. + if (isTypeSame(destType, srcType)) { + return true; + } + + if (isInstantiableClass(srcType) && srcType.priv.literalValue !== undefined) { + // Strip the literal. + srcType = ClassType.cloneWithLiteral(srcType, /* value */ undefined); + return isTypeSame(destType, srcType); + } + + if (isClassInstance(srcType) && srcType.priv.literalValue !== undefined) { + // Strip the literal. + srcType = ClassType.cloneWithLiteral(srcType, /* value */ undefined); + return isTypeSame(destType, srcType, { ignoreConditions: true }); + } + + return false; +} + +function _addTypeIfUnique(unionType: UnionType, typeToAdd: UnionableType, elideRedundantLiterals: boolean) { + // Handle the addition of a string literal in a special manner to + // avoid n^2 behavior in unions that contain hundreds of string + // literal types. Skip this for constrained types. + if (isClass(typeToAdd) && !typeToAdd.props?.condition) { + const literalMaps = isClassInstance(typeToAdd) + ? unionType.priv.literalInstances + : unionType.priv.literalClasses; + + if ( + ClassType.isBuiltIn(typeToAdd, 'str') && + typeToAdd.priv.literalValue !== undefined && + literalMaps.literalStrMap !== undefined + ) { + if (!literalMaps.literalStrMap.has(typeToAdd.priv.literalValue as string)) { + UnionType.addType(unionType, typeToAdd); + } + return; + } else if ( + ClassType.isBuiltIn(typeToAdd, 'int') && + typeToAdd.priv.literalValue !== undefined && + literalMaps.literalIntMap !== undefined + ) { + if (!literalMaps.literalIntMap.has(typeToAdd.priv.literalValue as number | bigint)) { + UnionType.addType(unionType, typeToAdd); + } + return; + } else if ( + ClassType.isEnumClass(typeToAdd) && + typeToAdd.priv.literalValue !== undefined && + literalMaps.literalEnumMap !== undefined + ) { + const enumLiteral = typeToAdd.priv.literalValue as EnumLiteral; + if (!literalMaps.literalEnumMap.has(enumLiteral.getName())) { + UnionType.addType(unionType, typeToAdd); + } + return; + } + } + + const isPseudoGeneric = isClass(typeToAdd) && ClassType.isPseudoGenericClass(typeToAdd); + + for (let i = 0; i < unionType.priv.subtypes.length; i++) { + const type = unionType.priv.subtypes[i]; + + // Does this type already exist in the types array? + if (isTypeSame(type, typeToAdd, { honorTypeForm: true })) { + return; + } + + // Handle the case where pseudo-generic classes with different + // type arguments are being combined. Rather than add multiple + // specialized types, we will replace them with a single specialized + // type that is specialized with Unknowns. This is important because + // we can hit recursive cases (where a pseudo-generic class is + // parameterized with its own class) ad infinitum. + if (isPseudoGeneric) { + if (isTypeSame(type, typeToAdd, { ignorePseudoGeneric: true, honorTypeForm: true })) { + unionType.priv.subtypes[i] = ClassType.specialize( + typeToAdd, + typeToAdd.shared.typeParams.map(() => UnknownType.create()) + ); + return; + } + } + + if (isClassInstance(type) && isClassInstance(typeToAdd)) { + // If the typeToAdd is a literal value and there's already + // a non-literal type that matches, don't add the literal value. + if (elideRedundantLiterals && isSameWithoutLiteralValue(type, typeToAdd)) { + if (type.priv.literalValue === undefined) { + return; + } + } + + // If we're adding Literal[False] or Literal[True] to its + // opposite, combine them into a non-literal 'bool' type. + if ( + ClassType.isBuiltIn(type, 'bool') && + !type.props?.condition && + ClassType.isBuiltIn(typeToAdd, 'bool') && + !typeToAdd.props?.condition + ) { + if ( + typeToAdd.priv.literalValue !== undefined && + !typeToAdd.priv.literalValue === type.priv.literalValue + ) { + unionType.priv.subtypes[i] = ClassType.cloneWithLiteral(type, /* value */ undefined); + return; + } + } + + // If the typeToAdd is a TypedDict that is the same class as the + // existing type, see if one of them is a proper subset of the other. + if (ClassType.isTypedDictClass(type) && ClassType.isSameGenericClass(type, typeToAdd)) { + // Do not proceed if the TypedDicts are generic and have different type arguments. + if (!type.priv.typeArgs && !typeToAdd.priv.typeArgs) { + if (ClassType.isTypedDictNarrower(typeToAdd, type)) { + return; + } else if (ClassType.isTypedDictNarrower(type, typeToAdd)) { + unionType.priv.subtypes[i] = typeToAdd; + return; + } + } + } + } + + // If the typeToAdd is an empty container and there's already + // non-empty container of the same type, don't add the empty container. + if (isClassInstance(typeToAdd) && typeToAdd.priv.isEmptyContainer) { + if (isClassInstance(type) && ClassType.isSameGenericClass(type, typeToAdd)) { + return; + } + } + } + + UnionType.addType(unionType, typeToAdd); +} diff --git a/packages/vipeye-internal/src/languageService/hoverProvider.ts b/packages/vipeye-internal/src/languageService/hoverProvider.ts index 06ec42dd..40a5addd 100644 --- a/packages/vipeye-internal/src/languageService/hoverProvider.ts +++ b/packages/vipeye-internal/src/languageService/hoverProvider.ts @@ -1,711 +1,772 @@ -/* - * hoverProvider.ts - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT license. - * Author: Eric Traut - * - * Logic that maps a position within a Python program file into - * markdown text that is displayed when the user hovers over that - * position within a smart editor. - */ - -import { CancellationToken, Hover, MarkupKind } from 'vscode-languageserver'; - -import { - Declaration, - DeclarationType, - FunctionDeclaration, - VariableDeclaration, - isUnresolvedAliasDeclaration, -} from '../analyzer/declaration'; -import * as ParseTreeUtils from '../analyzer/parseTreeUtils'; -import { SourceMapper } from '../analyzer/sourceMapper'; -import { SynthesizedTypeInfo } from '../analyzer/symbol'; -import { isBuiltInModule } from '../analyzer/typeDocStringUtils'; -import { PrintTypeOptions, TypeEvaluator } from '../analyzer/typeEvaluatorTypes'; -import { convertToInstance, doForEachSubtype, isMaybeDescriptorInstance } from '../analyzer/typeUtils'; -import { - ClassType, - Type, - TypeCategory, - getTypeAliasInfo, - isAnyOrUnknown, - isClassInstance, - isFunction, - isFunctionOrOverloaded, - isModule, - isParamSpec, - isTypeVar, -} from '../analyzer/types'; -import { throwIfCancellationRequested } from '../common/cancellationUtils'; -import { SignatureDisplayType } from '../common/configOptions'; -import { assertNever, fail } from '../common/debug'; -import { ProgramView } from '../common/extensibility'; -import { convertOffsetToPosition, convertPositionToOffset } from '../common/positionUtils'; -import { ServiceProvider } from '../common/serviceProvider'; -import { Position, Range, TextRange } from '../common/textRange'; -import { Uri } from '../common/uri/uri'; -import { ExpressionNode, FunctionNode, NameNode, ParseNode, ParseNodeType, StringNode } from '../parser/parseNodes'; -import { ParseFileResults } from '../parser/parser'; -import { TokenType } from '../parser/tokenizerTypes'; -import { - getClassAndConstructorTypes, - getConstructorTooltip, - getDocumentationPartsForTypeAndDeclWithSource, - getToolTipForType, - getTypeForToolTip, - getWrappedFunctionType, -} from './tooltipUtils'; - -export interface HoverTextPart { - python?: boolean; - text: string; -} - -export interface HoverResults { - parts: HoverTextPart[]; - range: Range; -} - -export function convertHoverResults(hoverResults: HoverResults | null, format: MarkupKind): Hover | null { - if (!hoverResults) { - return null; - } - - const markupString = hoverResults.parts - .map((part) => { - if (part.python) { - if (format === MarkupKind.Markdown) { - return '```python\n' + part.text + '\n```\n'; - } else if (format === MarkupKind.PlainText) { - return part.text + '\n\n'; - } else { - fail(`Unsupported markup type: ${format}`); - } - } - return part.text; - }) - .join('') - .trimEnd(); - - return { - contents: { - kind: format, - value: markupString, - }, - range: hoverResults.range, - }; -} - -function getDocumentationSeparator(previousPart: HoverTextPart): string { - if (previousPart.python) { - return '---\n'; - } - - if (previousPart.text.endsWith('\n\n')) { - return '---\n'; - } - - if (previousPart.text.endsWith('\n')) { - return '\n---\n'; - } - - return '\n\n---\n'; -} - -export function addParameterResultsPart( - serviceProvider: ServiceProvider, - paramNameNode: NameNode, - resolvedDecl: Declaration | undefined, - format: MarkupKind, - parts: HoverTextPart[] -) { - // See if we have a docstring for the parent function. - let docString: string | undefined = undefined; - const funcNode = ParseTreeUtils.getEnclosingFunction(resolvedDecl?.node || paramNameNode); - if (funcNode) { - docString = ParseTreeUtils.getDocString(funcNode?.d.suite?.d.statements ?? []); - if (docString) { - // Compute the docstring now. - docString = serviceProvider - .docStringService() - .extractParameterDocumentation(docString, paramNameNode.d.value, format); - } - } - if (!docString) { - return; - } - - parts.push({ - python: false, - text: docString, - }); -} - -function addReturnResultsPart( - serviceProvider: ServiceProvider, - functionNode: FunctionNode, - format: MarkupKind, - parts: HoverTextPart[] -) { - // See if we have a docstring for the function whose return arrow is being hovered. - const docString = ParseTreeUtils.getDocString(functionNode.d.suite?.d.statements ?? []); - if (!docString) { - return; - } - - const returnDoc = serviceProvider.docStringService().extractReturnDocumentation(docString, format); - if (!returnDoc) { - return; - } - - parts.push({ - python: false, - text: returnDoc, - }); -} - -export function addDocumentationResultsPart( - serviceProvider: ServiceProvider, - docString: string | undefined, - format: MarkupKind, - parts: HoverTextPart[], - resolvedDecl: Declaration | undefined, - forceLiteralOverride?: boolean -) { - if (!docString) { - return; - } - - if (format === MarkupKind.Markdown) { - const forceLiteral = forceLiteralOverride ?? isBuiltInModule(resolvedDecl?.uri); - const markDown = serviceProvider - .docStringService() - .convertDocStringToMarkdown(docString, forceLiteral, resolvedDecl?.uri); - - if (parts.length > 0 && markDown.length > 0) { - parts.push({ text: getDocumentationSeparator(parts[parts.length - 1]) }); - } - - parts.push({ text: markDown, python: false }); - return; - } - - if (format === MarkupKind.PlainText) { - parts.push({ text: serviceProvider.docStringService().convertDocStringToPlainText(docString), python: false }); - return; - } - - fail(`Unsupported markup type: ${format}`); -} - -export function getVariableTypeText( - evaluator: TypeEvaluator, - declaration: VariableDeclaration | undefined, - name: string, - type: Type, - typeNode: ExpressionNode, - functionSignatureDisplay: SignatureDisplayType, - sourceMapper?: SourceMapper -) { - let label = 'variable'; - if (declaration) { - label = declaration.isConstant || evaluator.isFinalVariableDeclaration(declaration) ? 'constant' : 'variable'; - } - - let typeVarName: string | undefined; - - if (type.props?.typeAliasInfo && typeNode.nodeType === ParseNodeType.Name) { - const typeAliasInfo = getTypeAliasInfo(type); - if (typeAliasInfo?.shared.name === typeNode.d.value) { - if (isTypeVar(type)) { - label = isParamSpec(type) ? 'param spec' : 'type variable'; - typeVarName = type.shared.name; - } else { - // Handle type aliases specially. - const typeText = evaluator.printType(convertToInstance(getTypeForToolTip(evaluator, typeNode)), { - expandTypeAlias: true, - }); - - return `(type) ${name} = ` + typeText; - } - } - } - - if ( - type.category === TypeCategory.Function || - type.category === TypeCategory.Overloaded || - typeNode.parent?.nodeType === ParseNodeType.Call - ) { - return getToolTipForType( - type, - label, - name, - evaluator, - /* isProperty */ false, - functionSignatureDisplay, - typeNode, - sourceMapper - ); - } - - const typeText = typeVarName ?? name + ': ' + evaluator.printType(getTypeForToolTip(evaluator, typeNode)); - - return `(${label}) ` + typeText; -} - -export class HoverProvider { - private readonly _parseResults: ParseFileResults | undefined; - private readonly _sourceMapper: SourceMapper; - - constructor( - private readonly _program: ProgramView, - private readonly _fileUri: Uri, - private readonly _position: Position, - private readonly _format: MarkupKind, - private readonly _token: CancellationToken - ) { - this._parseResults = this._program.getParseResults(this._fileUri); - this._sourceMapper = this._program.getSourceMapper(this._fileUri, this._token, /* mapCompiled */ true); - } - - getHover(): Hover | null { - return convertHoverResults(this._getHoverResult(), this._format); - } - - static getPrimaryDeclaration(declarations: Declaration[]) { - // In most cases, it's best to treat the first declaration as the - // "primary". This works well for properties that have setters - // which often have doc strings on the getter but not the setter. - // The one case where using the first declaration doesn't work as - // well is the case where an import statement within an __init__.py - // file uses the form "from .A import A". In this case, if we use - // the first declaration, it will show up as a module rather than - // the imported symbol type. - const primaryDeclaration = declarations[0]; - if (primaryDeclaration.type === DeclarationType.Alias && declarations.length > 1) { - return declarations[1]; - } else if ( - primaryDeclaration.type === DeclarationType.Variable && - declarations.length > 1 && - primaryDeclaration.isDefinedBySlots - ) { - // Slots cannot have docstrings, so pick the secondary. - return declarations[1]; - } - - return primaryDeclaration; - } - - private get _evaluator(): TypeEvaluator { - return this._program.evaluator!; - } - - private get _functionSignatureDisplay() { - return this._program.configOptions.functionSignatureDisplay; - } - - private _getHoverResult(): HoverResults | null { - throwIfCancellationRequested(this._token); - - if (!this._parseResults) { - return null; - } - - const offset = convertPositionToOffset(this._position, this._parseResults.tokenizerOutput.lines); - if (offset === undefined) { - return null; - } - - const node = ParseTreeUtils.findNodeByOffset(this._parseResults.parserOutput.parseTree, offset); - if (node === undefined) { - return null; - } - - const results: HoverResults = { - parts: [], - range: { - start: convertOffsetToPosition(node.start, this._parseResults.tokenizerOutput.lines), - end: convertOffsetToPosition(TextRange.getEnd(node), this._parseResults.tokenizerOutput.lines), - }, - }; - - if (node.nodeType === ParseNodeType.Name) { - const declInfo = this._evaluator.getDeclInfoForNameNode(node); - const declarations = declInfo?.decls; - - if (declarations && declarations.length > 0) { - const primaryDeclaration = HoverProvider.getPrimaryDeclaration(declarations); - this._addResultsForDeclaration(results.parts, primaryDeclaration, node); - } else if (declInfo && declInfo.synthesizedTypes.length > 0) { - const nameNode = node; - declInfo?.synthesizedTypes.forEach((type) => { - this._addResultsForSynthesizedType(results.parts, type, nameNode); - }); - this._addDocumentationPart(results.parts, node, /* resolvedDecl */ undefined); - } else if (!node.parent || node.parent.nodeType !== ParseNodeType.ModuleName) { - // If we had no declaration, see if we can provide a minimal tooltip. We'll skip - // this if it's part of a module name, since a module name part with no declaration - // is a directory (a namespace package), and we don't want to provide any hover - // information in that case. - if (results.parts.length === 0) { - const type = this._getType(node); - let typeText: string; - if (isModule(type)) { - // Handle modules specially because submodules aren't associated with - // declarations, but we want them to be presented in the same way as - // the top-level module, which does have a declaration. - typeText = '(module) ' + node.d.value; - } else { - let label = 'function'; - let isProperty = false; - - if (isMaybeDescriptorInstance(type, /* requireSetter */ false)) { - isProperty = true; - label = 'property'; - } - - typeText = getToolTipForType( - type, - label, - node.d.value, - this._evaluator, - isProperty, - this._functionSignatureDisplay - ); - } - - this._addResultsPart(results.parts, typeText, /* python */ true); - this._addDocumentationPart(results.parts, node, /* resolvedDecl */ undefined); - } - } - } else if (node.nodeType === ParseNodeType.String) { - const type = this._evaluator.getExpectedType(node)?.type; - if (type !== undefined) { - this._tryAddPartsForTypedDictKey(node, type, results.parts); - } - } else if (node.nodeType === ParseNodeType.Function) { - // Hovering over the `->` return-type arrow shows the function's return documentation. - const token = ParseTreeUtils.getTokenOverlapping(this._parseResults.tokenizerOutput.tokens, offset); - if (token?.type === TokenType.Arrow) { - addReturnResultsPart(this._program.serviceProvider, node, this._format, results.parts); - } - } - - return results.parts.length > 0 ? results : null; - } - - private _addResultsForDeclaration(parts: HoverTextPart[], declaration: Declaration, node: NameNode): void { - const resolvedDecl = - declaration.type === DeclarationType.Alias - ? this._evaluator.resolveAliasDeclaration(declaration, /* resolveLocalNames */ true) - : declaration; - if ( - !resolvedDecl || - (resolvedDecl.type === DeclarationType.Alias && isUnresolvedAliasDeclaration(resolvedDecl)) - ) { - this._addResultsPart(parts, `(import) ` + node.d.value + this._getTypeText(node), /* python */ true); - return; - } - - switch (resolvedDecl.type) { - case DeclarationType.Intrinsic: { - this._addResultsPart(parts, node.d.value + this._getTypeText(node), /* python */ true); - this._addDocumentationPart(parts, node, resolvedDecl); - break; - } - - case DeclarationType.Variable: { - // If the named node is an aliased import symbol, we can't call - // getType on the original name because it's not in the symbol - // table. Instead, use the node from the resolved alias. - let typeNode: ParseNode = node; - let cachedTypeForTypeNode: Type | undefined; - if ( - declaration.node.nodeType === ParseNodeType.ImportAs || - declaration.node.nodeType === ParseNodeType.ImportFromAs - ) { - if (declaration.node.d.alias && node !== declaration.node.d.alias) { - if (resolvedDecl.node.nodeType === ParseNodeType.Name) { - typeNode = resolvedDecl.node; - } - } - } else if (node.parent?.nodeType === ParseNodeType.Argument && node.parent.d.name === node) { - // If this is a named argument, we would normally have received a Parameter declaration - // rather than a variable declaration, but we can get here in the case of a dataclass. - if (declaration.node.nodeType === ParseNodeType.Name) { - if (!resolvedDecl.typeAnnotationNode) { - // No annotation: use the declaration name node. - typeNode = declaration.node; - } else { - // Has annotation: prefer the call-site node so the type evaluator - // returns the declared annotation type (not flow-narrowed by the default). - // Fall back to declaration node if the call-site resolves to Any/Unknown - // (e.g. NamedTuple synthesized constructors). - const callSiteType = this._getType(node); - if (isAnyOrUnknown(callSiteType)) { - typeNode = declaration.node; - } else { - // typeNode stays as `node`; reuse the type we just computed to - // avoid a duplicate _getType call on this hot hover path. - cachedTypeForTypeNode = callSiteType; - } - } - } - } - - // Determine if this identifier is a type alias. If so, expand - // the type alias when printing the type information. - const type = cachedTypeForTypeNode ?? this._getType(typeNode); - const typeText = getVariableTypeText( - this._evaluator, - resolvedDecl, - node.d.value, - type, - typeNode, - this._functionSignatureDisplay, - this._sourceMapper - ); - - this._addResultsPart(parts, typeText, /* python */ true); - this._addDocumentationPart(parts, node, resolvedDecl); - break; - } - - case DeclarationType.Param: { - this._addResultsPart(parts, '(parameter) ' + node.d.value + this._getTypeText(node), /* python */ true); - addParameterResultsPart(this._program.serviceProvider, node, resolvedDecl, this._format, parts); - this._addDocumentationPart(parts, node, resolvedDecl); - break; - } - - case DeclarationType.TypeParam: { - // If the user is hovering over a type parameter name in a class type parameter - // list, display the computed variance of the type param. - const typeParamListNode = ParseTreeUtils.getParentNodeOfType(node, ParseNodeType.TypeParameterList); - const nodeType = typeParamListNode?.parent?.nodeType; - const printTypeVarVariance = nodeType === ParseNodeType.Class || nodeType === ParseNodeType.TypeAlias; - - this._addResultsPart( - parts, - '(type parameter) ' + node.d.value + this._getTypeText(node, { printTypeVarVariance }), - /* python */ true - ); - this._addDocumentationPart(parts, node, resolvedDecl); - break; - } - - case DeclarationType.Class: - case DeclarationType.SpecialBuiltInClass: { - if (this._addInitOrNewMethodInsteadIfCallNode(node, parts, resolvedDecl)) { - return; - } - - const nameNode = resolvedDecl.type === DeclarationType.Class ? resolvedDecl.node.d.name : node; - this._addResultsPart(parts, '(class) ' + nameNode.d.value, /* python */ true); - this._addDocumentationPart(parts, node, resolvedDecl); - break; - } - - case DeclarationType.Function: { - let label = 'function'; - let isProperty = false; - if (resolvedDecl.isMethod) { - const declaredType = this._evaluator.getTypeForDeclaration(resolvedDecl)?.type; - isProperty = !!declaredType && isMaybeDescriptorInstance(declaredType, /* requireSetter */ false); - label = isProperty ? 'property' : 'method'; - } - - let type = this._getType(node); - const resolvedType = this._getType(resolvedDecl.node.d.name); - type = isAnyOrUnknown(type) ? resolvedType : type; - - // If this function is decorated with @functools.wraps, show the wrapped function's signature. - if (isFunction(type)) { - const wrappedType = getWrappedFunctionType(resolvedDecl as FunctionDeclaration, this._evaluator); - if (wrappedType) { - type = wrappedType; - } - } - const signatureString = getToolTipForType( - type, - label, - node.d.value, - this._evaluator, - isProperty, - this._functionSignatureDisplay, - /* typeNode */ undefined, - this._sourceMapper - ); - - this._addResultsPart(parts, signatureString, /* python */ true); - this._addDocumentationPart(parts, node, resolvedDecl); - break; - } - - case DeclarationType.Alias: { - // First the 'module' header. - this._addResultsPart(parts, '(module) ' + node.d.value, /* python */ true); - this._addDocumentationPart(parts, node, resolvedDecl); - break; - } - - case DeclarationType.TypeAlias: { - const type = convertToInstance(this._getType(node)); - const typeText = this._evaluator.printType(type, { expandTypeAlias: true }); - this._addResultsPart(parts, `(type) ${node.d.value} = ${typeText}`, /* python */ true); - this._addDocumentationPart(parts, node, resolvedDecl); - break; - } - - default: - assertNever(resolvedDecl); - } - } - - private _addResultsForSynthesizedType(parts: HoverTextPart[], typeInfo: SynthesizedTypeInfo, hoverNode: NameNode) { - let typeText: string | undefined; - - if (isModule(typeInfo.type)) { - typeText = '(module) ' + hoverNode.d.value; - } else { - const node = typeInfo.node ?? hoverNode; - - const type = this._getType(node); - typeText = getVariableTypeText( - this._evaluator, - /* declaration */ undefined, - node.d.value, - type, - node, - this._functionSignatureDisplay, - this._sourceMapper - ); - } - - if (typeText) { - this._addResultsPart(parts, typeText, /* python */ true); - } - } - - private _tryAddPartsForTypedDictKey(node: StringNode, type: Type, parts: HoverTextPart[]) { - // If the expected type is a TypedDict and the current node is a key entry then we can provide a tooltip - // with the type of the TypedDict key and its docstring, if available. - const seenEntries = new Set(); - doForEachSubtype(type, (subtype) => { - if (isClassInstance(subtype) && ClassType.isTypedDictClass(subtype)) { - const entry = subtype.shared.typedDictEntries?.knownItems.get(node.d.value); - if (entry) { - // e.g. (key) name: str - const text = '(key) ' + node.d.value + ': ' + this._evaluator.printType(entry.valueType); - - const declarations = ClassType.getSymbolTable(subtype).get(node.d.value)?.getDeclarations(); - let declarationWithDoc: Declaration | undefined; - let docString = ''; - if (declarations !== undefined && declarations?.length !== 0) { - // As we are just interested in the docString we don't have to worry about - // anything other than the first declaration. There also shouldn't be more - // than one declaration for a TypedDict key variable. - const declaration = declarations[0]; - if (declaration.type === DeclarationType.Variable && declaration.docString !== undefined) { - declarationWithDoc = declaration; - docString = declaration.docString; - } - } - - const dedupKey = `${text}\n${docString}`; - if (seenEntries.has(dedupKey)) { - return; - } - - seenEntries.add(dedupKey); - - // If we have already added parts for another declaration (e.g. for a union of TypedDicts that share the same key) - // then we need to add a separator to prevent a visual bug. - if (parts.length > 0) { - parts.push({ text: '\n\n---\n' }); - } - - this._addResultsPart(parts, text, /* python */ true); - - if (declarationWithDoc) { - this._addDocumentationPartForType(parts, subtype, declarationWithDoc); - } - } - } - }); - } - - private _addInitOrNewMethodInsteadIfCallNode(node: NameNode, parts: HoverTextPart[], declaration: Declaration) { - const result = getClassAndConstructorTypes(node, this._evaluator); - if (!result) { - return false; - } - - if (result.methodType && isFunctionOrOverloaded(result.methodType)) { - this._addResultsPart( - parts, - getConstructorTooltip(node.d.value, result.methodType, this._evaluator, this._functionSignatureDisplay), - /* python */ true - ); - - const addedDoc = this._addDocumentationPartForType(parts, result.methodType, declaration); - - if (!addedDoc) { - this._addDocumentationPartForType(parts, result.classType, declaration); - } - return true; - } - return false; - } - - private _getType(node: ExpressionNode) { - // It does common work necessary for hover for a type we got - // from raw type evaluator. - return getTypeForToolTip(this._evaluator, node); - } - - private _getTypeText(node: ExpressionNode, options?: PrintTypeOptions): string { - const type = this._getType(node); - return ': ' + this._evaluator.printType(type, options); - } - - private _addDocumentationPart(parts: HoverTextPart[], node: NameNode, resolvedDecl: Declaration | undefined) { - const type = this._getType(node); - this._addDocumentationPartForType(parts, type, resolvedDecl, node.d.value); - } - - private _addDocumentationPartForType( - parts: HoverTextPart[], - type: Type | undefined, - resolvedDecl: Declaration | undefined, - name?: string - ): boolean { - const documentation = getDocumentationPartsForTypeAndDeclWithSource( - this._sourceMapper, - type, - resolvedDecl, - this._evaluator, - { - name, - } - ); - - addDocumentationResultsPart( - this._program.serviceProvider, - documentation?.text, - this._format, - parts, - resolvedDecl, - documentation?.forceLiteral - ); - return !!documentation?.text; - } - - private _addResultsPart(parts: HoverTextPart[], text: string, python = false) { - parts.push({ - python, - text, - }); - } -} +/* + * hoverProvider.ts + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * Author: Eric Traut + * + * Logic that maps a position within a Python program file into + * markdown text that is displayed when the user hovers over that + * position within a smart editor. + */ + +import { CancellationToken, Hover, MarkupKind } from 'vscode-languageserver'; + +import { + Declaration, + DeclarationType, + FunctionDeclaration, + VariableDeclaration, + isUnresolvedAliasDeclaration, +} from '../analyzer/declaration'; +import { getFileInfo } from '../analyzer/analyzerNodeInfo'; +import * as ParseTreeUtils from '../analyzer/parseTreeUtils'; +import { SourceMapper } from '../analyzer/sourceMapper'; +import { SynthesizedTypeInfo } from '../analyzer/symbol'; +import { isBuiltInModule } from '../analyzer/typeDocStringUtils'; +import { PrintTypeOptions, TypeEvaluator } from '../analyzer/typeEvaluatorTypes'; +import { convertToInstance, doForEachSubtype, isMaybeDescriptorInstance } from '../analyzer/typeUtils'; +import { + ClassType, + Type, + TypeCategory, + getTypeAliasInfo, + isAnyOrUnknown, + isClassInstance, + isFunction, + isFunctionOrOverloaded, + isModule, + isParamSpec, + isTypeVar, +} from '../analyzer/types'; +import { throwIfCancellationRequested } from '../common/cancellationUtils'; +import { SignatureDisplayType } from '../common/configOptions'; +import { IsViperDialect } from '../common/dialect'; +import { assertNever, fail } from '../common/debug'; +import { ProgramView } from '../common/extensibility'; +import { convertOffsetToPosition, convertPositionToOffset } from '../common/positionUtils'; +import { ServiceProvider } from '../common/serviceProvider'; +import { Position, Range, TextRange } from '../common/textRange'; +import { Uri } from '../common/uri/uri'; +import { ExpressionNode, FunctionNode, NameNode, ParseNode, ParseNodeType, StringNode } from '../parser/parseNodes'; +import { ParseFileResults } from '../parser/parser'; +import { TokenType } from '../parser/tokenizerTypes'; +import { + getClassAndConstructorTypes, + getConstructorTooltip, + getDocumentationPartsForTypeAndDeclWithSource, + getToolTipForType, + getTypeForToolTip, + getWrappedFunctionType, +} from './tooltipUtils'; + +export interface HoverTextPart { + python?: boolean; + text: string; +} + +export interface HoverResults { + parts: HoverTextPart[]; + range: Range; +} + +export function convertHoverResults(hoverResults: HoverResults | null, format: MarkupKind): Hover | null { + if (!hoverResults) { + return null; + } + + const markupString = hoverResults.parts + .map((part) => { + if (part.python) { + if (format === MarkupKind.Markdown) { + return '```python\n' + part.text + '\n```\n'; + } else if (format === MarkupKind.PlainText) { + return part.text + '\n\n'; + } else { + fail(`Unsupported markup type: ${format}`); + } + } + return part.text; + }) + .join('') + .trimEnd(); + + return { + contents: { + kind: format, + value: markupString, + }, + range: hoverResults.range, + }; +} + +function getDocumentationSeparator(previousPart: HoverTextPart): string { + if (previousPart.python) { + return '---\n'; + } + + if (previousPart.text.endsWith('\n\n')) { + return '---\n'; + } + + if (previousPart.text.endsWith('\n')) { + return '\n---\n'; + } + + return '\n\n---\n'; +} + +export function addParameterResultsPart( + serviceProvider: ServiceProvider, + paramNameNode: NameNode, + resolvedDecl: Declaration | undefined, + format: MarkupKind, + parts: HoverTextPart[] +) { + // See if we have a docstring for the parent function. + let docString: string | undefined = undefined; + const funcNode = ParseTreeUtils.getEnclosingFunction(resolvedDecl?.node || paramNameNode); + if (funcNode) { + docString = ParseTreeUtils.getDocString(funcNode?.d.suite?.d.statements ?? []); + if (docString) { + // Compute the docstring now. + docString = serviceProvider + .docStringService() + .extractParameterDocumentation(docString, paramNameNode.d.value, format); + } + } + if (!docString) { + return; + } + + parts.push({ + python: false, + text: docString, + }); +} + +function addReturnResultsPart( + serviceProvider: ServiceProvider, + functionNode: FunctionNode, + format: MarkupKind, + parts: HoverTextPart[] +) { + // See if we have a docstring for the function whose return arrow is being hovered. + const docString = ParseTreeUtils.getDocString(functionNode.d.suite?.d.statements ?? []); + if (!docString) { + return; + } + + const returnDoc = serviceProvider.docStringService().extractReturnDocumentation(docString, format); + if (!returnDoc) { + return; + } + + parts.push({ + python: false, + text: returnDoc, + }); +} + +export function addDocumentationResultsPart( + serviceProvider: ServiceProvider, + docString: string | undefined, + format: MarkupKind, + parts: HoverTextPart[], + resolvedDecl: Declaration | undefined, + forceLiteralOverride?: boolean +) { + if (!docString) { + return; + } + + if (format === MarkupKind.Markdown) { + const forceLiteral = forceLiteralOverride ?? isBuiltInModule(resolvedDecl?.uri); + const markDown = serviceProvider + .docStringService() + .convertDocStringToMarkdown(docString, forceLiteral, resolvedDecl?.uri); + + if (parts.length > 0 && markDown.length > 0) { + parts.push({ text: getDocumentationSeparator(parts[parts.length - 1]) }); + } + + parts.push({ text: markDown, python: false }); + return; + } + + if (format === MarkupKind.PlainText) { + parts.push({ text: serviceProvider.docStringService().convertDocStringToPlainText(docString), python: false }); + return; + } + + fail(`Unsupported markup type: ${format}`); +} + +export function getVariableTypeText( + evaluator: TypeEvaluator, + declaration: VariableDeclaration | undefined, + name: string, + type: Type, + typeNode: ExpressionNode, + functionSignatureDisplay: SignatureDisplayType, + sourceMapper?: SourceMapper +) { + let label = 'variable'; + if (declaration) { + label = declaration.isConstant || evaluator.isFinalVariableDeclaration(declaration) ? 'constant' : 'variable'; + } + + let typeVarName: string | undefined; + + if (type.props?.typeAliasInfo && typeNode.nodeType === ParseNodeType.Name) { + const typeAliasInfo = getTypeAliasInfo(type); + if (typeAliasInfo?.shared.name === typeNode.d.value) { + if (isTypeVar(type)) { + label = isParamSpec(type) ? 'param spec' : 'type variable'; + typeVarName = type.shared.name; + } else { + // Handle type aliases specially. + const typeText = evaluator.printType(convertToInstance(getTypeForToolTip(evaluator, typeNode)), { + expandTypeAlias: true, + }); + + return `(type) ${name} = ` + typeText; + } + } + } + + // Viper 扩展:t.CTypedef 声明的类型别名悬浮提示。 + // 当变量声明带 t.CTypedef 注解时(如 `UINT32PTR: t.CTypedef = t.CUInt32T | t.CPtr`), + // 即使 typeAliasInfo 未被设置(跨模块 import 等场景),也显示类型别名格式。 + // 格式:(type) name = actualType。不展开类型别名(expandTypeAlias: false), + // 保留原始 typedef 表达式中的类型名称(如 CUInt32T 而非 int)。 + if ( + declaration?.typeAnnotationNode && + declaration.typeAnnotationNode.nodeType === ParseNodeType.MemberAccess && + (declaration.typeAnnotationNode as any).d?.member?.d?.value === 'CTypedef' + ) { + const typeText = evaluator.printType(convertToInstance(getTypeForToolTip(evaluator, typeNode)), { + expandTypeAlias: false, + }); + return `(type) ${name} = ` + typeText; + } + + if ( + type.category === TypeCategory.Function || + type.category === TypeCategory.Overloaded || + typeNode.parent?.nodeType === ParseNodeType.Call + ) { + return getToolTipForType( + type, + label, + name, + evaluator, + /* isProperty */ false, + functionSignatureDisplay, + typeNode, + sourceMapper + ); + } + + const typeText = typeVarName ?? name + ': ' + evaluator.printType(getTypeForToolTip(evaluator, typeNode)); + + // Viper 扩展:变量类型是 typedef 别名时,附加 typedef 信息换行显示。 + // 例如 `k: UINT16PTR` 悬浮显示: + // (variable) k: UINT16PTR + // (typedef) UINT16PTR = CPtr[CUInt16T] + // 仅当变量类型带有 typeAliasInfo 且别名名与变量名不同时触发 + // (避免类型别名定义本身悬浮时重复显示)。 + if ( + typeNode.nodeType === ParseNodeType.Name && + type.props?.typeAliasInfo + ) { + const aliasInfo = getTypeAliasInfo(type); + if (aliasInfo && aliasInfo.shared.name !== typeNode.d.value) { + const fileInfo = getFileInfo(typeNode); + if (IsViperDialect(fileInfo.executionEnvironment.dialect)) { + const aliasName = aliasInfo.shared.name; + const actualType = evaluator.printType( + convertToInstance(getTypeForToolTip(evaluator, typeNode)), + { expandTypeAlias: false } + ); + return `(${label}) ` + typeText + `\n(typedef) ${aliasName} = ` + actualType; + } + } + } + + return `(${label}) ` + typeText; +} + +export class HoverProvider { + private readonly _parseResults: ParseFileResults | undefined; + private readonly _sourceMapper: SourceMapper; + + constructor( + private readonly _program: ProgramView, + private readonly _fileUri: Uri, + private readonly _position: Position, + private readonly _format: MarkupKind, + private readonly _token: CancellationToken + ) { + this._parseResults = this._program.getParseResults(this._fileUri); + this._sourceMapper = this._program.getSourceMapper(this._fileUri, this._token, /* mapCompiled */ true); + } + + getHover(): Hover | null { + return convertHoverResults(this._getHoverResult(), this._format); + } + + static getPrimaryDeclaration(declarations: Declaration[]) { + // In most cases, it's best to treat the first declaration as the + // "primary". This works well for properties that have setters + // which often have doc strings on the getter but not the setter. + // The one case where using the first declaration doesn't work as + // well is the case where an import statement within an __init__.py + // file uses the form "from .A import A". In this case, if we use + // the first declaration, it will show up as a module rather than + // the imported symbol type. + const primaryDeclaration = declarations[0]; + if (primaryDeclaration.type === DeclarationType.Alias && declarations.length > 1) { + return declarations[1]; + } else if ( + primaryDeclaration.type === DeclarationType.Variable && + declarations.length > 1 && + primaryDeclaration.isDefinedBySlots + ) { + // Slots cannot have docstrings, so pick the secondary. + return declarations[1]; + } + + return primaryDeclaration; + } + + private get _evaluator(): TypeEvaluator { + return this._program.evaluator!; + } + + private get _functionSignatureDisplay() { + return this._program.configOptions.functionSignatureDisplay; + } + + private _getHoverResult(): HoverResults | null { + throwIfCancellationRequested(this._token); + + if (!this._parseResults) { + return null; + } + + const offset = convertPositionToOffset(this._position, this._parseResults.tokenizerOutput.lines); + if (offset === undefined) { + return null; + } + + const node = ParseTreeUtils.findNodeByOffset(this._parseResults.parserOutput.parseTree, offset); + if (node === undefined) { + return null; + } + + const results: HoverResults = { + parts: [], + range: { + start: convertOffsetToPosition(node.start, this._parseResults.tokenizerOutput.lines), + end: convertOffsetToPosition(TextRange.getEnd(node), this._parseResults.tokenizerOutput.lines), + }, + }; + + if (node.nodeType === ParseNodeType.Name) { + const declInfo = this._evaluator.getDeclInfoForNameNode(node); + const declarations = declInfo?.decls; + + if (declarations && declarations.length > 0) { + const primaryDeclaration = HoverProvider.getPrimaryDeclaration(declarations); + this._addResultsForDeclaration(results.parts, primaryDeclaration, node); + } else if (declInfo && declInfo.synthesizedTypes.length > 0) { + const nameNode = node; + declInfo?.synthesizedTypes.forEach((type) => { + this._addResultsForSynthesizedType(results.parts, type, nameNode); + }); + this._addDocumentationPart(results.parts, node, /* resolvedDecl */ undefined); + } else if (!node.parent || node.parent.nodeType !== ParseNodeType.ModuleName) { + // If we had no declaration, see if we can provide a minimal tooltip. We'll skip + // this if it's part of a module name, since a module name part with no declaration + // is a directory (a namespace package), and we don't want to provide any hover + // information in that case. + if (results.parts.length === 0) { + const type = this._getType(node); + let typeText: string; + if (isModule(type)) { + // Handle modules specially because submodules aren't associated with + // declarations, but we want them to be presented in the same way as + // the top-level module, which does have a declaration. + typeText = '(module) ' + node.d.value; + } else { + let label = 'function'; + let isProperty = false; + + if (isMaybeDescriptorInstance(type, /* requireSetter */ false)) { + isProperty = true; + label = 'property'; + } + + typeText = getToolTipForType( + type, + label, + node.d.value, + this._evaluator, + isProperty, + this._functionSignatureDisplay + ); + } + + this._addResultsPart(results.parts, typeText, /* python */ true); + this._addDocumentationPart(results.parts, node, /* resolvedDecl */ undefined); + } + } + } else if (node.nodeType === ParseNodeType.String) { + const type = this._evaluator.getExpectedType(node)?.type; + if (type !== undefined) { + this._tryAddPartsForTypedDictKey(node, type, results.parts); + } + } else if (node.nodeType === ParseNodeType.Function) { + // Hovering over the `->` return-type arrow shows the function's return documentation. + const token = ParseTreeUtils.getTokenOverlapping(this._parseResults.tokenizerOutput.tokens, offset); + if (token?.type === TokenType.Arrow) { + addReturnResultsPart(this._program.serviceProvider, node, this._format, results.parts); + } + } + + return results.parts.length > 0 ? results : null; + } + + private _addResultsForDeclaration(parts: HoverTextPart[], declaration: Declaration, node: NameNode): void { + const resolvedDecl = + declaration.type === DeclarationType.Alias + ? this._evaluator.resolveAliasDeclaration(declaration, /* resolveLocalNames */ true) + : declaration; + if ( + !resolvedDecl || + (resolvedDecl.type === DeclarationType.Alias && isUnresolvedAliasDeclaration(resolvedDecl)) + ) { + this._addResultsPart(parts, `(import) ` + node.d.value + this._getTypeText(node), /* python */ true); + return; + } + + switch (resolvedDecl.type) { + case DeclarationType.Intrinsic: { + this._addResultsPart(parts, node.d.value + this._getTypeText(node), /* python */ true); + this._addDocumentationPart(parts, node, resolvedDecl); + break; + } + + case DeclarationType.Variable: { + // If the named node is an aliased import symbol, we can't call + // getType on the original name because it's not in the symbol + // table. Instead, use the node from the resolved alias. + let typeNode: ParseNode = node; + let cachedTypeForTypeNode: Type | undefined; + if ( + declaration.node.nodeType === ParseNodeType.ImportAs || + declaration.node.nodeType === ParseNodeType.ImportFromAs + ) { + if (declaration.node.d.alias && node !== declaration.node.d.alias) { + if (resolvedDecl.node.nodeType === ParseNodeType.Name) { + typeNode = resolvedDecl.node; + } + } + } else if (node.parent?.nodeType === ParseNodeType.Argument && node.parent.d.name === node) { + // If this is a named argument, we would normally have received a Parameter declaration + // rather than a variable declaration, but we can get here in the case of a dataclass. + if (declaration.node.nodeType === ParseNodeType.Name) { + if (!resolvedDecl.typeAnnotationNode) { + // No annotation: use the declaration name node. + typeNode = declaration.node; + } else { + // Has annotation: prefer the call-site node so the type evaluator + // returns the declared annotation type (not flow-narrowed by the default). + // Fall back to declaration node if the call-site resolves to Any/Unknown + // (e.g. NamedTuple synthesized constructors). + const callSiteType = this._getType(node); + if (isAnyOrUnknown(callSiteType)) { + typeNode = declaration.node; + } else { + // typeNode stays as `node`; reuse the type we just computed to + // avoid a duplicate _getType call on this hot hover path. + cachedTypeForTypeNode = callSiteType; + } + } + } + } + + // Determine if this identifier is a type alias. If so, expand + // the type alias when printing the type information. + const type = cachedTypeForTypeNode ?? this._getType(typeNode); + const typeText = getVariableTypeText( + this._evaluator, + resolvedDecl, + node.d.value, + type, + typeNode, + this._functionSignatureDisplay, + this._sourceMapper + ); + + this._addResultsPart(parts, typeText, /* python */ true); + this._addDocumentationPart(parts, node, resolvedDecl); + break; + } + + case DeclarationType.Param: { + // Viper 扩展:参数类型是 typedef 别名时,附加 typedef 信息换行显示。 + // 例如 `fb: UINT32PTR` 悬浮显示: + // (parameter) fb: UINT32PTR + // (typedef) UINT32PTR = CPtr[CUInt32T] + const paramType = this._getType(node); + let paramLine = '(parameter) ' + node.d.value + this._getTypeText(node); + if (paramType?.props?.typeAliasInfo && node.nodeType === ParseNodeType.Name) { + const aliasInfo = getTypeAliasInfo(paramType); + if (aliasInfo && aliasInfo.shared.name !== node.d.value) { + const fileInfo = getFileInfo(node); + if (IsViperDialect(fileInfo.executionEnvironment.dialect)) { + const actualType = this._evaluator.printType( + convertToInstance(getTypeForToolTip(this._evaluator, node)), + { expandTypeAlias: false } + ); + paramLine += `\n(typedef) ${aliasInfo.shared.name} = ` + actualType; + } + } + } + this._addResultsPart(parts, paramLine, /* python */ true); + addParameterResultsPart(this._program.serviceProvider, node, resolvedDecl, this._format, parts); + this._addDocumentationPart(parts, node, resolvedDecl); + break; + } + + case DeclarationType.TypeParam: { + // If the user is hovering over a type parameter name in a class type parameter + // list, display the computed variance of the type param. + const typeParamListNode = ParseTreeUtils.getParentNodeOfType(node, ParseNodeType.TypeParameterList); + const nodeType = typeParamListNode?.parent?.nodeType; + const printTypeVarVariance = nodeType === ParseNodeType.Class || nodeType === ParseNodeType.TypeAlias; + + this._addResultsPart( + parts, + '(type parameter) ' + node.d.value + this._getTypeText(node, { printTypeVarVariance }), + /* python */ true + ); + this._addDocumentationPart(parts, node, resolvedDecl); + break; + } + + case DeclarationType.Class: + case DeclarationType.SpecialBuiltInClass: { + if (this._addInitOrNewMethodInsteadIfCallNode(node, parts, resolvedDecl)) { + return; + } + + const nameNode = resolvedDecl.type === DeclarationType.Class ? resolvedDecl.node.d.name : node; + this._addResultsPart(parts, '(class) ' + nameNode.d.value, /* python */ true); + this._addDocumentationPart(parts, node, resolvedDecl); + break; + } + + case DeclarationType.Function: { + let label = 'function'; + let isProperty = false; + if (resolvedDecl.isMethod) { + const declaredType = this._evaluator.getTypeForDeclaration(resolvedDecl)?.type; + isProperty = !!declaredType && isMaybeDescriptorInstance(declaredType, /* requireSetter */ false); + label = isProperty ? 'property' : 'method'; + } + + let type = this._getType(node); + const resolvedType = this._getType(resolvedDecl.node.d.name); + type = isAnyOrUnknown(type) ? resolvedType : type; + + // If this function is decorated with @functools.wraps, show the wrapped function's signature. + if (isFunction(type)) { + const wrappedType = getWrappedFunctionType(resolvedDecl as FunctionDeclaration, this._evaluator); + if (wrappedType) { + type = wrappedType; + } + } + const signatureString = getToolTipForType( + type, + label, + node.d.value, + this._evaluator, + isProperty, + this._functionSignatureDisplay, + /* typeNode */ undefined, + this._sourceMapper + ); + + this._addResultsPart(parts, signatureString, /* python */ true); + this._addDocumentationPart(parts, node, resolvedDecl); + break; + } + + case DeclarationType.Alias: { + // First the 'module' header. + this._addResultsPart(parts, '(module) ' + node.d.value, /* python */ true); + this._addDocumentationPart(parts, node, resolvedDecl); + break; + } + + case DeclarationType.TypeAlias: { + const type = convertToInstance(this._getType(node)); + const typeText = this._evaluator.printType(type, { expandTypeAlias: true }); + this._addResultsPart(parts, `(type) ${node.d.value} = ${typeText}`, /* python */ true); + this._addDocumentationPart(parts, node, resolvedDecl); + break; + } + + default: + assertNever(resolvedDecl); + } + } + + private _addResultsForSynthesizedType(parts: HoverTextPart[], typeInfo: SynthesizedTypeInfo, hoverNode: NameNode) { + let typeText: string | undefined; + + if (isModule(typeInfo.type)) { + typeText = '(module) ' + hoverNode.d.value; + } else { + const node = typeInfo.node ?? hoverNode; + + const type = this._getType(node); + typeText = getVariableTypeText( + this._evaluator, + /* declaration */ undefined, + node.d.value, + type, + node, + this._functionSignatureDisplay, + this._sourceMapper + ); + } + + if (typeText) { + this._addResultsPart(parts, typeText, /* python */ true); + } + } + + private _tryAddPartsForTypedDictKey(node: StringNode, type: Type, parts: HoverTextPart[]) { + // If the expected type is a TypedDict and the current node is a key entry then we can provide a tooltip + // with the type of the TypedDict key and its docstring, if available. + const seenEntries = new Set(); + doForEachSubtype(type, (subtype) => { + if (isClassInstance(subtype) && ClassType.isTypedDictClass(subtype)) { + const entry = subtype.shared.typedDictEntries?.knownItems.get(node.d.value); + if (entry) { + // e.g. (key) name: str + const text = '(key) ' + node.d.value + ': ' + this._evaluator.printType(entry.valueType); + + const declarations = ClassType.getSymbolTable(subtype).get(node.d.value)?.getDeclarations(); + let declarationWithDoc: Declaration | undefined; + let docString = ''; + if (declarations !== undefined && declarations?.length !== 0) { + // As we are just interested in the docString we don't have to worry about + // anything other than the first declaration. There also shouldn't be more + // than one declaration for a TypedDict key variable. + const declaration = declarations[0]; + if (declaration.type === DeclarationType.Variable && declaration.docString !== undefined) { + declarationWithDoc = declaration; + docString = declaration.docString; + } + } + + const dedupKey = `${text}\n${docString}`; + if (seenEntries.has(dedupKey)) { + return; + } + + seenEntries.add(dedupKey); + + // If we have already added parts for another declaration (e.g. for a union of TypedDicts that share the same key) + // then we need to add a separator to prevent a visual bug. + if (parts.length > 0) { + parts.push({ text: '\n\n---\n' }); + } + + this._addResultsPart(parts, text, /* python */ true); + + if (declarationWithDoc) { + this._addDocumentationPartForType(parts, subtype, declarationWithDoc); + } + } + } + }); + } + + private _addInitOrNewMethodInsteadIfCallNode(node: NameNode, parts: HoverTextPart[], declaration: Declaration) { + const result = getClassAndConstructorTypes(node, this._evaluator); + if (!result) { + return false; + } + + if (result.methodType && isFunctionOrOverloaded(result.methodType)) { + this._addResultsPart( + parts, + getConstructorTooltip(node.d.value, result.methodType, this._evaluator, this._functionSignatureDisplay), + /* python */ true + ); + + const addedDoc = this._addDocumentationPartForType(parts, result.methodType, declaration); + + if (!addedDoc) { + this._addDocumentationPartForType(parts, result.classType, declaration); + } + return true; + } + return false; + } + + private _getType(node: ExpressionNode) { + // It does common work necessary for hover for a type we got + // from raw type evaluator. + return getTypeForToolTip(this._evaluator, node); + } + + private _getTypeText(node: ExpressionNode, options?: PrintTypeOptions): string { + const type = this._getType(node); + return ': ' + this._evaluator.printType(type, options); + } + + private _addDocumentationPart(parts: HoverTextPart[], node: NameNode, resolvedDecl: Declaration | undefined) { + const type = this._getType(node); + this._addDocumentationPartForType(parts, type, resolvedDecl, node.d.value); + } + + private _addDocumentationPartForType( + parts: HoverTextPart[], + type: Type | undefined, + resolvedDecl: Declaration | undefined, + name?: string + ): boolean { + const documentation = getDocumentationPartsForTypeAndDeclWithSource( + this._sourceMapper, + type, + resolvedDecl, + this._evaluator, + { + name, + } + ); + + addDocumentationResultsPart( + this._program.serviceProvider, + documentation?.text, + this._format, + parts, + resolvedDecl, + documentation?.forceLiteral + ); + return !!documentation?.text; + } + + private _addResultsPart(parts: HoverTextPart[], text: string, python = false) { + parts.push({ + python, + text, + }); + } +} diff --git a/packages/vipeye-internal/src/languageService/semanticTokenProvider.ts b/packages/vipeye-internal/src/languageService/semanticTokenProvider.ts index da5e445f..9ccc90ab 100644 --- a/packages/vipeye-internal/src/languageService/semanticTokenProvider.ts +++ b/packages/vipeye-internal/src/languageService/semanticTokenProvider.ts @@ -1,676 +1,778 @@ -/* - * semanticTokenProvider.ts - * - * Vipeye 语义高亮提供器。 - * 基于 LSP 3.16 SemanticTokens 协议,复用 Vipeye 的 TypeEvaluator 判断符号定义状态。 - * - * 缓存策略(模仿 Pylance 闭源设计): - * - 内存缓存:Map,文档变更时失效 - * - 磁盘缓存:序列化 token 列表到临时目录,按文件内容 hash 索引 - * - 增量计算:仅当文档分析完成且 hash 变化时重新生成 - * - * Phase A 实现目标: - * - 区分已定义变量(蓝色)和未定义变量(白色/undefined) - * - 基础类型分类:class/function/parameter/namespace - */ - -import { Declaration, DeclarationType, VariableDeclaration } from '../analyzer/declaration'; -import { ParseTreeWalker } from '../analyzer/parseTreeWalker'; -import { isWithinTypeAnnotation } from '../analyzer/parseTreeUtils'; -import { TypeCategory } from '../analyzer/types'; -import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes'; -import { LogTracker } from '../common/logTracker'; -import { ProgramView } from '../common/extensibility'; -import { convertOffsetToPosition } from '../common/positionUtils'; -import { TextRange } from '../common/textRange'; -import { TextRangeCollection } from '../common/textRangeCollection'; -import { Uri } from '../common/uri/uri'; -import { - AssignmentNode, - ClassNode, - DecoratorNode, - FormatStringNode, - FunctionNode, - MemberAccessNode, - ModuleNameNode, - ModuleNode, - NameNode, - NumberNode, - ParseNodeType, - StringNode, - TypeAnnotationNode, -} from '../parser/parseNodes'; -import { SemanticTokenModifiers, SemanticTokenTypes } from './semanticTokenTypes'; - -// Node.js 文件系统模块(用于磁盘缓存) -import * as fs from 'fs'; -import * as path from 'path'; - -/** 磁盘缓存条目的最大存活时间(7 天,单位毫秒) */ -const DISK_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; - -/** - * Viper 内置类型名集合。 - * 当符号声明为 Class/Function 且名字在此集合中时,使用 `builtin` token 类型。 - * 包含 Viper 语义下的基本类型(int/str/bytes 等)和 Python 兼容内置。 - */ -const BUILTIN_TYPE_NAMES = new Set([ - // 基本类型(Viper 语义:int=i32, str=bytes=i8*, 等) - 'int', 'str', 'bytes', 'bool', 'float', 'complex', - 'list', 'dict', 'set', 'tuple', 'frozenset', 'bytearray', 'memoryview', - 'type', 'object', 'None', 'True', 'False', - // Viper 特有 - 't', 'c', -]); - -/** LSP SemanticTokens 相对编码格式:[deltaLine, deltaStart, length, tokenType, tokenModifiers] */ -type SemanticTokenData = number[]; - -/** 单个 token 原始数据 */ -interface RawToken { - line: number; - startChar: number; - length: number; - tokenType: number; - tokenModifiers: number; -} - -/** 文件缓存条目 */ -interface CacheEntry { - /** 文件内容 hash(用于缓存命中判断) */ - contentHash: string; - /** 编码后的 token 数据 */ - data: SemanticTokenData; - /** 生成时间戳 */ - timestamp: number; -} - -/** - * 语义 token 提供器。 - * 通过遍历 ParseTree 中的 NameNode,结合 TypeEvaluator 判断符号定义状态, - * 生成 LSP SemanticTokens 数据。 - */ -export class SemanticTokenProvider { - /** 内存缓存:uri → 缓存条目 */ - private readonly _memoryCache = new Map(); - /** 日志跟踪器 */ - private readonly _log: LogTracker; - /** 磁盘缓存目录(可选,Phase A 预留接口) */ - private readonly _cacheDir: string | undefined; - - constructor(log: LogTracker, cacheDir?: string) { - this._log = log; - this._cacheDir = cacheDir; - } - - /** - * 提供完整文档的语义 token。 - * @param program Vipeye ProgramView 实例 - * @param fileUri 文件 URI - * @returns LSP SemanticTokens(相对编码),若无分析结果返回 undefined - */ - provideFull(program: ProgramView, fileUri: Uri): { data: SemanticTokenData } | undefined { - return this._log.log(`SemanticTokenProvider.provideFull ${fileUri}`, () => { - const parseResults = program.getParseResults(fileUri); - if (!parseResults) { - return undefined; - } - - const parserOutput = parseResults.parserOutput; - if (!parserOutput) { - return undefined; - } - - // 计算文件内容 hash,用于缓存命中判断 - const contentHash = this._computeHash(parseResults.text); - const cacheKey = fileUri.toString(); - - // 内存缓存命中 - const cached = this._memoryCache.get(cacheKey); - if (cached && cached.contentHash === contentHash) { - return { data: cached.data }; - } - - // 磁盘缓存命中(若启用,Phase A 预留) - if (this._cacheDir) { - const diskCached = this._tryLoadFromDisk(cacheKey, contentHash); - if (diskCached) { - this._memoryCache.set(cacheKey, diskCached); - return { data: diskCached.data }; - } - } - - // 生成新 token - const evaluator = program.evaluator; - if (!evaluator) { - return undefined; - } - - const tokens = this._collectTokens(parserOutput.parseTree, parserOutput.lines, evaluator); - const data = this._encodeTokens(tokens); - - const entry: CacheEntry = { - contentHash, - data, - timestamp: Date.now(), - }; - this._memoryCache.set(cacheKey, entry); - - // 异步写入磁盘缓存(Phase A 预留) - if (this._cacheDir) { - this._trySaveToDisk(cacheKey, entry); - } - - return { data }; - }); - } - - /** - * 提供指定范围的语义 token。 - * 当前实现先生成完整 token 再过滤,后续可优化为仅遍历范围节点。 - */ - provideRange( - program: ProgramView, - fileUri: Uri, - range: { startLine: number; endLine: number } - ): { data: SemanticTokenData } | undefined { - const full = this.provideFull(program, fileUri); - if (!full) { - return undefined; - } - - // 解码后过滤范围,再重新编码 - const tokens = this._decodeTokens(full.data); - const filtered = tokens.filter( - (t) => t.line >= range.startLine && t.line <= range.endLine - ); - return { data: this._encodeTokens(filtered) }; - } - - /** 使指定文件的缓存失效(文档变更时调用) */ - invalidate(uri: Uri): void { - this._memoryCache.delete(uri.toString()); - // 同步删除磁盘缓存(避免下次加载到过期数据) - if (this._cacheDir) { - this._tryDeleteFromDisk(this._getCacheFilePath(uri.toString())); - } - } - - /** 清空所有缓存(内存 + 磁盘) */ - clear(): void { - this._memoryCache.clear(); - if (this._cacheDir) { - try { - const entries = fs.readdirSync(this._cacheDir); - for (const entry of entries) { - if (entry.endsWith('.json')) { - fs.unlinkSync(path.join(this._cacheDir, entry)); - } - } - } catch { - // 忽略清理失败 - } - } - } - - /** - * 遍历 ParseTree 收集所有 NameNode 的语义 token。 - * 使用 ParseTreeWalker 确保遍历完整性。 - */ - private _collectTokens( - parseTree: ModuleNode, - lines: TextRangeCollection, - evaluator: TypeEvaluator - ): RawToken[] { - const tokens: RawToken[] = []; - const walker = new SemanticTokenWalker(evaluator, lines); - walker.walk(parseTree); - - // 按位置排序(LSP 要求) - for (const token of walker.tokens) { - tokens.push(token); - } - tokens.sort((a, b) => { - if (a.line !== b.line) return a.line - b.line; - return a.startChar - b.startChar; - }); - - return tokens; - } - - /** - * 将 RawToken 列表编码为 LSP 相对编码格式。 - * 每个 token 占 5 个数字:[deltaLine, deltaStart, length, tokenType, tokenModifiers] - */ - private _encodeTokens(tokens: RawToken[]): SemanticTokenData { - const data: SemanticTokenData = []; - let prevLine = 0; - let prevStartChar = 0; - - for (const token of tokens) { - const deltaLine = token.line - prevLine; - const deltaStart = deltaLine === 0 ? token.startChar - prevStartChar : token.startChar; - - data.push(deltaLine, deltaStart, token.length, token.tokenType, token.tokenModifiers); - - prevLine = token.line; - prevStartChar = token.startChar; - } - - return data; - } - - /** 将 LSP 相对编码解码为 RawToken 列表(用于 range 过滤) */ - private _decodeTokens(data: SemanticTokenData): RawToken[] { - const tokens: RawToken[] = []; - let prevLine = 0; - let prevStartChar = 0; - - for (let i = 0; i < data.length; i += 5) { - const deltaLine = data[i]; - const deltaStart = data[i + 1]; - const length = data[i + 2]; - const tokenType = data[i + 3]; - const tokenModifiers = data[i + 4]; - - const line = prevLine + deltaLine; - const startChar = deltaLine === 0 ? prevStartChar + deltaStart : deltaStart; - - tokens.push({ - line, - startChar, - length, - tokenType, - tokenModifiers, - }); - - prevLine = line; - prevStartChar = startChar; - } - - return tokens; - } - - /** 计算字符串 hash(简单 FNV-1a 变体,足够缓存命中判断) */ - private _computeHash(text: string): string { - let hash = 2166136261; - for (let i = 0; i < text.length; i++) { - hash ^= text.charCodeAt(i); - hash = Math.imul(hash, 16777619); - } - return (hash >>> 0).toString(16); - } - - /** 根据缓存 key(uri 字符串)计算磁盘缓存文件路径 */ - private _getCacheFilePath(key: string): string { - const uriHash = this._computeHash(key); - return path.join(this._cacheDir!, `${uriHash}.json`); - } - - /** - * 尝试从磁盘加载缓存。 - * 同步读取(在关键路径上),验证 contentHash 并检查过期时间。 - */ - private _tryLoadFromDisk(key: string, expectedHash: string): CacheEntry | undefined { - if (!this._cacheDir) { - return undefined; - } - - const filePath = this._getCacheFilePath(key); - try { - if (!fs.existsSync(filePath)) { - return undefined; - } - - const stat = fs.statSync(filePath); - const age = Date.now() - stat.mtimeMs; - if (age > DISK_CACHE_MAX_AGE_MS) { - // 过期:删除并返回未命中 - this._tryDeleteFromDisk(filePath); - return undefined; - } - - const content = fs.readFileSync(filePath, 'utf8'); - const entry = JSON.parse(content) as CacheEntry; - - // 验证 contentHash(确保文件内容未变) - if (entry.contentHash !== expectedHash) { - return undefined; - } - - return entry; - } catch { - // 读取/解析失败:忽略,视为未命中 - return undefined; - } - } - - /** - * 尝试保存缓存到磁盘。 - * 异步写入(不阻塞响应路径),失败时静默忽略。 - */ - private _trySaveToDisk(key: string, entry: CacheEntry): void { - if (!this._cacheDir) { - return; - } - - const filePath = this._getCacheFilePath(key); - // 序列化(含 contentHash 用于加载时验证) - const content = JSON.stringify(entry); - - // 异步写入:fire-and-forget,错误时仅记录日志 - fs.promises - .writeFile(filePath, content, 'utf8') - .catch(() => { - // 写入失败不影响功能,仅忽略 - }); - } - - /** 尝试删除磁盘缓存文件(忽略错误) */ - private _tryDeleteFromDisk(filePath: string): void { - try { - if (fs.existsSync(filePath)) { - fs.unlinkSync(filePath); - } - } catch { - // 删除失败不影响功能 - } - } -} - -/** - * ParseTree 遍历器,访问所有 NameNode 并生成语义 token。 - */ -class SemanticTokenWalker extends ParseTreeWalker { - /** 收集到的 token 列表 */ - readonly tokens: RawToken[] = []; - private readonly _evaluator: TypeEvaluator; - /** 文件行集合(用于偏移转换) */ - private readonly _lines: TextRangeCollection; - - constructor(evaluator: TypeEvaluator, lines: TextRangeCollection) { - super(); - this._evaluator = evaluator; - this._lines = lines; - } - - /** 访问 NameNode:核心语义分类逻辑 */ - override visitName(node: NameNode): boolean { - const token = this._classifyNameNode(node); - if (token) { - this.tokens.push(token); - } - return true; - } - - /** 访问 ModuleNameNode:import 语句中的模块名路径部分 → namespace。 - * 返回 false 阻止遍历 nameParts(避免重复 token,因 visitName 也会处理) */ - override visitModuleName(node: ModuleNameNode): boolean { - for (const part of node.d.nameParts) { - const position = convertOffsetToPosition(part.start, this._lines); - if (position && part.length > 0) { - this.tokens.push({ - line: position.line, - startChar: position.character, - length: part.length, - tokenType: SemanticTokenTypes.namespace, - tokenModifiers: 0, - }); - } - } - return false; - } - - /** 访问 MemberAccessNode:成员访问的 .name → 按 member 声明分类。 - * 返回 false 阻止遍历 member(避免 visitName 重复处理),手动遍历 leftExpr。 - * 使用 _classifyNameNode 复用声明信息查询逻辑,确保类型别名/类/函数等 - * 在成员访问位置(如 t.CInt)也能正确分类。 */ - override visitMemberAccess(node: MemberAccessNode): boolean { - const member = node.d.member; - // 复用 _classifyNameNode 逻辑(基于 getDeclInfoForNameNode + getType) - const token = this._classifyNameNode(member); - if (token) { - this.tokens.push(token); - } - // 手动遍历左侧表达式(如 obj.prop 中的 obj) - this.walk(node.d.leftExpr); - return false; - } - - /** 访问 StringNode:字符串字面量 → string。 - * Viper 扩展:在类型注解上下文中的字符串(前向引用)不生成 string token, - * 让 TextMate 语法规则处理高亮(显示为类型颜色而非字符串颜色)。 */ - override visitString(node: StringNode): boolean { - if (isWithinTypeAnnotation(node, /* requireQuotedAnnotation */ false)) { - return true; - } - this._pushLiteralToken(node.start, node.length, SemanticTokenTypes.string); - return true; - } - - /** 访问 DecoratorNode:@decoratorName → decorator 类型。 - * 返回 false 阻止默认遍历 expr,避免重复 token */ - override visitDecorator(node: DecoratorNode): boolean { - const expr = node.d.expr; - - if (expr.nodeType === ParseNodeType.Name) { - // 简单装饰器:@decoratorName - const position = convertOffsetToPosition(expr.start, this._lines); - if (position && expr.length > 0) { - this.tokens.push({ - line: position.line, - startChar: position.character, - length: expr.length, - tokenType: SemanticTokenTypes.decorator, - tokenModifiers: 0, - }); - } - return false; - } - - // 复杂装饰器:@module.decoratorName 或 @decorator(args) - // 手动遍历 expr,让内部节点按正常逻辑分类 - this.walk(expr); - return false; - } - - /** 访问 NumberNode:数字字面量 → number */ - override visitNumber(node: NumberNode): boolean { - this._pushLiteralToken(node.start, node.length, SemanticTokenTypes.number); - return true; - } - - /** 辅助:推入字面量 token(string/number 等) */ - private _pushLiteralToken(start: number, length: number, tokenType: number): void { - if (length <= 0) { - return; - } - const position = convertOffsetToPosition(start, this._lines); - if (!position) { - return; - } - this.tokens.push({ - line: position.line, - startChar: position.character, - length, - tokenType, - tokenModifiers: 0, - }); - } - - /** - * 分类 NameNode 的语义类型。 - * 通过 getDeclInfoForNameNode 判断是否已定义,再根据 DeclarationType 和 TypeCategory 细分。 - * - * 完善功能: - * - definition 修饰符:当 NameNode 处于声明位置时添加 - * - self/cls 参数:特殊处理为 parameter + readonly - * - 常量识别:全大写名变量添加 readonly 修饰符 - */ - private _classifyNameNode(node: NameNode): RawToken | undefined { - const length = node.length; - if (length <= 0) { - return undefined; - } - - // 查询符号声明信息 - const declInfo = this._evaluator.getDeclInfoForNameNode(node); - const type = this._evaluator.getType(node); - - let tokenType: number; - let tokenModifiers = 0; - const nameText = node.d.value; - - // 判断是否处于声明位置(定义点 vs 引用点) - const isDefinition = this._isDefinitionSite(node); - const defMod = isDefinition - ? SemanticTokenModifiers.declaration | SemanticTokenModifiers.definition - : 0; - - if (!declInfo || !declInfo.decls || declInfo.decls.length === 0) { - // 未定义符号 → 白色(默认前景) - tokenType = SemanticTokenTypes.undefined; - } else { - const decl: Declaration | undefined = declInfo.decls[0]; - const declType = decl?.type; - - // 根据声明类型分类 - switch (declType) { - case DeclarationType.Class: - case DeclarationType.SpecialBuiltInClass: - // 内置类型(int/str/bytes 等)→ builtin 类型 - if (BUILTIN_TYPE_NAMES.has(nameText)) { - tokenType = SemanticTokenTypes.builtin; - } else { - tokenType = SemanticTokenTypes.class; - } - tokenModifiers |= defMod; - break; - case DeclarationType.Function: - tokenType = SemanticTokenTypes.function; - tokenModifiers |= defMod; - break; - case DeclarationType.Param: - tokenType = SemanticTokenTypes.parameter; - // self/cls 参数:添加 readonly 修饰符 - if (nameText === 'self' || nameText === 'cls') { - tokenModifiers |= SemanticTokenModifiers.readonly; - } - break; - case DeclarationType.Alias: - // import 的模块别名 - tokenType = SemanticTokenTypes.namespace; - tokenModifiers |= defMod; - break; - case DeclarationType.TypeAlias: - tokenType = SemanticTokenTypes.type; - tokenModifiers |= defMod; - break; - case DeclarationType.TypeParam: - tokenType = SemanticTokenTypes.type; - tokenModifiers |= defMod; - break; - case DeclarationType.Intrinsic: - // 内置符号(None/True/False/__class__ 等)→ builtin + readonly - tokenType = SemanticTokenTypes.builtin; - tokenModifiers |= SemanticTokenModifiers.readonly; - break; - case DeclarationType.Variable: { - // 检查是否是类型别名声明(`Name: TypeAlias = type`) - const varDecl = decl as VariableDeclaration; - if (varDecl.typeAliasName && varDecl.typeAnnotationNode) { - const annNode = varDecl.typeAnnotationNode as any; - if (annNode.nodeType === ParseNodeType.Name && annNode.d?.value === 'TypeAlias') { - tokenType = SemanticTokenTypes.type; - tokenModifiers |= defMod; - break; - } - } - // 普通变量 → variable(蓝色) - tokenType = SemanticTokenTypes.variable; - tokenModifiers |= defMod; - // 常量识别:全大写名(含下划线/数字)添加 readonly 修饰符 - if (/^[A-Z][A-Z0-9_]*$/.test(nameText) && nameText.length > 1) { - tokenModifiers |= SemanticTokenModifiers.readonly; - } - break; - } - default: - // 根据推导类型进一步分类 - if (type) { - if (type.category === TypeCategory.Class) { - tokenType = BUILTIN_TYPE_NAMES.has(nameText) - ? SemanticTokenTypes.builtin - : SemanticTokenTypes.class; - } else if (type.category === TypeCategory.Function || - type.category === TypeCategory.Overloaded) { - tokenType = SemanticTokenTypes.function; - } else if (type.category === TypeCategory.Module) { - tokenType = SemanticTokenTypes.namespace; - } else if (type.category === TypeCategory.TypeVar) { - tokenType = SemanticTokenTypes.type; - } else { - tokenType = SemanticTokenTypes.variable; - } - } else { - tokenType = SemanticTokenTypes.variable; - } - tokenModifiers |= defMod; - break; - } - } - - // 转换偏移为 line/char - const position = convertOffsetToPosition(node.start, this._lines); - if (!position) { - return undefined; - } - - return { - line: position.line, - startChar: position.character, - length, - tokenType, - tokenModifiers, - }; - } - - /** - * 判断 NameNode 是否处于声明位置(定义点而非引用点)。 - * 通过检查父节点类型和字段位置确定。 - * - * 判定规则: - * - FunctionNode.d.name:函数定义名 - * - ClassNode.d.name:类定义名 - * - AssignmentNode.d.leftExpr(当 leftExpr 是 NameNode):赋值左侧 - * - TypeAnnotationNode.d.valueExpr(当 valueExpr 是 NameNode):类型注解左侧 - */ - private _isDefinitionSite(node: NameNode): boolean { - const parent = node.parent; - if (!parent) { - return false; - } - - // 函数定义名:def foo() 中的 foo - if (parent.nodeType === ParseNodeType.Function) { - return (parent as FunctionNode).d.name === node; - } - - // 类定义名:class Foo 中的 Foo - if (parent.nodeType === ParseNodeType.Class) { - return (parent as ClassNode).d.name === node; - } - - // 赋值左侧:foo = 1 中的 foo - if (parent.nodeType === ParseNodeType.Assignment) { - return (parent as AssignmentNode).d.leftExpr === node; - } - - // 类型注解左侧:foo: int 中的 foo - if (parent.nodeType === ParseNodeType.TypeAnnotation) { - return (parent as TypeAnnotationNode).d.valueExpr === node; - } - - return false; - } -} +/* + * semanticTokenProvider.ts + * + * Vipeye 语义高亮提供器。 + * 基于 LSP 3.16 SemanticTokens 协议,复用 Vipeye 的 TypeEvaluator 判断符号定义状态。 + * + * 缓存策略(模仿 Pylance 闭源设计): + * - 内存缓存:Map,文档变更时失效 + * - 磁盘缓存:序列化 token 列表到临时目录,按文件内容 hash 索引 + * - 增量计算:仅当文档分析完成且 hash 变化时重新生成 + * + * Phase A 实现目标: + * - 区分已定义变量(蓝色)和未定义变量(白色/undefined) + * - 基础类型分类:class/function/parameter/namespace + */ + +import { Declaration, DeclarationType, VariableDeclaration } from '../analyzer/declaration'; +import { ParseTreeWalker } from '../analyzer/parseTreeWalker'; +import { isWithinTypeAnnotation } from '../analyzer/parseTreeUtils'; +import { TypeCategory } from '../analyzer/types'; +import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes'; +import { LogTracker } from '../common/logTracker'; +import { ProgramView } from '../common/extensibility'; +import { convertOffsetToPosition } from '../common/positionUtils'; +import { TextRange } from '../common/textRange'; +import { TextRangeCollection } from '../common/textRangeCollection'; +import { Uri } from '../common/uri/uri'; +import { + AssignmentNode, + ClassNode, + DecoratorNode, + FormatStringNode, + FunctionNode, + MemberAccessNode, + ModuleNameNode, + ModuleNode, + NameNode, + NumberNode, + ParseNodeType, + StringNode, + TypeAnnotationNode, +} from '../parser/parseNodes'; +import { Token } from '../parser/tokenizerTypes'; +import { SemanticTokenModifiers, SemanticTokenTypes } from './semanticTokenTypes'; + +// Node.js 文件系统模块(用于磁盘缓存) +import * as fs from 'fs'; +import * as path from 'path'; + +/** 磁盘缓存条目的最大存活时间(7 天,单位毫秒) */ +const DISK_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; + +/** + * Viper 内置类型名集合。 + * 当符号声明为 Class/Function 且名字在此集合中时,使用 `builtin` token 类型。 + * 包含 Viper 语义下的基本类型(int/str/bytes 等)和 Python 兼容内置。 + */ +const BUILTIN_TYPE_NAMES = new Set([ + // 基本类型(Viper 语义:int=i32, str=bytes=i8*, 等) + // 注意:这些类型在 semantic token 中作为 class 处理(非 builtin), + // 以便正确显示为 class 颜色而非 builtin 颜色。 + 'int', 'str', 'bytes', 'bool', 'float', 'complex', + 'list', 'dict', 'set', 'tuple', 'frozenset', 'bytearray', 'memoryview', + 'type', 'object', 'None', 'True', 'False', + // Viper 特有 + 't', 'c', +]); + +/** 内置类型类名(用于 semantic token 中作为 class 而非 builtin 处理) */ +const BUILTIN_CLASS_NAMES = new Set([ + 'int', 'str', 'bytes', 'bool', 'float', 'complex', + 'list', 'dict', 'set', 'tuple', 'frozenset', 'bytearray', 'memoryview', + 'type', 'object', +]); + +/** LSP SemanticTokens 相对编码格式:[deltaLine, deltaStart, length, tokenType, tokenModifiers] */ +type SemanticTokenData = number[]; + +/** 单个 token 原始数据 */ +interface RawToken { + line: number; + startChar: number; + length: number; + tokenType: number; + tokenModifiers: number; +} + +/** 文件缓存条目 */ +interface CacheEntry { + /** 文件内容 hash(用于缓存命中判断) */ + contentHash: string; + /** 编码后的 token 数据 */ + data: SemanticTokenData; + /** 生成时间戳 */ + timestamp: number; +} + +/** + * 语义 token 提供器。 + * 通过遍历 ParseTree 中的 NameNode,结合 TypeEvaluator 判断符号定义状态, + * 生成 LSP SemanticTokens 数据。 + */ +export class SemanticTokenProvider { + /** 内存缓存:uri → 缓存条目 */ + private readonly _memoryCache = new Map(); + /** 日志跟踪器 */ + private readonly _log: LogTracker; + /** 磁盘缓存目录(可选,Phase A 预留接口) */ + private readonly _cacheDir: string | undefined; + + constructor(log: LogTracker, cacheDir?: string) { + this._log = log; + this._cacheDir = cacheDir; + } + + /** + * 提供完整文档的语义 token。 + * @param program Vipeye ProgramView 实例 + * @param fileUri 文件 URI + * @returns LSP SemanticTokens(相对编码),若无分析结果返回 undefined + */ + provideFull(program: ProgramView, fileUri: Uri): { data: SemanticTokenData } | undefined { + return this._log.log(`SemanticTokenProvider.provideFull ${fileUri}`, () => { + const parseResults = program.getParseResults(fileUri); + if (!parseResults) { + return undefined; + } + + const parserOutput = parseResults.parserOutput; + if (!parserOutput) { + return undefined; + } + + // 计算文件内容 hash,用于缓存命中判断 + const contentHash = this._computeHash(parseResults.text); + const cacheKey = fileUri.toString(); + + // 内存缓存命中 + const cached = this._memoryCache.get(cacheKey); + if (cached && cached.contentHash === contentHash) { + return { data: cached.data }; + } + + // 磁盘缓存命中(若启用,Phase A 预留) + if (this._cacheDir) { + const diskCached = this._tryLoadFromDisk(cacheKey, contentHash); + if (diskCached) { + this._memoryCache.set(cacheKey, diskCached); + return { data: diskCached.data }; + } + } + + // 生成新 token + const evaluator = program.evaluator; + if (!evaluator) { + return undefined; + } + + const tokens = this._collectTokens( + parserOutput.parseTree, + parserOutput.lines, + evaluator, + parseResults.tokenizerOutput + ); + const data = this._encodeTokens(tokens); + + const entry: CacheEntry = { + contentHash, + data, + timestamp: Date.now(), + }; + this._memoryCache.set(cacheKey, entry); + + // 异步写入磁盘缓存(Phase A 预留) + if (this._cacheDir) { + this._trySaveToDisk(cacheKey, entry); + } + + return { data }; + }); + } + + /** + * 提供指定范围的语义 token。 + * 当前实现先生成完整 token 再过滤,后续可优化为仅遍历范围节点。 + */ + provideRange( + program: ProgramView, + fileUri: Uri, + range: { startLine: number; endLine: number } + ): { data: SemanticTokenData } | undefined { + const full = this.provideFull(program, fileUri); + if (!full) { + return undefined; + } + + // 解码后过滤范围,再重新编码 + const tokens = this._decodeTokens(full.data); + const filtered = tokens.filter( + (t) => t.line >= range.startLine && t.line <= range.endLine + ); + return { data: this._encodeTokens(filtered) }; + } + + /** 使指定文件的缓存失效(文档变更时调用) */ + invalidate(uri: Uri): void { + this._memoryCache.delete(uri.toString()); + // 同步删除磁盘缓存(避免下次加载到过期数据) + if (this._cacheDir) { + this._tryDeleteFromDisk(this._getCacheFilePath(uri.toString())); + } + } + + /** 清空所有缓存(内存 + 磁盘) */ + clear(): void { + this._memoryCache.clear(); + if (this._cacheDir) { + try { + const entries = fs.readdirSync(this._cacheDir); + for (const entry of entries) { + if (entry.endsWith('.json')) { + fs.unlinkSync(path.join(this._cacheDir, entry)); + } + } + } catch { + // 忽略清理失败 + } + } + } + + /** + * 遍历 ParseTree 和 tokenizer token 流收集语义 token。 + * ParseTree 遍历:NameNode、MemberAccess、String、Number、Decorator 等。 + * Token 流遍历:关键字(KeywordToken)和注释(Comment)。 + * Viper 扩展:在 TextMate 规则被清空后,通过 LSP 语义高亮为关键字和注释着色。 + */ + private _collectTokens( + parseTree: ModuleNode, + lines: TextRangeCollection, + evaluator: TypeEvaluator, + tokenizerOutput?: { tokens: TextRangeCollection } + ): RawToken[] { + const tokens: RawToken[] = []; + + // 1. 遍历 tokenizer token 流,提取注释。 + // 注意:关键字(def/class/pass 等)不由 LSP semantic token 处理, + // 而是由 TextMate 规则高亮(keyword.control.viper scope → 主题蓝色)。 + // 这样即使 semanticTokenScopes 配置未完全加载,关键字也能正确着色。 + if (tokenizerOutput) { + const tokenStream = tokenizerOutput.tokens; + const tokenCount = tokenStream.count; + for (let i = 0; i < tokenCount; i++) { + const tok = tokenStream.getItemAt(i); + // 注释(每个 token 前可能附带注释) + if (tok.comments) { + for (const comment of tok.comments) { + this._pushTokenFromRange( + tokens, lines, comment.start, comment.length, + SemanticTokenTypes.comment + ); + } + } + } + } + + // 2. 遍历 ParseTree,提取 NameNode、String、Number、Decorator 等 + const walker = new SemanticTokenWalker(evaluator, lines); + walker.walk(parseTree); + for (const token of walker.tokens) { + tokens.push(token); + } + + // 按位置排序(LSP 要求) + tokens.sort((a, b) => { + if (a.line !== b.line) return a.line - b.line; + return a.startChar - b.startChar; + }); + + return tokens; + } + + /** 辅助:从 offset/length 创建 RawToken 并推入列表 */ + private _pushTokenFromRange( + tokens: RawToken[], + lines: TextRangeCollection, + start: number, + length: number, + tokenType: number + ): void { + if (length <= 0) { + return; + } + const position = convertOffsetToPosition(start, lines); + if (!position) { + return; + } + tokens.push({ + line: position.line, + startChar: position.character, + length, + tokenType, + tokenModifiers: 0, + }); + } + + /** + * 将 RawToken 列表编码为 LSP 相对编码格式。 + * 每个 token 占 5 个数字:[deltaLine, deltaStart, length, tokenType, tokenModifiers] + */ + private _encodeTokens(tokens: RawToken[]): SemanticTokenData { + const data: SemanticTokenData = []; + let prevLine = 0; + let prevStartChar = 0; + + for (const token of tokens) { + const deltaLine = token.line - prevLine; + const deltaStart = deltaLine === 0 ? token.startChar - prevStartChar : token.startChar; + + data.push(deltaLine, deltaStart, token.length, token.tokenType, token.tokenModifiers); + + prevLine = token.line; + prevStartChar = token.startChar; + } + + return data; + } + + /** 将 LSP 相对编码解码为 RawToken 列表(用于 range 过滤) */ + private _decodeTokens(data: SemanticTokenData): RawToken[] { + const tokens: RawToken[] = []; + let prevLine = 0; + let prevStartChar = 0; + + for (let i = 0; i < data.length; i += 5) { + const deltaLine = data[i]; + const deltaStart = data[i + 1]; + const length = data[i + 2]; + const tokenType = data[i + 3]; + const tokenModifiers = data[i + 4]; + + const line = prevLine + deltaLine; + const startChar = deltaLine === 0 ? prevStartChar + deltaStart : deltaStart; + + tokens.push({ + line, + startChar, + length, + tokenType, + tokenModifiers, + }); + + prevLine = line; + prevStartChar = startChar; + } + + return tokens; + } + + /** 计算字符串 hash(简单 FNV-1a 变体,足够缓存命中判断) */ + private _computeHash(text: string): string { + let hash = 2166136261; + for (let i = 0; i < text.length; i++) { + hash ^= text.charCodeAt(i); + hash = Math.imul(hash, 16777619); + } + return (hash >>> 0).toString(16); + } + + /** 根据缓存 key(uri 字符串)计算磁盘缓存文件路径 */ + private _getCacheFilePath(key: string): string { + const uriHash = this._computeHash(key); + return path.join(this._cacheDir!, `${uriHash}.json`); + } + + /** + * 尝试从磁盘加载缓存。 + * 同步读取(在关键路径上),验证 contentHash 并检查过期时间。 + */ + private _tryLoadFromDisk(key: string, expectedHash: string): CacheEntry | undefined { + if (!this._cacheDir) { + return undefined; + } + + const filePath = this._getCacheFilePath(key); + try { + if (!fs.existsSync(filePath)) { + return undefined; + } + + const stat = fs.statSync(filePath); + const age = Date.now() - stat.mtimeMs; + if (age > DISK_CACHE_MAX_AGE_MS) { + // 过期:删除并返回未命中 + this._tryDeleteFromDisk(filePath); + return undefined; + } + + const content = fs.readFileSync(filePath, 'utf8'); + const entry = JSON.parse(content) as CacheEntry; + + // 验证 contentHash(确保文件内容未变) + if (entry.contentHash !== expectedHash) { + return undefined; + } + + return entry; + } catch { + // 读取/解析失败:忽略,视为未命中 + return undefined; + } + } + + /** + * 尝试保存缓存到磁盘。 + * 异步写入(不阻塞响应路径),失败时静默忽略。 + */ + private _trySaveToDisk(key: string, entry: CacheEntry): void { + if (!this._cacheDir) { + return; + } + + const filePath = this._getCacheFilePath(key); + // 序列化(含 contentHash 用于加载时验证) + const content = JSON.stringify(entry); + + // 异步写入:fire-and-forget,错误时仅记录日志 + fs.promises + .writeFile(filePath, content, 'utf8') + .catch(() => { + // 写入失败不影响功能,仅忽略 + }); + } + + /** 尝试删除磁盘缓存文件(忽略错误) */ + private _tryDeleteFromDisk(filePath: string): void { + try { + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + } + } catch { + // 删除失败不影响功能 + } + } +} + +/** + * ParseTree 遍历器,访问所有 NameNode 并生成语义 token。 + */ +class SemanticTokenWalker extends ParseTreeWalker { + /** 收集到的 token 列表 */ + readonly tokens: RawToken[] = []; + private readonly _evaluator: TypeEvaluator; + /** 文件行集合(用于偏移转换) */ + private readonly _lines: TextRangeCollection; + + constructor(evaluator: TypeEvaluator, lines: TextRangeCollection) { + super(); + this._evaluator = evaluator; + this._lines = lines; + } + + /** 访问 NameNode:核心语义分类逻辑 */ + override visitName(node: NameNode): boolean { + const token = this._classifyNameNode(node); + if (token) { + this.tokens.push(token); + } + return true; + } + + /** 访问 ModuleNameNode:import 语句中的模块名路径部分 → namespace。 + * 返回 false 阻止遍历 nameParts(避免重复 token,因 visitName 也会处理) */ + override visitModuleName(node: ModuleNameNode): boolean { + for (const part of node.d.nameParts) { + const position = convertOffsetToPosition(part.start, this._lines); + if (position && part.length > 0) { + this.tokens.push({ + line: position.line, + startChar: position.character, + length: part.length, + tokenType: SemanticTokenTypes.namespace, + tokenModifiers: 0, + }); + } + } + return false; + } + + /** 访问 MemberAccessNode:成员访问的 .name → 按 member 声明分类。 + * 返回 false 阻止遍历 member(避免 visitName 重复处理),手动遍历 leftExpr。 + * 使用 _classifyNameNode 复用声明信息查询逻辑,确保类型别名/类/函数等 + * 在成员访问位置(如 t.CInt)也能正确分类。 */ + override visitMemberAccess(node: MemberAccessNode): boolean { + const member = node.d.member; + // 复用 _classifyNameNode 逻辑(基于 getDeclInfoForNameNode + getType) + const token = this._classifyNameNode(member); + if (token) { + this.tokens.push(token); + } + // 手动遍历左侧表达式(如 obj.prop 中的 obj) + this.walk(node.d.leftExpr); + return false; + } + + /** 访问 StringNode:字符串字面量 → string。 + * Viper 扩展:在类型注解上下文中的字符串(前向引用)不生成 string token, + * 让 TextMate 语法规则处理高亮(显示为类型颜色而非字符串颜色)。 */ + override visitString(node: StringNode): boolean { + if (isWithinTypeAnnotation(node, /* requireQuotedAnnotation */ false)) { + return true; + } + this._pushLiteralToken(node.start, node.length, SemanticTokenTypes.string); + return true; + } + + /** 访问 DecoratorNode:@decoratorName → decorator 类型。 + * 返回 false 阻止默认遍历 expr,避免重复 token */ + override visitDecorator(node: DecoratorNode): boolean { + const expr = node.d.expr; + + if (expr.nodeType === ParseNodeType.Name) { + // 简单装饰器:@decoratorName + const position = convertOffsetToPosition(expr.start, this._lines); + if (position && expr.length > 0) { + this.tokens.push({ + line: position.line, + startChar: position.character, + length: expr.length, + tokenType: SemanticTokenTypes.decorator, + tokenModifiers: 0, + }); + } + return false; + } + + // 复杂装饰器:@module.decoratorName 或 @decorator(args) + // 手动遍历 expr,让内部节点按正常逻辑分类 + this.walk(expr); + return false; + } + + /** 访问 NumberNode:数字字面量 → number */ + override visitNumber(node: NumberNode): boolean { + this._pushLiteralToken(node.start, node.length, SemanticTokenTypes.number); + return true; + } + + /** 辅助:推入字面量 token(string/number 等) */ + private _pushLiteralToken(start: number, length: number, tokenType: number): void { + if (length <= 0) { + return; + } + const position = convertOffsetToPosition(start, this._lines); + if (!position) { + return; + } + this.tokens.push({ + line: position.line, + startChar: position.character, + length, + tokenType, + tokenModifiers: 0, + }); + } + + /** + * 分类 NameNode 的语义类型。 + * 通过 getDeclInfoForNameNode 判断是否已定义,再根据 DeclarationType 和 TypeCategory 细分。 + * + * 完善功能: + * - definition 修饰符:当 NameNode 处于声明位置时添加 + * - self/cls 参数:特殊处理为 parameter + readonly + * - 常量识别:全大写名变量添加 readonly 修饰符 + */ + private _classifyNameNode(node: NameNode): RawToken | undefined { + const length = node.length; + if (length <= 0) { + return undefined; + } + + // 查询符号声明信息 + const declInfo = this._evaluator.getDeclInfoForNameNode(node); + const type = this._evaluator.getType(node); + + let tokenType: number; + let tokenModifiers = 0; + const nameText = node.d.value; + + // 判断是否处于声明位置(定义点 vs 引用点) + const isDefinition = this._isDefinitionSite(node); + const defMod = isDefinition + ? SemanticTokenModifiers.declaration | SemanticTokenModifiers.definition + : 0; + + if (!declInfo || !declInfo.decls || declInfo.decls.length === 0) { + // 未定义符号 → 白色(默认前景) + tokenType = SemanticTokenTypes.undefined; + } else { + const decl: Declaration | undefined = declInfo.decls[0]; + const declType = decl?.type; + + // 根据声明类型分类 + switch (declType) { + case DeclarationType.Class: + case DeclarationType.SpecialBuiltInClass: + // 内置类型类(int/str/bytes 等)→ class(非 builtin) + // None/True/False/t/c 等非类型类 → builtin + if (BUILTIN_CLASS_NAMES.has(nameText)) { + tokenType = SemanticTokenTypes.class; + } else if (BUILTIN_TYPE_NAMES.has(nameText)) { + tokenType = SemanticTokenTypes.builtin; + } else { + tokenType = SemanticTokenTypes.class; + } + tokenModifiers |= defMod; + break; + case DeclarationType.Function: + tokenType = SemanticTokenTypes.function; + tokenModifiers |= defMod; + break; + case DeclarationType.Param: + tokenType = SemanticTokenTypes.parameter; + // self/cls 参数:添加 readonly 修饰符 + if (nameText === 'self' || nameText === 'cls') { + tokenModifiers |= SemanticTokenModifiers.readonly; + } + break; + case DeclarationType.Alias: + // import 的模块别名 + tokenType = SemanticTokenTypes.namespace; + tokenModifiers |= defMod; + break; + case DeclarationType.TypeAlias: + tokenType = SemanticTokenTypes.type; + tokenModifiers |= defMod; + break; + case DeclarationType.TypeParam: + tokenType = SemanticTokenTypes.type; + tokenModifiers |= defMod; + break; + case DeclarationType.Intrinsic: + // 内置符号(None/True/False/__class__ 等)→ builtin + readonly + tokenType = SemanticTokenTypes.builtin; + tokenModifiers |= SemanticTokenModifiers.readonly; + break; + case DeclarationType.Variable: { + // Viper 扩展:当声明被解析为 Variable 但实际类型为 Module 时, + // 在类型注解上下文中实际引用的是模块名而非字段。 + // 覆盖场景:sheet: sheet.Sheet 中第二个 sheet 是模块名, + // 但因同名字段遮蔽返回 Variable 声明。getType 返回 Module 类型。 + if (type && type.category === TypeCategory.Module) { + tokenType = SemanticTokenTypes.namespace; + tokenModifiers |= defMod; + break; + } + // 检查是否是类型别名声明(`Name: TypeAlias = type` 或 `Name: t.CTypedef = type`) + const varDecl = decl as VariableDeclaration; + if (varDecl.typeAliasName && varDecl.typeAnnotationNode) { + const annNode = varDecl.typeAnnotationNode as any; + // 标准 TypeAlias 注解 + if (annNode.nodeType === ParseNodeType.Name && annNode.d?.value === 'TypeAlias') { + tokenType = SemanticTokenTypes.type; + tokenModifiers |= defMod; + break; + } + // Viper 扩展:t.CTypedef 注解(MemberAccess,成员名为 CTypedef) + if ( + annNode.nodeType === ParseNodeType.MemberAccess && + annNode.d?.member?.d?.value === 'CTypedef' + ) { + tokenType = SemanticTokenTypes.type; + tokenModifiers |= defMod; + break; + } + } + // Viper 扩展:即使没有 typeAliasName,也检查 typeAnnotationNode 是否为 t.CTypedef + // (CTypedef 声明可能不设置 typeAliasName,但仍是类型别名) + if (varDecl.typeAnnotationNode) { + const annNode = varDecl.typeAnnotationNode as any; + if ( + annNode.nodeType === ParseNodeType.MemberAccess && + annNode.d?.member?.d?.value === 'CTypedef' + ) { + tokenType = SemanticTokenTypes.type; + tokenModifiers |= defMod; + break; + } + } + // 普通变量 → variable(蓝色) + tokenType = SemanticTokenTypes.variable; + tokenModifiers |= defMod; + // 常量识别:全大写名(含下划线/数字)添加 readonly 修饰符 + if (/^[A-Z][A-Z0-9_]*$/.test(nameText) && nameText.length > 1) { + tokenModifiers |= SemanticTokenModifiers.readonly; + } + break; + } + default: + // 根据推导类型进一步分类 + if (type) { + if (type.category === TypeCategory.Class) { + tokenType = BUILTIN_CLASS_NAMES.has(nameText) + ? SemanticTokenTypes.class + : (BUILTIN_TYPE_NAMES.has(nameText) + ? SemanticTokenTypes.builtin + : SemanticTokenTypes.class); + } else if (type.category === TypeCategory.Function || + type.category === TypeCategory.Overloaded) { + tokenType = SemanticTokenTypes.function; + } else if (type.category === TypeCategory.Module) { + tokenType = SemanticTokenTypes.namespace; + } else if (type.category === TypeCategory.TypeVar) { + tokenType = SemanticTokenTypes.type; + } else { + tokenType = SemanticTokenTypes.variable; + } + } else { + tokenType = SemanticTokenTypes.variable; + } + tokenModifiers |= defMod; + break; + } + } + + // 转换偏移为 line/char + const position = convertOffsetToPosition(node.start, this._lines); + if (!position) { + return undefined; + } + + return { + line: position.line, + startChar: position.character, + length, + tokenType, + tokenModifiers, + }; + } + + /** + * 判断 NameNode 是否处于声明位置(定义点而非引用点)。 + * 通过检查父节点类型和字段位置确定。 + * + * 判定规则: + * - FunctionNode.d.name:函数定义名 + * - ClassNode.d.name:类定义名 + * - AssignmentNode.d.leftExpr(当 leftExpr 是 NameNode):赋值左侧 + * - TypeAnnotationNode.d.valueExpr(当 valueExpr 是 NameNode):类型注解左侧 + */ + private _isDefinitionSite(node: NameNode): boolean { + const parent = node.parent; + if (!parent) { + return false; + } + + // 函数定义名:def foo() 中的 foo + if (parent.nodeType === ParseNodeType.Function) { + return (parent as FunctionNode).d.name === node; + } + + // 类定义名:class Foo 中的 Foo + if (parent.nodeType === ParseNodeType.Class) { + return (parent as ClassNode).d.name === node; + } + + // 赋值左侧:foo = 1 中的 foo + if (parent.nodeType === ParseNodeType.Assignment) { + return (parent as AssignmentNode).d.leftExpr === node; + } + + // 类型注解左侧:foo: int 中的 foo + if (parent.nodeType === ParseNodeType.TypeAnnotation) { + return (parent as TypeAnnotationNode).d.valueExpr === node; + } + + return false; + } +} diff --git a/packages/vipeye-internal/src/localization/localize.ts b/packages/vipeye-internal/src/localization/localize.ts index a648953a..969e2009 100644 --- a/packages/vipeye-internal/src/localization/localize.ts +++ b/packages/vipeye-internal/src/localization/localize.ts @@ -594,6 +594,22 @@ export namespace Localizer { new ParameterizedString<{ importName: string; venv: string }>( getRawString('Diagnostic.importSourceResolveFailure') ); + export const viperNamespacePackageIncomplete = () => + new ParameterizedString<{ importName: string }>( + getRawString('Diagnostic.viperNamespacePackageIncomplete') + ); + export const viperUnresolvedImportName = () => + new ParameterizedString<{ name: string }>( + getRawString('Diagnostic.viperUnresolvedImportName') + ); + export const viperUnknownMemberAccess = () => + new ParameterizedString<{ memberName: string }>( + getRawString('Diagnostic.viperUnknownMemberAccess') + ); + export const viperGlobalAssignmentWithoutGlobal = () => + new ParameterizedString<{ name: string }>( + getRawString('Diagnostic.viperGlobalAssignmentWithoutGlobal') + ); export const importSymbolUnknown = () => new ParameterizedString<{ name: string }>(getRawString('Diagnostic.importSymbolUnknown')); export const incompatibleMethodOverride = () => diff --git a/packages/vipeye-internal/src/localization/package.nls.en-us.json b/packages/vipeye-internal/src/localization/package.nls.en-us.json index d5d06ecb..0562963f 100644 --- a/packages/vipeye-internal/src/localization/package.nls.en-us.json +++ b/packages/vipeye-internal/src/localization/package.nls.en-us.json @@ -641,6 +641,10 @@ "importDepthExceeded": "Import chain depth exceeded {depth}", "importResolveFailure": "Import \"{importName}\" could not be resolved", "importSourceResolveFailure": "Import \"{importName}\" could not be resolved from source", + "viperNamespacePackageIncomplete": "Namespace package \"{importName}\" is missing __init__.vp, possibly incomplete", + "viperUnresolvedImportName": "Unresolved import name \"{name}\"", + "viperUnknownMemberAccess": "Cannot access member \"{memberName}\" on unresolved type, object may be undefined", + "viperGlobalAssignmentWithoutGlobal": "Global variable \"{name}\" requires global declaration when writing inside a function", "importSymbolUnknown": "\"{name}\" is unknown import symbol", "incompatibleMethodOverride": "Method \"{name}\" overrides class \"{className}\" in an incompatible manner", "inconsistentIndent": "Unindent amount does not match previous indent", diff --git a/packages/vipeye-internal/src/localization/package.nls.zh-cn.json b/packages/vipeye-internal/src/localization/package.nls.zh-cn.json index 6c063628..2795e372 100644 --- a/packages/vipeye-internal/src/localization/package.nls.zh-cn.json +++ b/packages/vipeye-internal/src/localization/package.nls.zh-cn.json @@ -265,6 +265,10 @@ "importDepthExceeded": "导入链深度超过 {depth}", "importResolveFailure": "无法解析导入“{importName}”", "importSourceResolveFailure": "无法从源解析导入“{importName}”", + "viperNamespacePackageIncomplete": "命名空间包“{importName}”缺少 __init__.vp,疑似未完成", + "viperUnresolvedImportName": "无法解析的导入名称“{name}”", + "viperUnknownMemberAccess": "无法解析类型上的成员“{memberName}”访问,对象可能未定义", + "viperGlobalAssignmentWithoutGlobal": "全局变量“{name}”在函数内写入需要 global 声明", "importSymbolUnknown": "“{name}”是未知的导入符号", "incompatibleMethodOverride": "方法“{name}”以不兼容的方式替代类“{className}”", "inconsistentIndent": "取消缩进量与以前的缩进不匹配", diff --git a/packages/vipeye-internal/vipershed-fallback/stdlib/_typeshed/__init__.vpi b/packages/vipeye-internal/vipershed-fallback/stdlib/_typeshed/__init__.vpi index e4ffa101..785b2f31 100644 --- a/packages/vipeye-internal/vipershed-fallback/stdlib/_typeshed/__init__.vpi +++ b/packages/vipeye-internal/vipershed-fallback/stdlib/_typeshed/__init__.vpi @@ -1 +1,4 @@ # _typeshed 占位。 +# 定义 NoneType 供类型求值器使用(getNoneType() 通过 _typeshed.NoneType 查找)。 +# 直接定义而非从 types 导入,避免 _typeshed ↔ types 循环导入。 +class NoneType: ... diff --git a/packages/vipeye-internal/vipershed-fallback/stdlib/argparse.vpi b/packages/vipeye-internal/vipershed-fallback/stdlib/argparse.vpi deleted file mode 100644 index 8c1c5bca..00000000 --- a/packages/vipeye-internal/vipershed-fallback/stdlib/argparse.vpi +++ /dev/null @@ -1,206 +0,0 @@ -# Viper 命令行参数解析库 argparse 存根。 -# Python argparse 风格的 C 实现,依赖 memhub 和 hashtable。 -# 姑息策略: -# - 类型/动作常量映射为 int -# - _mbuddy 为模块级全局 MemBuddy 指针(int) -# - Argument/ParsedArgs/ArgumentParser 内部指针字段映射为 int - -import t -import string -import stdio -import memhub -import hashtable - - -# ============================================================ -# 类型常量 -# ============================================================ -STRING: int = 0 # 字符串类型 -INT: int = 1 # 整数类型 -BOOL: int = 2 # 布尔类型 - -# 动作常量 -STORE: int = 0 # 存储值(默认) -STORE_TRUE: int = 1 # 出现时存 True -STORE_FALSE: int = 2 # 出现时存 False -COUNT: int = 3 # 计数出现次数 - -# 限制 -MAX_ARGS: int = 32 # 单个 parser 最大参数数 - - -# 模块级 mbuddy 指针(由用户在 main 中赋值) -_mbuddy: int # memhub.MemBuddy | t.CPtr - - -# ============================================================ -# Argument - 参数定义结构体 -# ============================================================ -class Argument: - """命令行参数定义。""" - name: str # "--input" 或 "output"(位置参数) - short_name: str # "-i" 或 None - help_text: str # 帮助文本 - arg_type: int # STRING, INT, BOOL - action: int # STORE, STORE_TRUE, STORE_FALSE, COUNT - default_int: int # INT 类型默认值 - default_str: str # STRING 类型默认值 - required: bool # 是否必须 - is_positional: bool # 是否位置参数 - dest: str # 查找键名("--input" → "input") - - -# ============================================================ -# ParsedArgs - 解析结果 -# ============================================================ -class ParsedArgs: - """参数解析结果,内部用 HashTable 存储。""" - _ht: int # hashtable.HashTable 指针 - __mbuddy__: int # memhub.MemBuddy 指针 - - def __new__(self, mb: int) -> int: - """堆分配 ParsedArgs 对象。""" - return 0 - - def __init__(self, mb: int) -> None: - """初始化解析结果。 - - Args: - mb: MemBuddy 指针(int) - """ - ... - - def get_str(self, name: str) -> str: - """获取字符串值。 - - Args: - name: 键名(dest) - Returns: - 字符串值,键不存在返回 None - """ - return "" - - def get_int(self, name: str) -> int: - """获取整数值。 - - Args: - name: 键名(dest) - Returns: - 整数值,键不存在返回 0 - """ - return 0 - - def get_bool(self, name: str) -> int: - """获取布尔值(返回 1 或 0)。 - - Args: - name: 键名(dest) - Returns: - 1 或 0,键不存在返回 0 - """ - return 0 - - def has(self, name: str) -> bool: - """检查键是否存在。""" - return False - - def _set_str(self, name: str, val: str) -> None: ... - def _set_int(self, name: str, val: int) -> None: ... - def _set_bool(self, name: str, val: bool) -> None: ... - - -# ============================================================ -# ArgumentParser - 命令行参数解析器 -# ============================================================ -class ArgumentParser: - """命令行参数解析器。 - - 用法: - argparse._mbuddy = mb - parser = ArgumentParser("myprog", "A test", pool=mb) - parser.add_argument("--input", "-i", arg_type=argparse.STRING, - required=True, help="input file") - parser.add_argument("--count", "-c", arg_type=argparse.INT, - default=0, help="repeat count") - parser.add_argument("--verbose", "-v", action=argparse.STORE_TRUE, - help="verbose mode") - parser.add_argument("output", arg_type=argparse.STRING, - help="output path") - args = parser.parse_args(argc, argv) - if args is not None: - inp: str = args.get_str("input") - cnt: int = args.get_int("count") - verb: int = args.get_bool("verbose") - out: str = args.get_str("output") - argparse.release(args) - """ - _prog: str - _description: str - _args: int # Argument 数组基址(指针) - _arg_count: int - __mbuddy__: int # memhub.MemBuddy 指针 - - def __new__(self, prog: str = "", description: str = "", - pool: int = 0) -> int: - """堆分配 ArgumentParser 对象。""" - return 0 - - def __init__(self, prog: str = "", description: str = "", - pool: int = 0) -> None: - """初始化解析器。 - - Args: - prog: 程序名 - description: 描述文本 - pool: MemBuddy 指针(int),为 0 时使用模块级 _mbuddy - """ - ... - - def add_argument(self, name: str = "", short: str = "", - arg_type: int = 0, default: int = 0, - default_str: str = "", required: bool = False, - action: int = 0, help: str = "") -> None: - """注册一个命令行参数。 - - Args: - name: 参数名("--input" 或 "output") - short: 短选项("-i"),可选 - arg_type: 类型(STRING/INT/BOOL) - default: INT 默认值 - default_str: STRING 默认值 - required: 是否必须 - action: 动作(STORE/STORE_TRUE/STORE_FALSE/COUNT) - help: 帮助文本 - """ - ... - - def parse_args(self, argc: int, argv: int) -> int: - """解析命令行参数。 - - Args: - argc: 参数个数 - argv: 参数数组(char**,指针,int),argv[0] 为程序名 - Returns: - ParsedArgs 指针(int),失败返回 0(None) - """ - return 0 - - def print_help(self) -> None: - """打印帮助信息到 stdout。""" - ... - - def print_usage(self) -> None: - """打印用法简述到 stdout。""" - ... - - -# ============================================================ -# release - 释放 ParsedArgs 及其内部资源 -# ============================================================ -def release(args: int) -> None: - """释放 ParsedArgs 对象及其内部 HashTable。 - - Args: - args: ParsedArgs 指针(int),为 0(None)无操作 - """ - ... diff --git a/packages/vipeye-internal/vipershed-fallback/stdlib/c.vpi b/packages/vipeye-internal/vipershed-fallback/stdlib/c.vpi index 895793ef..0ffbe1c5 100644 --- a/packages/vipeye-internal/vipershed-fallback/stdlib/c.vpi +++ b/packages/vipeye-internal/vipershed-fallback/stdlib/c.vpi @@ -2,13 +2,14 @@ # 所有函数返回值类型姑息映射为 Python 类型。 from typing import Any, TypeVar +import t T = TypeVar('T') -def Addr(x: T) -> int: - """取地址 &x,返回指针(int)。""" - return 0 +def Addr(x: T) -> t.CPtr[T]: + """取地址 &x,返回指向 T 的指针(&x)。""" + ... def Deref(ptr: int) -> T: """解引用 *ptr。""" diff --git a/packages/vipeye-internal/vipershed-fallback/stdlib/hashtable.vpi b/packages/vipeye-internal/vipershed-fallback/stdlib/hashtable.vpi deleted file mode 100644 index c2365707..00000000 --- a/packages/vipeye-internal/vipershed-fallback/stdlib/hashtable.vpi +++ /dev/null @@ -1,140 +0,0 @@ -# Viper 开放寻址哈希表库 hashtable 存根。 -# 使用线性探测处理冲突,支持自动扩容和墓碑删除。 -# 键类型固定为 str (i8*),值类型为 void* (通用指针)。 -# 姑息策略: -# - __slots__ 等指针字段映射为 int(指针整数) -# - __mbuddy__ 为 MemBuddy 指针(int) -# - 值指针映射为 int -# - 键存在性/删除返回 int(0/1) - -import t -import string -import memhub - - -# 槽大小: hash(8) + key(8) + value(8) = 24 字节 -_HT_SLOT_SIZE: int = 24 -# FNV-1a 64-bit 常量 -_FNV_OFFSET: int = 0xcbf29ce484222325 -_FNV_PRIME: int = 0x100000001b3 - - -def _fnv1a_hash(key: str) -> int: - """FNV-1a 64-bit 哈希函数。 - - 保证返回值 >= 2(避开空槽 0 和墓碑 1)。 - """ - return 0 - - -class HashTable: - """开放寻址哈希表,使用线性探测处理冲突。 - - 键类型固定为 str (i8*),值类型为 void* (通用指针)。 - 使用 mbuddy 分配器管理内存。负载因子超过 0.75 时自动扩容。 - - 用法: - mb: memhub.MemBuddy = memhub.MemBuddy(arena, arena_size) - ht: HashTable = HashTable(mb) - ht.set_int("x", 42) - p: int = ht["x"] - v: int = c.Deref(p) - """ - __slots__: int # 槽位数组基址(指针) - __count__: int # 已用条目数 - __capacity__: int # 槽位总数(2 的幂) - __tombstones__: int # 墓碑数 - __mbuddy__: int # memhub.MemBuddy 指针 - __iter_index__: int # 迭代游标 - - def __new__(self, mb: int) -> int: - """堆分配 HashTable 对象。 - - Args: - mb: MemBuddy 指针(int) - Returns: - 对象指针(int) - """ - return 0 - - def __init__(self, mb: int) -> None: - """初始化哈希表。 - - Args: - mb: MemBuddy 指针(int) - """ - ... - - def __setitem__(self, key: str, val: int) -> None: - """插入或更新键值对。 - - Args: - key: 键字符串 - val: 值指针(int),自动取地址 - """ - ... - - def __getitem__(self, key: str) -> int: - """查找 key,返回值指针。 - - Returns: - 值指针(int),不存在返回 0(None) - """ - return 0 - - def __contains__(self, key: str) -> int: - """检查 key 是否存在。 - - Returns: - 存在返回 1,不存在返回 0 - """ - return 0 - - def __delitem__(self, key: str) -> int: - """删除 key。 - - Returns: - 成功返回 1,key 不存在返回 0 - """ - return 0 - - def __len__(self) -> int: - """返回已用条目数。""" - return 0 - - def __iter__(self) -> 'HashTable': - """初始化迭代器,返回 self。""" - return self - - def __next__(self) -> str: - """返回下一个键。迭代结束抛出 StopIteration。""" - raise StopIteration - - def get(self, key: str, default: int) -> int: - """返回值指针,不存在则返回 default。 - - Args: - key: 键字符串 - default: 默认值指针(int) - Returns: - 值指针(int) - """ - return 0 - - def set_int(self, key: str, val: int) -> None: - """存储 int 值。值复制到 mbuddy 持久存储。 - - Args: - key: 键字符串 - val: 整数值 - """ - ... - - def set_str(self, key: str, val: str) -> None: - """存储 str 值。直接存储字符串指针。 - - Args: - key: 键字符串 - val: 值字符串 - """ - ... diff --git a/packages/vipeye-internal/vipershed-fallback/stdlib/linkedlist.vpi b/packages/vipeye-internal/vipershed-fallback/stdlib/linkedlist.vpi deleted file mode 100644 index f5cab8fd..00000000 --- a/packages/vipeye-internal/vipershed-fallback/stdlib/linkedlist.vpi +++ /dev/null @@ -1,210 +0,0 @@ -# Viper 链表数据结构库 linkedlist 存根。 -# 提供 LinkedNode/SListNode/GSListNode/GSList/GTreeNode 等节点类型。 -# 姑息策略: -# - 节点指针字段映射为节点类型本身(含 None 表示空指针) -# - 计数字段映射为 int -# - 返回节点指针映射为节点类型或 None - -from typing import Generic, TypeVar, Optional - -T = TypeVar('T') - - -# ============================================================ -# LinkedNode: 非多态继承基类(@t.NoVTable) -# 子类继承此类即"启用"标准链表/树形结构能力: -# - 双向兄弟链表(next/prev)→ O(1) 删除 -# - 父子树结构(child/last_child/parent)→ O(1) 追加子节点 -# - 子节点计数(child_count) -# ============================================================ -class LinkedNode: - """双向兄弟链表 + 父子树节点基类。 - - 字段展平嵌入子类,无 vtable 开销(对应 C++ 非多态继承)。 - 用法: - class MyNode(LinkedNode): - value: int - root: MyNode = MyNode() - child: MyNode = MyNode() - root.append(child) - child.detach() - """ - next: Optional['LinkedNode'] # 下一个兄弟节点 - prev: Optional['LinkedNode'] # 上一个兄弟节点(双向链表,O(1) 删除) - child: Optional['LinkedNode'] # 第一个子节点 - last_child: Optional['LinkedNode'] # 最后一个子节点(O(1) 追加) - parent: Optional['LinkedNode'] # 父节点 - child_count: int # 子节点数 - - def append(self, node: 'LinkedNode') -> None: - """将 node 追加为 self 的最后一个子节点(O(1))。 - - 若 node 已在某链表中,调用方应先 node.detach() 再 append。 - """ - ... - - def prepend(self, node: 'LinkedNode') -> None: - """将 node 插入为 self 的第一个子节点(O(1))。""" - ... - - def insert_before(self, node: 'LinkedNode', new_node: 'LinkedNode') -> None: - """在 node 之前插入 new_node(node 必须是 self 的子节点,O(1))。""" - ... - - def insert_after(self, node: 'LinkedNode', new_node: 'LinkedNode') -> None: - """在 node 之后插入 new_node(node 必须是 self 的子节点,O(1))。""" - ... - - def remove_child(self, node: 'LinkedNode') -> None: - """从 self 的子链表中摘除 node(O(1))。node 必须是 self 的直接子节点。""" - ... - - def detach(self) -> None: - """将 self 从其所在兄弟链表中摘除(O(1))。 - - 调用后 self 的 next/prev/parent 被清空,child 链不受影响。 - """ - ... - - def unlink(self) -> None: - """断开 self 的所有连接(兄弟 + 父子),不递归处理子节点。""" - ... - - def has_children(self) -> int: - """返回 1 若有子节点,否则 0。""" - return 0 - - def child_at(self, index: int) -> Optional['LinkedNode']: - """返回第 index 个子节点(O(n),从 0 开始),越界返回 None。""" - return None - - def count_children(self) -> int: - """遍历计算子节点数(O(n)),用于校验 child_count。""" - return 0 - - def first_sibling(self) -> Optional['LinkedNode']: - """返回兄弟链表的头节点(沿 prev 回溯)。""" - return None - - def last_sibling(self) -> Optional['LinkedNode']: - """返回兄弟链表的尾节点(沿 next 前进)。""" - return None - - -# ============================================================ -# SListNode: 单向链表节点(@t.NoVTable) -# 轻量级单向链表,仅 Next 指针,无 prev/parent/child -# ============================================================ -class SListNode: - """单向链表节点。""" - Next: Optional['SListNode'] # 下一个节点 - - def append(self, node: 'SListNode') -> 'SListNode': - """将 node 追加到链表末尾(O(n)),返回 self(head)。""" - return self - - def append_after(self, node: 'SListNode') -> 'SListNode': - """将 node 追加到 self 之后(O(1)),返回新的 tail(即 node)。""" - return node - - def count(self) -> int: - """遍历计算链表长度(O(n))。""" - return 0 - - def at(self, index: int) -> Optional['SListNode']: - """返回第 index 个节点(O(n),从 0 开始),越界返回 None。""" - return None - - def remove(self, node: 'SListNode') -> 'SListNode': - """从链表中移除 node(O(n)),返回新的 head(self)。""" - return self - - -# ============================================================ -# GSListNode[T]: 泛型单向链表节点(@t.NoVTable + PEP 695 泛型) -# 递归泛型用法:class GNode(GSListNode[GNode]): -# ============================================================ -class GSListNode(Generic[T]): - """泛型单向链表节点。 - - 相比 SListNode,Next 字段类型为 T,特化后无需向下转型。 - """ - Next: Optional[T] - - -# ============================================================ -# GSList[T]: 泛型单向链表容器(@t.NoVTable + PEP 695 泛型) -# 持有 Head/Tail/Count,提供 O(1) append 和 O(n) at -# ============================================================ -class GSList(Generic[T]): - """泛型单向链表容器。 - - 节点类型 T 必须继承 GSListNode[T](以获得 Next 字段)。 - 用法: - lst: GSList[GNode] = GSList[GNode]() - lst.append(node) - """ - Head: Optional[T] - Tail: Optional[T] - Count: int - - def __init__(self) -> None: - """初始化空链表。""" - ... - - def append(self, node: T) -> None: - """将 node 追加到链表末尾(O(1))。""" - ... - - def count(self) -> int: - """返回节点数(O(1))。""" - return 0 - - def at(self, index: int) -> Optional[T]: - """返回第 index 个节点(O(n),从 0 开始),越界返回 None。""" - return None - - -# ============================================================ -# GTreeNode[T]: 泛型树节点(@t.NoVTable + PEP 695 泛型) -# 强类型版本的 LinkedNode,所有指针字段类型为 T -# 递归泛型用法:class AST(GTreeNode[AST]): -# ============================================================ -class GTreeNode(Generic[T]): - """泛型树节点。 - - 强类型版本的 LinkedNode,提供: - - 双向兄弟链表(Next/Prev)→ O(1) 删除 - - 父子树结构(Child/LastChild/Parent)→ O(1) 追加子节点 - - 子节点计数(Count) - """ - Next: Optional[T] # 下一个兄弟节点 - Prev: Optional[T] # 上一个兄弟节点(双向链表,O(1) 删除) - Child: Optional[T] # 第一个子节点 - LastChild: Optional[T] # 最后一个子节点(O(1) 追加) - Parent: Optional[T] # 父节点 - Count: int # 子节点数 - - def append(self, node: T) -> None: - """将 node 追加为 self 的最后一个子节点(O(1))。""" - ... - - def prepend(self, node: T) -> None: - """将 node 插入为 self 的第一个子节点(O(1))。""" - ... - - def detach(self) -> None: - """将 self 从其所在兄弟链表中摘除(O(1))。""" - ... - - def has_children(self) -> int: - """返回 1 若有子节点,否则 0。""" - return 0 - - def child_at(self, index: int) -> Optional[T]: - """返回第 index 个子节点(O(n),从 0 开始),越界返回 None。""" - return None - - def count_children(self) -> int: - """遍历计算子节点数(O(n)),用于校验 Count。""" - return 0 diff --git a/packages/vipeye-internal/vipershed-fallback/stdlib/memhub.vpi b/packages/vipeye-internal/vipershed-fallback/stdlib/memhub.vpi deleted file mode 100644 index f83fc612..00000000 --- a/packages/vipeye-internal/vipershed-fallback/stdlib/memhub.vpi +++ /dev/null @@ -1,243 +0,0 @@ -# Viper 统一内存管理库 memhub 存根。 -# 提供 MemManager 多态基类及 MemPool/MemSlab/MemBuddy 三个子类。 -# 姑息策略: -# - 指针字段映射为 int(指针整数) -# - 大小/计数映射为 int -# - 返回指针映射为 int(NULL=0 表示失败) - -import t -import string -import stdio - - -# ============================================================ -# 常量 -# ============================================================ -MEMHUB_ALIGN: int = 8 # 对齐粒度 -MEMSLAB_MIN_BLOCK: int = 16 # MemSlab 最小块大小 -MEMSLAB_BITMAP_BYTES: int = 256 # MemSlab 位图字节数 -MEMBUDDY_MIN_BLOCK: int = 32 # MemBuddy 最小块大小 -MEMBUDDY_MAX_ORDERS: int = 32 # MemBuddy 最大阶数 -MEMBUDDY_HEADER_SIZE: int = 8 # MemBuddy 块头大小 - - -def _align_up(val: int, align: int) -> int: - """向上对齐到 align 的倍数。""" - return 0 - - -def _largest_pow2_le(val: int) -> int: - """返回不超过 val 的最大 2 的幂。""" - return 0 - - -def _block_size_at_order(order: int) -> int: - """返回指定阶数的块大小。""" - return 0 - - -# ============================================================ -# MemManager - 多态基类(@t.CVTable) -# 子类通过 vtable 覆盖 alloc/free/reset,支持多态调用 -# ============================================================ -class MemManager: - """内存管理器多态基类。 - - 子类: - MemPool - arena bump 分配器(不支持单个 free) - MemSlab - 定长块池(位图跟踪) - MemBuddy - 伙伴系统(支持合并) - """ - __provides__: list # ['__memmgr__'] - base: int # 内存区起始地址(指针) - size: int # 内存区总大小 - - def __init__(self, base: int, size: int) -> None: - """初始化内存管理器。 - - Args: - base: 内存区起始地址(指针,int) - size: 内存区总大小(字节) - """ - ... - - # === 虚函数(子类覆盖) === - def alloc(self, size: int) -> int: - """分配 size 字节内存。 - - Returns: - 成功返回指针(int),失败返回 0(NULL) - """ - return 0 - - def free(self, ptr: int) -> int: - """释放内存。 - - Returns: - 成功返回 1,失败返回 0 - """ - return 0 - - def reset(self) -> int: - """重置内存区(回收全部)。 - - Returns: - 成功返回 1 - """ - return 0 - - # === 具体方法(自动多态分派) === - def calloc(self, count: int, size: int) -> int: - """分配 count*size 字节并清零。""" - return 0 - - def realloc(self, ptr: int, new_size: int) -> int: - """重新调整分配大小。""" - return 0 - - def __enter__(self) -> 'MemManager': - """进入 with 上下文,返回 self。""" - ... - - def __exit__(self) -> None: - """退出 with 上下文,调用 reset。""" - ... - - def alloc_buf(self, capacity: int) -> int: - """分配一个 viperio.Buf 及其数据区。 - - Args: - capacity: 数据区容量 - Returns: - Buf 指针(int),失败返回 0 - """ - return 0 - - -# ============================================================ -# MemPool - arena bump 分配器 -# 不支持单个 free,reset 一次性回收全部 -# ============================================================ -class MemPool(MemManager): - """Arena bump 分配器。""" - offset: int # bump 游标 - high_water: int # 峰值水位 - - def __init__(self, base: int, size: int) -> None: ... - - def alloc(self, size: int) -> int: ... - def free(self, ptr: int) -> int: ... - def reset(self) -> int: ... - - -# ============================================================ -# MemSlab - 定长块池 -# arena 开头存位图,后续区域按 block_size 切分,空闲链管理 -# ============================================================ -class MemSlab(MemManager): - """定长块池分配器。""" - block_size: int # 每块大小(对齐后) - block_count: int # 总块数 - used_count: int # 已用块数 - free_list: int # 空闲链头(指针) - alloc_map: int # 分配位图基址(指针) - alloc_map_size: int # 位图字节数 - usable: int # 可用块区起始(指针) - usable_size: int # 可用块区大小 - - def __init__(self, base: int, size: int, block_size: int) -> None: - """初始化定长块池。 - - Args: - base: 内存区起始地址 - size: 内存区总大小 - block_size: 每块大小 - """ - ... - - def alloc(self, size: int) -> int: ... - def free(self, ptr: int) -> int: ... - def reset(self) -> int: ... - - -# ============================================================ -# MemBuddy - 伙伴系统分配器 -# 线程安全(自旋锁),支持合并 -# ============================================================ -class MemBuddy(MemManager): - """伙伴系统分配器。 - - __provides__ 同时提供 '__mbuddy__' 和 '__memmgr__', - 使 with MemBuddy(...) 上下文内的 __requires__=['__mbuddy__'] 类能注入。 - """ - __provides__: list # ['__mbuddy__', '__memmgr__'] - max_order: int # 最大阶数 - free_lists: int # 空闲链头数组基址(指针) - lock_val: int # 自旋锁 - usable: int # 可用区起始(指针) - usable_size: int # 可用区大小(2 的幂) - - def __init__(self, base: int, size: int) -> None: - """初始化伙伴系统。 - - Args: - base: 内存区起始地址 - size: 内存区总大小 - """ - ... - - def alloc(self, size: int) -> int: - """分配内存(线程安全)。 - - 失败时打印内存使用情况到 stderr。 - """ - return 0 - - def free(self, ptr: int) -> int: - """释放内存(线程安全,自动合并伙伴块)。""" - return 0 - - def reset(self) -> int: - """重置(回收全部)。""" - return 0 - - def realloc(self, ptr: int, new_size: int) -> int: - """重新调整大小(从块头读取旧阶数,无需 old_size)。""" - return 0 - - @property - def mem_size(self) -> int: - """可用区总大小(2 的幂)。""" - return 0 - - def stats(self) -> int: - """返回可用区总大小。""" - return 0 - - def free_count(self) -> int: - """统计所有阶数空闲链中的块总数。""" - return 0 - - def free_bytes(self) -> int: - """统计所有空闲块的总字节数。""" - return 0 - - def used_bytes(self) -> int: - """已使用字节数 = 可用区总大小 - 空闲字节数。""" - return 0 - - def dump_stats(self, label: int) -> None: - """打印内存使用统计到 stdout。 - - Args: - label: 标签字符串指针(int) - """ - ... - - def self_check(self) -> int: - """验证伙伴分配器内部一致性。 - - Returns: - 0=OK,非 0=损坏 - """ - return 0 diff --git a/packages/vipeye-internal/vipershed-fallback/stdlib/stdint.vpi b/packages/vipeye-internal/vipershed-fallback/stdlib/stdint.vpi deleted file mode 100644 index 41685327..00000000 --- a/packages/vipeye-internal/vipershed-fallback/stdlib/stdint.vpi +++ /dev/null @@ -1,138 +0,0 @@ -# Viper C stdint.h 类型别名存根。 -# 姑息策略:所有整数/指针别名映射为 Python int,浮点映射为 float。 -# 与 t.vpi 配合,提供 Win32/TransPyC 风格的大写别名及 C 标准小写 _t 别名。 - -from t import * - -# ============================================================================= -# 大写别名(Win32/TransPyC 风格) -# 注意:INTPTR/UINTPTR 在此模块中是 int*/unsigned int* 指针类型(历史命名)。 -# 它们与 C 标准的 intptr_t/uintptr_t 整数类型不同,后者见下方小写 _t 区域。 -# ============================================================================= -INT: int # = t.CInt -INTPTR: int # = t.CInt | t.CPtr(int 指针) - -BOOL: int # = t.CInt -UINT: int # = t.CUnsignedInt -UINTPTR: int # = UINT | t.CPtr -BYTE: int # = t.CUnsignedChar -BYTEPTR: int # = BYTE | t.CPtr -WORD: int # = t.CUInt16T -DWORD: int # = t.CUInt32T -QWORD: int # = t.CUInt64T -TCHAR: int # = t.CChar -CHARLIST: int # = str | t.CPtr -VOID: int # = t.CVoid - -SHORT: int # = t.CShort -SHORTPTR: int # = t.CShort | t.CPtr -USHORT: int # = t.CUnsignedShort -USHORTPTR: int # = t.CUnsignedShort | t.CPtr - -LONGLONG: int # = t.CLongLong -ULONGLONG: int # = t.CUnsignedLongLong -LONG: int # = t.CLong -ULONG: int # = t.CUnsignedLong - -WCHAR: int # = WORD -WCHARPTR: int # = WORD | t.CPtr -CHARPTR: int # = t.CChar | t.CPtr -FSIZE_t: int # = DWORD -LBA_t: int # = DWORD - -VOIDPTR: int # = t.CVoid | t.CPtr -FLOAT: float # = t.CFloat -DOUBLE: float # = t.CDouble - -FLOAT8: float # = t.CFloat8T -FLOAT16: float # = t.CFloat16T -FLOAT32: float # = t.CFloat32T -FLOAT64: float # = t.CFloat64T -FLOAT128: float # = t.CFloat128T - -# ============================================================================= -# 定长整数大写别名 -# ============================================================================= -INT8: int # = t.CInt8T -INT16: int # = t.CInt16T -INT32: int # = t.CInt32T -INT64: int # = t.CInt64T - -UINT8: int # = t.CUInt8T -UINT16: int # = t.CUInt16T -UINT32: int # = t.CUInt32T -UINT64: int # = t.CUInt64T - -INT8PTR: int # = t.CInt8T | t.CPtr -INT16PTR: int # = t.CInt16T | t.CPtr -INT32PTR: int # = t.CInt32T | t.CPtr -INT64PTR: int # = t.CInt64T | t.CPtr - -UINT8PTR: int # = t.CUInt8T | t.CPtr -UINT16PTR: int # = t.CUInt16T | t.CPtr -UINT32PTR: int # = t.CUInt32T | t.CPtr -UINT64PTR: int # = t.CUInt64T | t.CPtr - -CHAR8: int # = t.CChar8T -CHAR16: int # = t.CChar16T -CHAR32: int # = t.CChar32T -CHAR8PTR: int # = t.CChar8T | t.CPtr -CHAR16PTR: int # = t.CChar16T | t.CPtr -CHAR32PTR: int # = t.CChar32T | t.CPtr - -i8: int # = t.CInt8T -i16: int # = t.CInt16T -i32: int # = t.CInt32T -i64: int # = t.CInt64T - -u8: int # = t.CUInt8T -u16: int # = t.CUInt16T -u32: int # = t.CUInt32T -u64: int # = t.CUInt64T - -SIZE_T: int # = t.CSizeT -SSIZE_T: int # = t.CPtrDiffT -PTRDIFF_T: int # = t.CPtrDiffT - -# ============================================================================= -# C 标准库 类型名(小写 _t 后缀) -# 用户 `from stdint import *` 后即可直接使用 int8_t / size_t 等。 -# ============================================================================= -int8_t: int # = t.CInt8T -int16_t: int # = t.CInt16T -int32_t: int # = t.CInt32T -int64_t: int # = t.CInt64T - -uint8_t: int # = t.CUInt8T -uint16_t: int # = t.CUInt16T -uint32_t: int # = t.CUInt32T -uint64_t: int # = t.CUInt64T - -size_t: int # = t.CSizeT -ssize_t: int # = t.CPtrDiffT -ptrdiff_t: int # = t.CPtrDiffT - -# intptr_t/uintptr_t 是足以容纳指针的整数类型(不是指针!) -# 与上方的 INTPTR/UINTPTR(int 指针)语义不同。 -intptr_t: int # = t.CIntPtrT -uintptr_t: int # = t.CUIntPtrT - -# 宽字符 / 字符类型 -wchar_t: int # = t.CWCharT -char8_t: int # = t.CChar8T -char16_t: int # = t.CChar16T -char32_t: int # = t.CChar32T - -# 浮点特化类型 -float8_t: float # = t.CFloat8T -float16_t: float # = t.CFloat16T -float32_t: float # = t.CFloat32T -float64_t: float # = t.CFloat64T -float128_t: float # = t.CFloat128T - -_Bool: int # = t.CBool - - -def __getattr__(name: str) -> int: - """兜底:未声明的 stdint 别名返回 int。""" - return 0 diff --git a/packages/vipeye-internal/vipershed-fallback/stdlib/stdio.vpi b/packages/vipeye-internal/vipershed-fallback/stdlib/stdio.vpi deleted file mode 100644 index 6a77bf34..00000000 --- a/packages/vipeye-internal/vipershed-fallback/stdlib/stdio.vpi +++ /dev/null @@ -1,113 +0,0 @@ -# Viper C stdio.h 函数存根。 -# 姑息策略: -# - 流指针(FILE*)映射为 bytes(姑息用 bytes 表示缓冲区指针) -# - 字符串格式参数映射为 str -# - 缓冲区参数映射为 bytes(可变缓冲) -# - 返回 t.CInt 映射为 int,t.State 映射为 None - -from typing import Any - - -def printf(fmt: str, *args: Any) -> int: - """格式化输出到 stdout。 - - Args: - fmt: 格式字符串(C 风格 %d/%s/%x/%f 等) - *args: 格式参数 - Returns: - 写入字符数(t.CInt),失败返回负值 - """ - return 0 - - -def fprintf(stream: bytes, fmt: str, *args: Any) -> int: - """格式化输出到指定流。 - - Args: - stream: FILE* 指针(姑息用 bytes) - fmt: 格式字符串 - *args: 格式参数 - Returns: - 写入字符数 - """ - return 0 - - -def sprintf(buf: bytes, fmt: str, *args: Any) -> int: - """格式化写入到缓冲区(不检查边界,慎用)。 - - Args: - buf: 目标缓冲区(bytes,可变) - fmt: 格式字符串 - *args: 格式参数 - Returns: - 写入字符数 - """ - return 0 - - -def snprintf(buf: bytes, size: int, fmt: str, *args: Any) -> int: - """格式化写入到缓冲区(带大小限制)。 - - Args: - buf: 目标缓冲区 - size: 缓冲区最大容量 - fmt: 格式字符串 - *args: 格式参数 - Returns: - 本应写入的字符数(可能 >= size) - """ - return 0 - - -def puts(s: str) -> int: - """输出字符串到 stdout 并追加换行。 - - Args: - s: 要输出的字符串 - Returns: - 成功返回非负值,失败返回 EOF - """ - return 0 - - -def fputs(s: str, stream: bytes) -> int: - """输出字符串到指定流(不追加换行)。 - - Args: - s: 要输出的字符串 - stream: FILE* 指针 - Returns: - 成功返回非负值,失败返回 EOF - """ - return 0 - - -def fgets(buf: bytes, size: int, stream: bytes) -> bytes: - """从流读取一行。 - - Args: - buf: 目标缓冲区 - size: 缓冲区最大容量 - stream: FILE* 指针 - Returns: - 成功返回 buf 指针,失败或 EOF 返回 None - """ - ... - - -def fflush(stream: bytes) -> int: - """刷新流缓冲区。 - - Args: - stream: FILE* 指针,传 0(NULL)刷新所有输出流 - Returns: - 成功返回 0,失败返回 EOF - """ - return 0 - - -# 标准流指针(全局变量,仅声明供 printf 等函数使用) -stdin: bytes = 0 # 标准输入流 -stdout: bytes = 0 # 标准输出流 -stderr: bytes = 0 # 标准错误流 diff --git a/packages/vipeye-internal/vipershed-fallback/stdlib/stdlib.vpi b/packages/vipeye-internal/vipershed-fallback/stdlib/stdlib.vpi deleted file mode 100644 index fb8747ed..00000000 --- a/packages/vipeye-internal/vipershed-fallback/stdlib/stdlib.vpi +++ /dev/null @@ -1,121 +0,0 @@ -# Viper C stdlib.h 函数存根。 -# 姑息策略: -# - malloc/calloc/realloc 返回 int(指针整数,NULL=0 表示失败) -# - free 接受 int(指针) -# - system 返回 int(命令退出码) - -from stdint import * - - -def malloc(size: int) -> int: - """分配 size 字节的内存。 - - Args: - size: 要分配的字节数 - Returns: - 成功返回指向分配内存的指针(int),失败返回 0(NULL) - """ - return 0 - - -def calloc(nmemb: int, size: int) -> int: - """分配 nmemb 个 size 字节的内存并清零。 - - Args: - nmemb: 元素个数 - size: 每个元素的字节数 - Returns: - 成功返回指针(int),失败返回 0 - """ - return 0 - - -def realloc(p: int, size: int) -> int: - """重新调整已分配内存的大小。 - - Args: - p: 原指针(int),可为 0(NULL)表示新分配 - size: 新大小 - Returns: - 成功返回新指针(int),失败返回 0(原内存不变) - """ - return 0 - - -def free(p: int) -> None: - """释放已分配的内存。 - - Args: - p: malloc/calloc/realloc 返回的指针,传 0(NULL)无操作 - """ - ... - - -def system(cmd: str) -> int: - """执行系统命令。 - - Args: - cmd: 命令字符串(C 风格 const char*) - Returns: - 命令退出码(平台相关),cmd 为 None 时返回非 0 表示 shell 可用 - """ - return 0 - - -def exit(status: int) -> None: - """终止进程。 - - Args: - status: 退出码(0 表示成功) - """ - ... - - -def abort() -> None: - """异常终止进程(触发 SIGABRT)。""" - ... - - -def atof(s: str) -> float: - """字符串转 double。 - - Args: - s: 数字字符串 - Returns: - 解析得到的浮点数 - """ - return 0.0 - - -def atoi(s: str) -> int: - """字符串转 int。 - - Args: - s: 数字字符串 - Returns: - 解析得到的整数 - """ - return 0 - - -def atol(s: str) -> int: - """字符串转 long。 - - Args: - s: 数字字符串 - Returns: - 解析得到的整数 - """ - return 0 - - -def qsort(base: int, nmemb: int, size: int, compar: int) -> None: - """快速排序。 - - Args: - base: 数组基址指针(int) - nmemb: 元素个数 - size: 每个元素字节数 - compar: 比较函数指针(int),签名 int (*)(const void*, const void*) - """ - ... diff --git a/packages/vipeye-internal/vipershed-fallback/stdlib/string.vpi b/packages/vipeye-internal/vipershed-fallback/stdlib/string.vpi deleted file mode 100644 index 6acdf18e..00000000 --- a/packages/vipeye-internal/vipershed-fallback/stdlib/string.vpi +++ /dev/null @@ -1,218 +0,0 @@ -# Viper C string.h 函数存根。 -# 姑息策略: -# - 字符串参数映射为 str -# - 内存缓冲区指针映射为 int(指针整数) -# - 内存函数返回指针(int) -# - 长度/比较返回 int - -from typing import Any - - -# ============================================================================= -# 字符串复制 -# ============================================================================= -def strcpy(dest: str, src: str) -> str: - """复制 src 到 dest(含终止符)。 - - Returns: - dest 指针 - """ - ... - - -def strncpy(dest: str, src: str, n: int) -> str: - """复制 src 前 n 个字符到 dest。 - - 不足 n 时用 \\0 填充,超过 n 不追加终止符。 - - Args: - n: 最大复制字节数 - Returns: - dest 指针 - """ - ... - - -# ============================================================================= -# 字符串拼接 -# ============================================================================= -def strcat(dest: str, src: str) -> str: - """将 src 追加到 dest 末尾。 - - Returns: - dest 指针 - """ - ... - - -def strncat(dest: str, src: str, n: int) -> str: - """将 src 前 n 个字符追加到 dest 末尾。""" - ... - - -# ============================================================================= -# 字符串比较 -# ============================================================================= -def strcmp(str1: str, str2: str) -> int: - """比较两个字符串。 - - Returns: - < 0 表示 str1 < str2,= 0 表示相等,> 0 表示 str1 > str2 - """ - return 0 - - -def strncmp(str1: str, str2: str, n: int) -> int: - """比较两个字符串前 n 个字符。""" - return 0 - - -def samestr(str1: str, str2: str) -> bool: - """Viper 扩展:直接 == 比较。""" - return False - - -# ============================================================================= -# 字符串长度 -# ============================================================================= -def strlen(src: str) -> int: - """返回字符串长度(不含终止符)。""" - return 0 - - -# ============================================================================= -# 字符查找 -# ============================================================================= -def strchr(s: str, cr: int) -> str: - """在 s 中查找字符 cr 第一次出现的位置。 - - Args: - cr: 字符的 ASCII 码(int) - Returns: - 指向该字符的指针,未找到返回 None - """ - ... - - -def strrchr(s: str, cr: int) -> str: - """在 s 中查找字符 cr 最后一次出现的位置。 - - Returns: - 指向该字符的指针,未找到返回 None - """ - ... - - -def strstr(s: str, needle: str) -> str: - """在 s 中查找子串 needle 第一次出现的位置。 - - Returns: - 指向子串起始的指针,未找到返回 None - """ - ... - - -def strspn(s: str, skip: str = " ") -> int: - """返回 s 开头连续属于 skip 的字符数。""" - return 0 - - -# ============================================================================= -# 内存操作 -# ============================================================================= -def memset(ptr: int, value: int, num: int) -> int: - """将 ptr 指向的 num 字节内存全部设置为 value。 - - Args: - ptr: 内存指针(int) - value: 填充值(取低 8 位) - num: 字节数 - Returns: - ptr 指针 - """ - return 0 - - -def memset32(ptr: int, value: int, count: int) -> int: - """将 ptr 指向的 count 个 32 位整数全部设置为 value。 - - Args: - ptr: 内存指针(int) - value: 32 位填充值 - count: 32 位整数个数 - Returns: - ptr 指针 - """ - return 0 - - -def memcpy(dest: int, src: int, num: int) -> int: - """从 src 复制 num 字节到 dest(不处理重叠)。 - - Returns: - dest 指针 - """ - return 0 - - -def memmove(dest: int, src: int, num: int) -> int: - """从 src 复制 num 字节到 dest(处理重叠)。 - - Returns: - dest 指针 - """ - return 0 - - -def memcmp(ptr1: int, ptr2: int, n: int) -> int: - """比较两块内存前 n 字节。 - - Returns: - < 0 表示 ptr1 < ptr2,= 0 表示相等,> 0 表示 ptr1 > ptr2 - """ - return 0 - - -# ============================================================================= -# 字符串转数字(Viper 实现放在 string.py 中) -# ============================================================================= -def atoi(src: str) -> int: - """字符串转 int(支持前导空白和符号)。""" - return 0 - - -def atoll(src: str) -> int: - """字符串转 long long。""" - return 0 - - -def atof(src: str) -> float: - """字符串转 double(支持整数/小数/指数部分)。""" - return 0.0 - - -def split(s: str, delim: str, result: list) -> int: - """Viper 扩展:按单字符分隔符切分字符串。 - - Args: - s: 待切分字符串 - delim: 单字符分隔符 - result: 结果数组(str 指针数组),调用方需保证容量 - Returns: - 切分得到的 token 数 - """ - return 0 - - -# ============================================================================= -# 字符集常量 -# ============================================================================= -ascii_lowercase: str = "abcdefghijklmnopqrstuvwxyz" -ascii_uppercase: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -ascii_letters: str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - -digits: str = "0123456789" -hexdigits: str = "0123456789abcdefABCDEF" -octdigits: str = "01234567" -punctuation: str = "!\"#$%&'()*+,-./:;<=>?@[]^_`{|}~" -whitespace: str = " \t\n\r\f\v" diff --git a/packages/vipeye-internal/vipershed-fallback/stdlib/sys.vpi b/packages/vipeye-internal/vipershed-fallback/stdlib/sys.vpi deleted file mode 100644 index 9412fceb..00000000 --- a/packages/vipeye-internal/vipershed-fallback/stdlib/sys.vpi +++ /dev/null @@ -1,101 +0,0 @@ -# Viper sys 模块存根。 -# 提供 C 标准库 IO 声明、平台信息、进程信息、命令行参数访问。 -# 姑息策略: -# - _mbuddy 为模块级全局 MemBuddy 指针(int) -# - 命令行 argv 数组指针为 int(char**) -# - exit 终止进程返回 None - -import t -import memhub -from stdint import * - - -# 模块级全局 MBuddy 指针(由用户在 main 中赋值初始化) -_mbuddy: int # memhub.MemBuddy | t.CPtr - - -# ============================================================================= -# C 标准库 IO 声明(从 sys 模块转发) -# ============================================================================= -def fgets(buf: int, size: int, stream: int) -> int: - """从流读取一行(C stdio 声明)。 - - Args: - buf: 目标缓冲区指针(int) - size: 缓冲区最大容量 - stream: FILE* 指针(int) - Returns: - 成功返回 buf 指针,失败返回 0(NULL) - """ - return 0 - - -def fflush(stream: int) -> int: - """刷新流缓冲区(C stdio 声明)。 - - Args: - stream: FILE* 指针(int),传 0 刷新所有输出流 - Returns: - 成功返回 0,失败返回 EOF - """ - return 0 - - -def exit(status: int) -> None: - """终止进程(刷新缓冲区后终止,跨平台)。 - - Args: - status: 退出码(0 表示成功) - """ - ... - - -# ============================================================================= -# 平台信息 -# ============================================================================= -# 最大整数值(64 位平台 ssize_t 上限) -maxsize: int = 9223372036854775807 - - -def platform() -> str: - """返回平台标识字符串。 - - Returns: - 'win32' 或 'linux' - """ - return "" - - -# ============================================================================= -# 进程信息 -# ============================================================================= -def get_pid() -> int: - """返回当前进程 ID。""" - return 0 - - -# ============================================================================= -# 命令行参数(argv) -# ============================================================================= -# 全局 argv 状态(首次访问时初始化) -_argc: int = 0 -# _argv 是指针数组(char**),姑息映射为 int -_argv: int - - -def argc() -> int: - """返回命令行参数个数。首次调用时自动初始化。""" - return 0 - - -def argv(index: int) -> str: - """返回第 index 个命令行参数(index 从 0 开始)。 - - 首次调用时自动初始化。索引越界返回 None。 - - Args: - index: 参数索引(0 = 程序名) - Returns: - 参数字符串,越界返回 None - """ - return "" diff --git a/packages/vipeye-internal/vipershed-fallback/stdlib/t.vpi b/packages/vipeye-internal/vipershed-fallback/stdlib/t.vpi index 5dabb043..95e03f6e 100644 --- a/packages/vipeye-internal/vipershed-fallback/stdlib/t.vpi +++ b/packages/vipeye-internal/vipershed-fallback/stdlib/t.vpi @@ -1,8 +1,9 @@ # Viper 类型系统模块 t 的存根。 # -# 姑息策略(完善版): -# - 基本数值/字符类型使用 TypeAlias 别名(等价 Python 内置类型), -# 使 `k: t.CInt = 1` 合法(1 是 int,CInt 别名 int) +# 类型策略: +# - 所有 C 类型作为独立 class 继承 CType,保留类型语义信息(绿色高亮)。 +# - typeEvaluator 中的 Viper 扩展允许 int/float/str/bool 值赋给 CType 子类, +# 使 `k: t.CInt = 1` 合法(1 是 int,CInt 在底层等价 int)。 # - 指针/修饰符/命名类型作为独立 class 继承 CType, # 保持"类型"语义(绿色高亮)和联合类型 `T | t.CPtr` 支持 # - CPtr 支持泛型下标 `t.CPtr[t.CInt]` @@ -15,6 +16,7 @@ from typing import Any, Generic, Optional, Type, TypeAlias, TypeVar from typing import Callable as CallableT T = TypeVar('T') +N = TypeVar('N') # ============================================================================= @@ -57,90 +59,280 @@ class CTypeDefault(CType): # ============================================================================= -# 基本整数类型(TypeAlias 别名,等价 int) +# 基本整数类型(独立 class,继承 CType) +# Viper 语义:C 整数类型在底层等价于 int,但作为独立类型保留语义信息。 +# typeEvaluator 中的 Viper 扩展允许 int 值赋给 CType 子类。 # ============================================================================= -CChar: TypeAlias = int -CShort: TypeAlias = int -CInt: TypeAlias = int -CLong: TypeAlias = int -CLongLong: TypeAlias = int -CSignedChar: TypeAlias = int -CUnsignedChar: TypeAlias = int -CUnsignedShort: TypeAlias = int -CUnsignedInt: TypeAlias = int -CUnsignedLong: TypeAlias = int -CUnsignedLongLong: TypeAlias = int +class CChar(CType): + """char 类型。""" + pass + +class CShort(CType): + """short 类型。""" + pass + +class CInt(CType): + """int 类型。""" + pass + +class CLong(CType): + """long 类型。""" + pass + +class CLongLong(CType): + """long long 类型。""" + pass + +class CSignedChar(CType): + """signed char 类型。""" + pass + +class CUnsignedChar(CType): + """unsigned char 类型。""" + pass + +class CUnsignedShort(CType): + """unsigned short 类型。""" + pass + +class CUnsignedInt(CType): + """unsigned int 类型。""" + pass + +class CUnsignedLong(CType): + """unsigned long 类型。""" + pass + +class CUnsignedLongLong(CType): + """unsigned long long 类型。""" + pass + # 固定宽度整数 -CInt8T: TypeAlias = int -CInt16T: TypeAlias = int -CInt32T: TypeAlias = int -CInt64T: TypeAlias = int -CUInt8T: TypeAlias = int -CUInt16T: TypeAlias = int -CUInt32T: TypeAlias = int -CUInt64T: TypeAlias = int +class CInt8T(CType): + """int8_t 类型。""" + pass + +class CInt16T(CType): + """int16_t 类型。""" + pass + +class CInt32T(CType): + """int32_t 类型。""" + pass + +class CInt64T(CType): + """int64_t 类型。""" + pass + +class CUInt8T(CType): + """uint8_t 类型。""" + pass + +class CUInt16T(CType): + """uint16_t 类型。""" + pass + +class CUInt32T(CType): + """uint32_t 类型。""" + pass + +class CUInt64T(CType): + """uint64_t 类型。""" + pass + # 最小/最快宽度整数 -CIntLeast8T: TypeAlias = int -CIntLeast16T: TypeAlias = int -CIntLeast32T: TypeAlias = int -CIntLeast64T: TypeAlias = int -CIntFast8T: TypeAlias = int -CIntFast16T: TypeAlias = int -CIntFast32T: TypeAlias = int -CIntFast64T: TypeAlias = int -CUIntLeast8T: TypeAlias = int -CUIntLeast16T: TypeAlias = int -CUIntLeast32T: TypeAlias = int -CUIntLeast64T: TypeAlias = int -CUIntFast8T: TypeAlias = int -CUIntFast16T: TypeAlias = int -CUIntFast32T: TypeAlias = int -CUIntFast64T: TypeAlias = int +class CIntLeast8T(CType): + """int_least8_t 类型。""" + pass + +class CIntLeast16T(CType): + """int_least16_t 类型。""" + pass + +class CIntLeast32T(CType): + """int_least32_t 类型。""" + pass + +class CIntLeast64T(CType): + """int_least64_t 类型。""" + pass + +class CIntFast8T(CType): + """int_fast8_t 类型。""" + pass + +class CIntFast16T(CType): + """int_fast16_t 类型。""" + pass + +class CIntFast32T(CType): + """int_fast32_t 类型。""" + pass + +class CIntFast64T(CType): + """int_fast64_t 类型。""" + pass + +class CUIntLeast8T(CType): + """uint_least8_t 类型。""" + pass + +class CUIntLeast16T(CType): + """uint_least16_t 类型。""" + pass + +class CUIntLeast32T(CType): + """uint_least32_t 类型。""" + pass + +class CUIntLeast64T(CType): + """uint_least64_t 类型。""" + pass + +class CUIntFast8T(CType): + """uint_fast8_t 类型。""" + pass + +class CUIntFast16T(CType): + """uint_fast16_t 类型。""" + pass + +class CUIntFast32T(CType): + """uint_fast32_t 类型。""" + pass + +class CUIntFast64T(CType): + """uint_fast64_t 类型。""" + pass + # 最大宽度整数 -CIntMaxT: TypeAlias = int -CUIntMaxT: TypeAlias = int +class CIntMaxT(CType): + """intmax_t 类型。""" + pass + +class CUIntMaxT(CType): + """uintmax_t 类型。""" + pass + # 平台相关整数 -CSizeT: TypeAlias = int -CIntPtrT: TypeAlias = int -CUIntPtrT: TypeAlias = int -CPtrDiffT: TypeAlias = int +class CSizeT(CType): + """size_t 类型。""" + pass + +class CIntPtrT(CType): + """intptr_t 类型。""" + pass + +class CUIntPtrT(CType): + """uintptr_t 类型。""" + pass + +class CPtrDiffT(CType): + """ptrdiff_t 类型。""" + pass + # 布尔 -CBool: TypeAlias = int +class CBool(CType): + """bool 类型(_Bool)。""" + pass + # 宽字符 -CWCharT: TypeAlias = int -CChar8T: TypeAlias = int -CChar16T: TypeAlias = int -CChar32T: TypeAlias = int +class CWCharT(CType): + """wchar_t 类型。""" + pass -# LLVM 简写(直接作为类型别名,等价于固定宽度整数) -i1: TypeAlias = int -i8: TypeAlias = int -i16: TypeAlias = int -i32: TypeAlias = int -i64: TypeAlias = int -u1: TypeAlias = int -u8: TypeAlias = int -u16: TypeAlias = int -u32: TypeAlias = int -u64: TypeAlias = int +class CChar8T(CType): + """char8_t 类型。""" + pass + +class CChar16T(CType): + """char16_t 类型。""" + pass + +class CChar32T(CType): + """char32_t 类型。""" + pass + + +# LLVM 简写(独立 class,等价于固定宽度整数) +class i1(CType): + """LLVM i1 类型(1 位整数)。""" + pass + +class i8(CType): + """LLVM i8 类型(8 位整数)。""" + pass + +class i16(CType): + """LLVM i16 类型(16 位整数)。""" + pass + +class i32(CType): + """LLVM i32 类型(32 位整数)。""" + pass + +class i64(CType): + """LLVM i64 类型(64 位整数)。""" + pass + +class u1(CType): + """LLVM u1 类型(1 位无符号整数)。""" + pass + +class u8(CType): + """LLVM u8 类型(8 位无符号整数)。""" + pass + +class u16(CType): + """LLVM u16 类型(16 位无符号整数)。""" + pass + +class u32(CType): + """LLVM u32 类型(32 位无符号整数)。""" + pass + +class u64(CType): + """LLVM u64 类型(64 位无符号整数)。""" + pass # ============================================================================= -# 浮点类型(TypeAlias 别名,等价 float) +# 浮点类型(独立 class,继承 CType) +# Viper 语义:C 浮点类型在底层等价于 float,但作为独立类型保留语义信息。 # ============================================================================= -CFloat: TypeAlias = float -CDouble: TypeAlias = float -CFloat8T: TypeAlias = float -CFloat16T: TypeAlias = float -CFloat32T: TypeAlias = float -CFloat64T: TypeAlias = float -CFloat128T: TypeAlias = float +class CFloat(CType): + """float 类型。""" + pass + +class CDouble(CType): + """double 类型。""" + pass + +class CFloat8T(CType): + """_Float8 类型。""" + pass + +class CFloat16T(CType): + """_Float16 类型。""" + pass + +class CFloat32T(CType): + """_Float32 类型。""" + pass + +class CFloat64T(CType): + """_Float64 类型。""" + pass + +class CFloat128T(CType): + """_Float128 类型。""" + pass # ============================================================================= @@ -181,14 +373,15 @@ class CArrayPtr(CType): # ============================================================================= # 数组类型(支持下标) # ============================================================================= -class CArray(CType): +class CArray(CType, Generic[T, N]): """栈上固定数组类型注解。 用法: names: t.CArray[VOIDPTR, 32] # void*[32] data: t.CArray[t.CInt, 64] # int[64] + T 为元素类型,N 为数组长度(整数)。 """ def __class_getitem__(cls, item: Any) -> type: ... - def __getitem__(self, index: int) -> Any: ... - def __setitem__(self, index: int, value: Any) -> None: ... + def __getitem__(self, index: int) -> T: ... + def __setitem__(self, index: int, value: T) -> None: ... # ============================================================================= @@ -625,9 +818,9 @@ class ASM_DESCR: # ============================================================================= # 函数指针 # ============================================================================= -class Callable(CType): - """函数指针类型。""" - pass +# Viper 的 Callable 等价于 typing.Callable,支持 Callable[[], TReturn] 订阅语法。 +# 直接赋值(不加 TypeAlias 注解),让 Pyright 识别为 typing.Callable 别名。 +Callable = CallableT # ============================================================================= diff --git a/packages/vipeye-internal/vipershed-fallback/stdlib/viperlib.vpi b/packages/vipeye-internal/vipershed-fallback/stdlib/viperlib.vpi deleted file mode 100644 index 3253a463..00000000 --- a/packages/vipeye-internal/vipershed-fallback/stdlib/viperlib.vpi +++ /dev/null @@ -1,93 +0,0 @@ -# Viper 标准库扩展 viperlib 存根。 -# 提供 snprintf/sprintf/vsnprintf 等 C 风格格式化函数的纯 Viper 实现。 -# 姑息策略: -# - 缓冲区指针映射为 int(指针整数) -# - 格式字符串映射为 str -# - va_list 参数列表映射为 int(指针整数) -# - 返回 t.CInt 映射为 int - -from typing import Any - - -# 临时缓冲区大小(vsnprintf 内部数字转换用) -TEMP_BUF_SIZE: int = 68 - - -def get_digit_count(num: int, base: int) -> int: - """计算 num 在指定进制下的位数。 - - Args: - num: 无符号整数 - base: 进制(2/8/10/16) - Returns: - 位数 - """ - return 0 - - -def sprintf(buf: int, rule: str, *args: Any) -> None: - """格式化写入到缓冲区(不检查边界)。 - - 内部调用 vsnprintf,size 设为 INT_MAX。 - - Args: - buf: 目标缓冲区指针(int) - rule: 格式字符串 - *args: 格式参数 - """ - ... - - -def vsprintf(buf: int, rule: str, *args: Any) -> int: - """可变参数版本的 sprintf。 - - Args: - buf: 目标缓冲区指针(int) - rule: 格式字符串 - *args: 格式参数 - Returns: - 写入字符数 - """ - return 0 - - -def snprintf(buf: int, size: int, fmt: str, *args: Any) -> int: - """格式化写入到缓冲区(带大小限制)。 - - Args: - buf: 目标缓冲区指针(int) - size: 缓冲区最大容量 - fmt: 格式字符串 - *args: 格式参数 - Returns: - 本应写入的字符数 - """ - return 0 - - -def vsnprintf(buf: int, size: int, fmt: str, ap: int) -> int: - """可变参数版本的 snprintf。 - - 支持的格式说明符:%c %s %d %i %u %x %X %o %b %p %f %F %e %E %g %G %a %A %n %% - 支持的修饰符:- 0 + 空格 # 宽度 .精度 h hh l ll z j t - - Args: - buf: 目标缓冲区指针(int) - size: 缓冲区最大容量 - fmt: 格式字符串 - ap: va_list 参数列表指针(int) - Returns: - 写入字符数 - """ - return 0 - - -def _check_special_float(fv: float) -> int: - """检测浮点特殊值(IEEE 754)。 - - Args: - fv: 待检测的 double 值 - Returns: - 0=normal, 1=+inf, 2=-inf, 3=nan - """ - return 0 diff --git a/packages/vscode-vipeye/package.json b/packages/vscode-vipeye/package.json index 5f2849ff..cc5fbef8 100644 --- a/packages/vscode-vipeye/package.json +++ b/packages/vscode-vipeye/package.json @@ -54,6 +54,68 @@ "path": "./syntaxes/viper.tmLanguage.json" } ], + "configurationDefaults": { + "[viper]": { + "editor.semanticHighlighting.enabled": true + } + }, + "semanticTokenScopes": [ + { + "language": "viper", + "scopes": { + "keyword": [ + "keyword.control", + "keyword" + ], + "builtin": [ + "constant.language", + "variable.language", + "support.type.builtin", + "support.function.builtin" + ], + "variable": [ + "variable" + ], + "function": [ + "entity.name.function" + ], + "class": [ + "entity.name.class", + "entity.name.type" + ], + "type": [ + "entity.name.type", + "support.type" + ], + "namespace": [ + "entity.name.namespace" + ], + "string": [ + "string" + ], + "number": [ + "constant.numeric" + ], + "comment": [ + "comment" + ], + "decorator": [ + "entity.name.function.decorator", + "meta.decorator" + ], + "parameter": [ + "variable.parameter" + ], + "property": [ + "variable.other.property", + "variable.other.member" + ], + "undefined": [ + "variable" + ] + } + } + ], "commands": [ { "command": "viper.organizeimports", diff --git a/packages/vscode-vipeye/syntaxes/viper.tmLanguage.json b/packages/vscode-vipeye/syntaxes/viper.tmLanguage.json index 540618fe..cbed1f49 100644 --- a/packages/vscode-vipeye/syntaxes/viper.tmLanguage.json +++ b/packages/vscode-vipeye/syntaxes/viper.tmLanguage.json @@ -3,824 +3,125 @@ "scopeName": "source.viper", "patterns": [ { - "include": "#statement" + "include": "#comment" + }, + { + "include": "#keyword" + }, + { + "include": "#string" + }, + { + "include": "#number" + }, + { + "include": "#decorator" } ], "repository": { - "statement": { + "comment": { "patterns": [ { - "include": "#line-comment" - }, - { - "include": "#type-annotation-string-single" - }, - { - "include": "#type-annotation-string-double" - }, - { - "include": "#string-quoted-double-block" - }, - { - "include": "#string-quoted-single-block" - }, - { - "include": "#string-quoted-double" - }, - { - "include": "#string-quoted-single" - }, - { - "include": "#fstring-double-block" - }, - { - "include": "#fstring-single-block" - }, - { - "include": "#fstring-double" - }, - { - "include": "#fstring-single" - }, - { - "include": "#literal" - }, - { - "include": "#decorator" - }, - { - "include": "#function-declaration" - }, - { - "include": "#class-declaration" - }, - { - "include": "#import-statement" - }, - { - "include": "#reserved" - }, - { - "include": "#builtin-functions" - }, - { - "include": "#module-access" - }, - { - "include": "#class-call" - }, - { - "include": "#function-call" - }, - { - "include": "#builtin-types" - }, - { - "include": "#builtin-constants" - }, - { - "include": "#magic-names" - }, - { - "include": "#language-variable" - }, - { - "include": "#type-name" - }, - { - "include": "#variable" - }, - { - "include": "#illegal" + "name": "comment.line.number-sign.viper", + "match": "#.*$" } ] }, - "line-comment": { - "name": "comment.line.number-sign.viper", - "match": "(\\#).*$", - "captures": { - "1": { - "name": "punctuation.definition.comment.viper" - } - } - }, - "string-quoted-double-block": { - "name": "string.quoted.double.block.viper", - "begin": "(\"\"\")", - "beginCaptures": { - "1": { - "name": "punctuation.definition.string.begin.viper" - } - }, - "end": "(\"\"\")", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.viper" - } - }, + "keyword": { "patterns": [ { - "include": "#escape-sequence-unicode" + "name": "keyword.control.flow.viper", + "match": "\\b(if|elif|else|while|for|in|return|break|continue|pass|raise|try|except|finally|with|as|import|from|yield|async|await|del|assert|lambda|match|case)\\b" }, { - "include": "#escape-sequence" + "name": "storage.type.function.viper", + "match": "\\bdef\\b" }, { - "include": "#string-formatting" + "name": "storage.type.class.viper", + "match": "\\bclass\\b" + }, + { + "name": "keyword.other.viper", + "match": "\\b(global|nonlocal|is|not|and|or|type)\\b" + }, + { + "name": "constant.language.null.viper", + "match": "\\bNone\\b" + }, + { + "name": "constant.language.boolean.viper", + "match": "\\b(True|False)\\b" } ] }, - "string-quoted-single-block": { - "name": "string.quoted.single.block.viper", - "begin": "(''')", - "beginCaptures": { - "1": { - "name": "punctuation.definition.string.begin.viper" - } - }, - "end": "(''')", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.viper" - } - }, + "string": { "patterns": [ { - "include": "#escape-sequence-unicode" - }, - { - "include": "#escape-sequence" - }, - { - "include": "#string-formatting" - } - ] - }, - "string-quoted-double": { - "name": "string.quoted.double.viper", - "begin": "(\")", - "beginCaptures": { - "1": { - "name": "punctuation.definition.string.begin.viper" - } - }, - "end": "(\")", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.viper" - } - }, - "patterns": [ - { - "include": "#escape-sequence-unicode" - }, - { - "include": "#escape-sequence" - }, - { - "include": "#string-formatting" - } - ] - }, - "string-quoted-single": { - "name": "string.quoted.single.viper", - "begin": "(')", - "beginCaptures": { - "1": { - "name": "punctuation.definition.string.begin.viper" - } - }, - "end": "(')", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.viper" - } - }, - "patterns": [ - { - "include": "#escape-sequence-unicode" - }, - { - "include": "#escape-sequence" - }, - { - "include": "#string-formatting" - } - ] - }, - "fstring-double-block": { - "name": "string.quoted.double.block.viper meta.fstring.viper", - "begin": "([fFrR]b?|b[fFrR])(\"\"\")", - "beginCaptures": { - "1": { - "name": "storage.type.string.viper" - }, - "2": { - "name": "punctuation.definition.string.begin.viper" - } - }, - "end": "(\"\"\")", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.viper" - } - }, - "patterns": [ - { - "include": "#fstring-content" - } - ] - }, - "fstring-single-block": { - "name": "string.quoted.single.block.viper meta.fstring.viper", - "begin": "([fFrR]b?|b[fFrR])(''')", - "beginCaptures": { - "1": { - "name": "storage.type.string.viper" - }, - "2": { - "name": "punctuation.definition.string.begin.viper" - } - }, - "end": "(''')", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.viper" - } - }, - "patterns": [ - { - "include": "#fstring-content" - } - ] - }, - "fstring-double": { - "name": "string.quoted.double.viper meta.fstring.viper", - "begin": "([fFrR]b?|b[fFrR])(\")", - "beginCaptures": { - "1": { - "name": "storage.type.string.viper" - }, - "2": { - "name": "punctuation.definition.string.begin.viper" - } - }, - "end": "(\")", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.viper" - } - }, - "patterns": [ - { - "include": "#fstring-content" - } - ] - }, - "fstring-single": { - "name": "string.quoted.single.viper meta.fstring.viper", - "begin": "([fFrR]b?|b[fFrR])(')", - "beginCaptures": { - "1": { - "name": "storage.type.string.viper" - }, - "2": { - "name": "punctuation.definition.string.begin.viper" - } - }, - "end": "(')", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.viper" - } - }, - "patterns": [ - { - "include": "#fstring-content" - } - ] - }, - "fstring-content": { - "patterns": [ - { - "include": "#escape-sequence-unicode" - }, - { - "include": "#escape-sequence" - }, - { - "name": "meta.interpolation.viper", - "begin": "(\\{)", - "beginCaptures": { - "1": { - "name": "keyword.control.decorator.viper" - } - }, - "end": "(\\})", - "endCaptures": { - "1": { - "name": "keyword.control.decorator.viper" - } - }, - "contentName": "meta.embedded", + "name": "string.quoted.double.block.viper", + "begin": "\"\"\"", + "end": "\"\"\"", "patterns": [ { - "include": "#literal" - }, + "include": "#fstring-interpolation" + } + ] + }, + { + "name": "string.quoted.single.block.viper", + "begin": "'''", + "end": "'''", + "patterns": [ { - "include": "#reserved" - }, + "include": "#fstring-interpolation" + } + ] + }, + { + "name": "string.quoted.double.viper", + "begin": "\"", + "end": "\"", + "patterns": [ { - "include": "#builtin-functions" - }, + "include": "#fstring-interpolation" + } + ] + }, + { + "name": "string.quoted.single.viper", + "begin": "'", + "end": "'", + "patterns": [ { - "include": "#builtin-types" - }, - { - "include": "#builtin-constants" - }, - { - "include": "#language-variable" - }, - { - "include": "#module-access" - }, - { - "include": "#class-call" - }, - { - "include": "#function-call" - }, - { - "include": "#string-quoted-single" - }, - { - "include": "#string-quoted-double" - }, - { - "include": "#fstring-interpolation-punctuation" - }, - { - "include": "#variable" + "include": "#fstring-interpolation" } ] } ] }, - "fstring-interpolation-punctuation": { + "fstring-interpolation": { "patterns": [ { - "name": "keyword.operator.assignment.viper", - "match": ":=" - }, - { - "name": "keyword.other.conversion.viper", - "match": "![rsa]" - }, - { - "name": "punctuation.separator.format-spec.viper", - "match": ":" - }, - { - "name": "meta.brace.round.viper", - "match": "[()]" - }, - { - "name": "meta.brace.square.viper", - "match": "[\\[\\]]" - }, - { - "name": "punctuation.separator.arguments.viper", - "match": "," - }, - { - "name": "punctuation.accessor.viper", - "match": "\\." - }, - { - "name": "keyword.operator.assignment.viper", - "match": "=" - }, - { - "name": "keyword.operator.arithmetic.viper", - "match": "[+\\-*/%]" - }, - { - "name": "keyword.operator.comparison.viper", - "match": "[<>=!]=|[<>]" + "name": "meta.embedded.interpolation.viper", + "match": "\\{[^}]*\\}" } ] }, - "escape-sequence-unicode": { - "name": "constant.character.escape.viper", - "match": "\\\\([uU][0-9A-Fa-f]{4,8}|N\\{[^}]+\\})" - }, - "escape-sequence": { - "name": "constant.character.escape.viper", - "match": "\\\\(['\"\\\\\\n\\r\\t\\abfv0-7]|x[0-9A-Fa-f]{2})" - }, - "string-formatting": { - "name": "constant.other.placeholder.viper", - "match": "%(\\([^)]*\\))?[+#-]*(\\d+|\\*)?(?:\\.(\\d+|\\*))?[hlL]?[diouxXeEfFgGcrsa%]" - }, - "literal": { + "number": { "patterns": [ { - "name": "constant.language.viper", - "match": "\\b(True|False|None|NotImplemented|Ellipsis|__debug__)\\b" - }, - { - "name": "constant.numeric.integer.hexadecimal.viper", - "match": "\\b0[xX][_0-9A-Fa-f]+\\b" - }, - { - "name": "constant.numeric.integer.octal.viper", - "match": "\\b0[oO][_0-7]+\\b" - }, - { - "name": "constant.numeric.integer.binary.viper", - "match": "\\b0[bB][_01]+\\b" - }, - { - "name": "constant.numeric.float.viper", - "match": "(?x)\n (?" - }, - { - "name": "punctuation.separator.parameters.viper", - "match": "," - } - ] - }, - "return-type": { - "patterns": [ - { - "include": "#return-type-string-single" - }, - { - "include": "#return-type-string-double" - }, - { - "name": "keyword.operator.arrow.viper", - "match": "->" - }, - { - "include": "#builtin-types" - }, - { - "include": "#type-annotation-content" - } - ] - }, - "reserved": { - "patterns": [ - { - "name": "keyword.control.flow.viper", - "match": "\\b(elif|else|for|if|while|return|yield|with|try|finally|except|raise|break|continue|pass|match|case)\\b" - }, - { - "name": "keyword.control.import.viper", - "match": "\\b(import|from|as)\\b" - }, - { - "name": "keyword.control.flow.viper", - "match": "\\b(and|or|not|in|is)\\b" - }, - { - "name": "storage.type.function.viper", - "match": "\\b(def|lambda)\\b" - }, - { - "name": "storage.type.class.viper", - "match": "\\b(class)\\b" - }, - { - "name": "storage.modifier.async.viper", - "match": "\\b(async|await)\\b" - }, - { - "name": "storage.modifier.declaration.viper", - "match": "\\b(global|nonlocal|del|assert)\\b" - } - ] - }, - "builtin-functions": { - "name": "support.function.builtin.viper", - "match": "(?x)\n \\b(\n __import__|abs|aiter|all|anext|any|ascii|bin|bool|breakpoint|bytearray|\n bytes|callable|chr|classmethod|compile|complex|delattr|dict|dir|divmod|\n enumerate|eval|exec|exit|filter|float|format|frozenset|getattr|globals|\n hasattr|hash|help|hex|id|input|int|isinstance|issubclass|iter|len|\n list|locals|map|max|memoryview|min|next|object|oct|open|ord|pow|print|\n property|quit|range|repr|reversed|round|set|setattr|slice|sorted|\n staticmethod|str|sum|super|tuple|type|vars|zip\n )\\b(?=\\s*\\()" - }, - "function-call": { - "name": "meta.function-call.viper", - "match": "\\b([A-Za-z_][\\w]*)\\s*(?=\\()", - "captures": { - "1": { - "name": "entity.name.function.viper" - } - } - }, - "builtin-types": { - "name": "support.type.builtin.viper", - "match": "(?x)\n \\b(\n Any|Callable|ClassVar|Final|ForwardRef|Generic|Literal|NamedTuple|\n NewType|Optional|ParamSpec|Protocol|Tuple|Type|TypeAlias|TypeVar|\n TypeVarTuple|Union|Unpack|\n int|str|bytes|float|bool|list|dict|tuple|set|frozenset|object|type|\n complex|bytearray|memoryview|range|slice|None|True|False|Ellipsis\n )\\b" - }, - "builtin-constants": { - "name": "support.constant.builtin.viper", - "match": "(?x)\n \\b(\n ArithmeticError|AssertionError|AttributeError|BaseException|\n BaseExceptionGroup|BlockingIOError|BrokenPipeError|BufferError|\n BytesWarning|ChildProcessError|ConnectionAbortedError|\n ConnectionError|ConnectionRefusedError|ConnectionResetError|\n DeprecationWarning|EOFError|EncodingWarning|EnvironmentError|\n Exception|ExceptionGroup|FileExistsError|FileNotFoundError|\n FloatingPointError|FutureWarning|GeneratorExit|IOError|ImportError|\n ImportWarning|IndentationError|IndexError|InterruptedError|\n IsADirectoryError|KeyError|KeyboardInterrupt|LookupError|MemoryError|\n ModuleNotFoundError|NameError|NotADirectoryError|\n NotImplementedWarning|OSError|OverflowError|\n PendingDeprecationWarning|PermissionError|ProcessLookupError|\n RecursionError|ReferenceError|ResourceWarning|RuntimeError|\n RuntimeWarning|StopAsyncIteration|StopIteration|SyntaxError|\n SyntaxWarning|SystemError|SystemExit|TabError|TimeoutError|TypeError|\n UnboundLocalError|UnicodeDecodeError|UnicodeEncodeError|UnicodeError|\n UnicodeTranslateError|UnicodeWarning|UserWarning|ValueError|Warning|\n WindowsError|ZeroDivisionError\n )\\b" - }, - "magic-names": { - "name": "support.variable.magic.viper", - "match": "(?x)\n \\b(\n __all__|__bases__|__basicsize__|__builtins__|__class__|__dict__|\n __doc__|__file__|__flags__|__itemsize__|__members__|__module__|\n __mro__|__name__|__path__|__qualname__|__subclasses__|__version__|\n __abs__|__add__|__aenter__|__aexit__|__aiter__|__and__|__anext__|\n __await__|__bool__|__bytes__|__call__|__cmp__|__coerce__|__complex__|\n __contains__|__del__|__delattr__|__delete__|__delitem__|__delslice__|\n __dir__|__divmod__|__enter__|__eq__|__exit__|__float__|__floordiv__|\n __format__|__ge__|__get__|__getattr__|__getattribute__|__getitem__|\n __getnewargs__|__getslice__|__gt__|__hash__|__hex__|__iadd__|__iand__|\n __ifloordiv__|__ilshift__|__imatmul__|__imod__|__imul__|__index__|\n __init__|__init_subclass__|__int__|__invert__|__ior__|__ipow__|\n __irshift__|__isub__|__iter__|__itruediv__|__ixor__|__le__|__len__|\n __length_hint__|__lshift__|__lt__|__matmul__|__missing__|__mod__|\n __mro_entries__|__mul__|__ne__|__neg__|__new__|__next__|__nonzero__|\n __oct__|__or__|__pos__|__pow__|__radd__|__rand__|__rdivmod__|__repr__|\n __reversed__|__rfloordiv__|__rlshift__|__rmatmul__|__rmod__|__rmul__|\n __ror__|__round__|__rpow__|__rrshift__|__rshift__|__rsub__|\n __rtruediv__|__rxor__|__set__|__setattr__|__setitem__|__setslice__|\n __sizeof__|__str__|__sub__|__subclasshook__|__truediv__|__trunc__|\n __xor__\n )\\b" - }, - "language-variable": { - "name": "variable.language.viper", - "match": "\\b(self|cls)\\b" - }, - "variable": { - "name": "variable.other.viper", - "match": "\\b[A-Za-z_][\\w]*\\b" - }, - "import-statement": { - "patterns": [ - { - "name": "meta.import.viper", - "match": "(?x)\n ^[ \\t]*\n (from)\\s+\n ([A-Za-z_][\\w]*(?:\\.[A-Za-z_][\\w]*)*)\n \\s+(import)\\b", - "captures": { - "1": {"name": "keyword.control.import.viper"}, - "2": {"name": "entity.name.namespace.viper"}, - "3": {"name": "keyword.control.import.viper"} - } - }, - { - "name": "meta.import.viper", - "begin": "(?x)^[ \\t]*(import)\\b\\s*", - "beginCaptures": { - "1": {"name": "keyword.control.import.viper"} - }, - "end": "(?=#|$)", - "patterns": [ - { - "match": "\\b(as)\\b", - "name": "keyword.control.import.viper" - }, - { - "match": "\\b([A-Za-z_][\\w]*(?:\\.[A-Za-z_][\\w]*)*)\\b", - "name": "entity.name.namespace.viper" - }, - { - "match": ",", - "name": "punctuation.separator.import.viper" - } - ] - } - ] - }, - "class-call": { - "name": "meta.class-call.viper", - "match": "\\b([A-Z][\\w]*)\\s*(?=\\()", - "captures": { - "1": {"name": "entity.name.type.class.viper"} - } - }, - "module-access": { - "patterns": [ - { - "name": "meta.module-access.viper", - "match": "(?x)\n \\b(?!self\\b|cls\\b)([A-Za-z_][\\w]*)\n (\\.)\n ([A-Za-z_][\\w]*)\n \\s*(?=\\()", - "captures": { - "1": {"name": "entity.name.namespace.viper"}, - "2": {"name": "punctuation.separator.dot.viper"}, - "3": {"name": "entity.name.function.viper"} - } - }, - { - "name": "meta.module-access.viper", - "match": "(?x)\n \\b(?!self\\b|cls\\b)([A-Za-z_][\\w]*)\n (\\.)\n ([A-Za-z_][\\w]*)", - "captures": { - "1": {"name": "entity.name.namespace.viper"}, - "2": {"name": "punctuation.separator.dot.viper"}, - "3": {"name": "variable.other.property.viper"} - } - } - ] - }, - "type-name": { - "name": "entity.name.type.class.viper", - "match": "\\b[A-Z][\\w]*\\b" - }, - "type-annotation-string-single": { - "comment": "Viper 字符串注解(前向引用):匹配 var: '...' 形式的类型注解字符串", - "begin": "([A-Za-z_][\\w]*)\\s*(:)\\s*(')", - "beginCaptures": { - "1": { "name": "variable.other.property.viper" }, - "2": { "name": "keyword.operator.annotation.viper" }, - "3": { "name": "punctuation.definition.string.begin.viper" } - }, - "end": "(')", - "endCaptures": { - "1": { "name": "punctuation.definition.string.end.viper" } - }, - "contentName": "meta.type-annotation.viper", - "patterns": [ - { "include": "#type-annotation-content" } - ] - }, - "type-annotation-string-double": { - "comment": "Viper 字符串注解(前向引用):匹配 var: \"...\" 形式的类型注解字符串", - "begin": "([A-Za-z_][\\w]*)\\s*(:)\\s*(\")", - "beginCaptures": { - "1": { "name": "variable.other.property.viper" }, - "2": { "name": "keyword.operator.annotation.viper" }, - "3": { "name": "punctuation.definition.string.begin.viper" } - }, - "end": "(\")", - "endCaptures": { - "1": { "name": "punctuation.definition.string.end.viper" } - }, - "contentName": "meta.type-annotation.viper", - "patterns": [ - { "include": "#type-annotation-content" } - ] - }, - "type-annotation-content": { - "comment": "字符串注解内部的类型表达式(类型名、| 组合、None、t.CPtr 等模块限定类型)", - "patterns": [ - { - "name": "keyword.operator.bitwise.or.viper", - "match": "\\|" - }, - { - "name": "variable.language.viper", - "match": "\\bNone\\b" - }, - { - "include": "#builtin-types" - }, - { - "comment": "模块限定类型(如 t.CPtr, t.CInt)", - "match": "(?x)\\b([A-Za-z_][\\w]*)(\\.)([A-Za-z_][\\w]*)\\b", - "captures": { - "1": { "name": "entity.name.namespace.viper" }, - "2": { "name": "punctuation.accessor.viper" }, - "3": { "name": "entity.name.type.class.viper" } - } - }, - { - "name": "punctuation.definition.type-arguments.begin.viper", - "match": "\\[" - }, - { - "name": "punctuation.definition.type-arguments.end.viper", - "match": "\\]" - }, - { - "name": "punctuation.separator.type-arguments.viper", - "match": "," - }, - { - "name": "entity.name.type.class.viper", - "match": "\\b[A-Za-z_][\\w]*\\b" - } - ] - }, - "return-type-string-single": { - "comment": "Viper 返回类型字符串注解:匹配 -> '...' 形式", - "begin": "(->)\\s*(')", - "beginCaptures": { - "1": { "name": "keyword.operator.arrow.viper" }, - "2": { "name": "punctuation.definition.string.begin.viper" } - }, - "end": "(')", - "endCaptures": { - "1": { "name": "punctuation.definition.string.end.viper" } - }, - "contentName": "meta.type-annotation.viper", - "patterns": [ - { "include": "#type-annotation-content" } - ] - }, - "return-type-string-double": { - "comment": "Viper 返回类型字符串注解:匹配 -> \"...\" 形式", - "begin": "(->)\\s*(\")", - "beginCaptures": { - "1": { "name": "keyword.operator.arrow.viper" }, - "2": { "name": "punctuation.definition.string.begin.viper" } - }, - "end": "(\")", - "endCaptures": { - "1": { "name": "punctuation.definition.string.end.viper" } - }, - "contentName": "meta.type-annotation.viper", - "patterns": [ - { "include": "#type-annotation-content" } - ] - }, - "illegal": { - "name": "invalid.illegal.viper", - "match": "\\b\\d+[^\\w\\s]" } } }