diff --git a/docs/configuration.md b/docs/configuration.md index 5468c1558f..b5e7ef606c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -259,7 +259,7 @@ The following settings can be specified for each execution environment. - **pythonPlatform** [string, optional]: Specifies the target platform that will be used for this execution environment. If not specified, the global `pythonPlatform` setting is used instead. -In addition, any of the [type check diagnostics settings](configuration.md/type-check-diagnostics-settings) listed above can be specified. These settings act as overrides for the files in this execution environment. +In addition, any of the [type check diagnostics settings](configuration.md#type-check-diagnostics-settings) listed above can be specified. These settings act as overrides for the files in this execution environment. ## Sample Config File The following is an example of a pyright config file: diff --git a/lerna.json b/lerna.json index b73b5a58d9..fd381616af 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "1.1.368", + "version": "1.1.369", "command": { "version": { "push": false, diff --git a/package-lock.json b/package-lock.json index 690dc7dbb4..48e3437bbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2568,11 +2568,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, - "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -3948,9 +3949,10 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, - "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -4873,8 +4875,9 @@ }, "node_modules/is-number": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -9787,8 +9790,9 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, - "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -12235,10 +12239,12 @@ } }, "braces": { - "version": "3.0.2", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "buffer": { @@ -13121,7 +13127,9 @@ } }, "fill-range": { - "version": "7.0.1", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -13695,6 +13703,8 @@ }, "is-number": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, "is-obj": { @@ -16930,6 +16940,8 @@ }, "to-regex-range": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { "is-number": "^7.0.0" diff --git a/packages/pyright-internal/package-lock.json b/packages/pyright-internal/package-lock.json index 8385a67f86..5c2c57cd5c 100644 --- a/packages/pyright-internal/package-lock.json +++ b/packages/pyright-internal/package-lock.json @@ -1,12 +1,12 @@ { "name": "pyright-internal", - "version": "1.1.368", + "version": "1.1.369", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "pyright-internal", - "version": "1.1.368", + "version": "1.1.369", "license": "MIT", "dependencies": { "@actions/core": "^1.10.1", diff --git a/packages/pyright-internal/package.json b/packages/pyright-internal/package.json index 5c875ac449..4d93c5c6b0 100644 --- a/packages/pyright-internal/package.json +++ b/packages/pyright-internal/package.json @@ -2,7 +2,7 @@ "name": "pyright-internal", "displayName": "pyright", "description": "Type checker for the Python language", - "version": "1.1.368", + "version": "1.1.369", "license": "MIT", "private": true, "files": [ diff --git a/packages/pyright-internal/src/analyzer/cacheManager.ts b/packages/pyright-internal/src/analyzer/cacheManager.ts index d3a8afb1e9..7f5318cf21 100644 --- a/packages/pyright-internal/src/analyzer/cacheManager.ts +++ b/packages/pyright-internal/src/analyzer/cacheManager.ts @@ -115,6 +115,10 @@ export class CacheManager { // Returns a ratio of used bytes to total bytes. getUsedHeapRatio(console?: ConsoleInterface) { + if (this._pausedCount > 0) { + return -1; + } + const heapStats = getHeapStatistics(); let usage = this._getTotalHeapUsage(heapStats); diff --git a/packages/pyright-internal/src/analyzer/checker.ts b/packages/pyright-internal/src/analyzer/checker.ts index 93f506b2d0..afeda8f501 100644 --- a/packages/pyright-internal/src/analyzer/checker.ts +++ b/packages/pyright-internal/src/analyzer/checker.ts @@ -96,6 +96,7 @@ import { OperatorType, StringTokenFlags, TokenType } from '../parser/tokenizerTy import { AnalyzerFileInfo } from './analyzerFileInfo'; import * as AnalyzerNodeInfo from './analyzerNodeInfo'; import { getBoundCallMethod, getBoundInitMethod, getBoundNewMethod } from './constructors'; +import { addInheritedDataClassEntries } from './dataClasses'; import { Declaration, DeclarationType, isAliasDeclaration } from './declaration'; import { getNameNodeForDeclaration } from './declarationUtils'; import { deprecatedAliases, deprecatedSpecialForms } from './deprecatedSymbols'; @@ -166,6 +167,7 @@ import { AnyType, ClassType, ClassTypeFlags, + DataClassEntry, EnumLiteral, FunctionType, FunctionTypeFlags, @@ -983,7 +985,7 @@ export class Checker extends ParseTreeWalker { declaredReturnType, returnType, diagAddendum, - new TypeVarContext(), + /* destTypeVarContext */ undefined, /* srcTypeVarContext */ undefined, AssignTypeFlags.AllowBoolTypeGuard ) @@ -2712,6 +2714,31 @@ export class Checker extends ParseTreeWalker { !this._evaluator.isNodeReachable(statement, prevStatement) && !this._evaluator.isNotTypeCheckingBlock(statement) ) { + // if a statement is a function call where an argument is typed as `Never`, then it's probably an `assert_never` pattern, + // so don't report the statement as unreachable. this check is probably overkill and we could instead just special-case + // `typing.assert_never`, but we want to support user-defined "assert never" functions to be more flexible. + if ( + statement.nodeType === ParseNodeType.StatementList && + statement.statements.find( + (statement) => + statement.nodeType === ParseNodeType.Call && + this._evaluator.matchCallArgsToParams(statement)?.find((result) => + result.match.argParams.find( + (param) => + //check the function parameter type: + param.paramType.category === TypeCategory.Never && + // check the provided argument type (ideally this shouldn't be necessary and it should instead report a reportCallIssue + // if the argument is wrong, but since we're in unreachable code, no type errors occur.) + param.argument.valueExpression && + isNever( + this._evaluator.getTypeOfExpression(param.argument.valueExpression).type + ) + ) + ) + ) + ) { + continue; + } // Create a text range that covers the next statement through // the end of the statement list. const start = statement.start; @@ -5169,6 +5196,13 @@ export class Checker extends ParseTreeWalker { getProtocolSymbolsRecursive(classType, abstractSymbols, ClassTypeFlags.SupportsAbstractMethods); } + // If this is a dataclass, get all of the entries so we can tell which + // ones are initialized by the synthesized __init__ method. + const dataClassEntries: DataClassEntry[] = []; + if (ClassType.isDataClass(classType)) { + addInheritedDataClassEntries(classType, dataClassEntries); + } + ClassType.getSymbolTable(classType).forEach((localSymbol, name) => { abstractSymbols.delete(name); @@ -5202,10 +5236,10 @@ export class Checker extends ParseTreeWalker { return true; } - // If this is part of a dataclass or a class handled by a dataclass_transform, - // exempt it because the class variable will be transformed into an instance - // variable in this case. - if (ClassType.isDataClass(classType)) { + // If this is part of a dataclass, a class handled by a dataclass_transform, + // or a NamedTuple, exempt it because the class variable will be transformed + // into an instance variable in this case. + if (ClassType.isDataClass(classType) || ClassType.isReadOnlyInstanceVariables(classType)) { return true; } @@ -5232,19 +5266,13 @@ export class Checker extends ParseTreeWalker { if (parentSymbol) { return; } - if ( - !classType.details.baseClasses.some( - (baseClass) => - baseClass.category === TypeCategory.Class && baseClass.details.fullName === 'typing.NamedTuple' - ) - ) { - // Report the variable as uninitialized only on the first decl. - this._evaluator.addDiagnostic( - DiagnosticRule.reportUninitializedInstanceVariable, - LocMessage.uninitializedInstanceVariable().format({ name: name }), - decls[0].node - ); - } + + // Report the variable as uninitialized only on the first decl. + this._evaluator.addDiagnostic( + DiagnosticRule.reportUninitializedInstanceVariable, + LocMessage.uninitializedInstanceVariable().format({ name: name }), + decls[0].node + ); }); // See if there are any variables from abstract base classes @@ -5257,20 +5285,30 @@ export class Checker extends ParseTreeWalker { return; } - if (decls[0].type === DeclarationType.Variable) { - // If none of the declarations involve assignments, assume it's - // not implemented in the protocol. - if (!decls.some((decl) => decl.type === DeclarationType.Variable && !!decl.inferredTypeSource)) { - // This is a variable declaration that is not implemented in the - // protocol base class. Make sure it's implemented in the derived class. - diagAddendum.addMessage( - LocAddendum.uninitializedAbstractVariable().format({ - name, - classType: member.classType.details.name, - }) - ); + if (decls[0].type !== DeclarationType.Variable) { + return; + } + + // Dataclass fields are typically exempted from this check because + // they have synthesized __init__ methods that initialize these variables. + const dcEntry = dataClassEntries?.find((entry) => entry.name === name); + if (dcEntry) { + if (dcEntry.includeInInit) { + return; + } + } else { + // Do one or more declarations involve assignments? + if (decls.some((decl) => decl.type === DeclarationType.Variable && !!decl.inferredTypeSource)) { + return; } } + + diagAddendum.addMessage( + LocAddendum.uninitializedAbstractVariable().format({ + name, + classType: member.classType.details.name, + }) + ); }); if (!diagAddendum.isEmpty()) { @@ -5880,7 +5918,15 @@ export class Checker extends ParseTreeWalker { ); } } else { - // TODO - check types of property methods fget, fset, fdel. + this._validateMultipleInheritancePropertyOverride( + overriddenClassAndSymbol.classType, + childClassType, + overriddenType, + overrideType, + overrideSymbol, + memberName, + errorNode + ); } } else { // This check can be expensive, so don't perform it if the corresponding @@ -5977,26 +6023,156 @@ export class Checker extends ParseTreeWalker { } if (diag && overrideDecl && overriddenDecl) { - diag.addRelatedInfo( - LocAddendum.baseClassOverriddenType().format({ - baseClass: this._evaluator.printType(convertToInstance(overriddenClassAndSymbol.classType)), - type: this._evaluator.printType(overriddenType), - }), - overriddenDecl.uri, - overriddenDecl.range - ); - - diag.addRelatedInfo( - LocAddendum.baseClassOverridesType().format({ - baseClass: this._evaluator.printType(convertToInstance(overrideClassAndSymbol.classType)), - type: this._evaluator.printType(overrideType), - }), - overrideDecl.uri, - overrideDecl.range + this._addMultipleInheritanceRelatedInfo( + diag, + overriddenClassAndSymbol.classType, + overriddenType, + overriddenDecl, + overrideClassAndSymbol.classType, + overrideType, + overrideDecl ); } } + private _addMultipleInheritanceRelatedInfo( + diag: Diagnostic, + overriddenClass: ClassType, + overriddenType: Type, + overriddenDecl: Declaration, + overrideClass: ClassType, + overrideType: Type, + overrideDecl: Declaration + ) { + diag.addRelatedInfo( + LocAddendum.baseClassOverriddenType().format({ + baseClass: this._evaluator.printType(convertToInstance(overriddenClass)), + type: this._evaluator.printType(overriddenType), + }), + overriddenDecl.uri, + overriddenDecl.range + ); + + diag.addRelatedInfo( + LocAddendum.baseClassOverridesType().format({ + baseClass: this._evaluator.printType(convertToInstance(overrideClass)), + type: this._evaluator.printType(overrideType), + }), + overrideDecl.uri, + overrideDecl.range + ); + } + + private _validateMultipleInheritancePropertyOverride( + overriddenClassType: ClassType, + overrideClassType: ClassType, + overriddenSymbolType: Type, + overrideSymbolType: Type, + overrideSymbol: Symbol, + memberName: string, + errorNode: ParseNode + ) { + const propMethodInfo: [string, (c: ClassType) => FunctionType | undefined][] = [ + ['fget', (c) => c.fgetInfo?.methodType], + ['fset', (c) => c.fsetInfo?.methodType], + ['fdel', (c) => c.fdelInfo?.methodType], + ]; + + propMethodInfo.forEach((info) => { + const diagAddendum = new DiagnosticAddendum(); + const [methodName, methodAccessor] = info; + const baseClassPropMethod = methodAccessor(overriddenSymbolType as ClassType); + const subclassPropMethod = methodAccessor(overrideSymbolType as ClassType); + + // Is the method present on the base class but missing in the subclass? + if (baseClassPropMethod) { + const baseClassMethodType = partiallySpecializeType(baseClassPropMethod, overriddenClassType); + + if (isFunction(baseClassMethodType)) { + if (!subclassPropMethod) { + // The method is missing. + diagAddendum.addMessage( + LocAddendum.propertyMethodMissing().format({ + name: methodName, + }) + ); + + const decls = overrideSymbol.getDeclarations(); + + if (decls.length > 0) { + const lastDecl = decls[decls.length - 1]; + const diag = this._evaluator.addDiagnostic( + DiagnosticRule.reportIncompatibleMethodOverride, + LocMessage.propertyOverridden().format({ + name: memberName, + className: overriddenClassType.details.name, + }) + diagAddendum.getString(), + errorNode + ); + + const origDecl = baseClassMethodType.details.declaration; + if (diag && origDecl) { + this._addMultipleInheritanceRelatedInfo( + diag, + overriddenClassType, + overriddenSymbolType, + origDecl, + overrideClassType, + overrideSymbolType, + lastDecl + ); + } + } + } else { + const subclassMethodType = partiallySpecializeType(subclassPropMethod, overrideClassType); + + if (isFunction(subclassMethodType)) { + if ( + !this._evaluator.validateOverrideMethod( + baseClassMethodType, + subclassMethodType, + overrideClassType, + diagAddendum.createAddendum() + ) + ) { + diagAddendum.addMessage( + LocAddendum.propertyMethodIncompatible().format({ + name: methodName, + }) + ); + const decl = subclassMethodType.details.declaration; + + if (decl && decl.type === DeclarationType.Function) { + const diag = this._evaluator.addDiagnostic( + DiagnosticRule.reportIncompatibleMethodOverride, + LocMessage.propertyOverridden().format({ + name: memberName, + className: overriddenClassType.details.name, + }) + diagAddendum.getString(), + errorNode + ); + + const origDecl = baseClassMethodType.details.declaration; + if (diag && origDecl) { + this._addMultipleInheritanceRelatedInfo( + diag, + overriddenClassType, + overriddenSymbolType, + origDecl, + overrideClassType, + overrideSymbolType, + decl + ); + } + } + } + } + } + } + } + }); + } + // Validates that any overloaded methods are consistent in how they // are decorated. For example, if the first overload is not marked @final // but subsequent ones are, an error should be reported. @@ -6498,93 +6674,14 @@ export class Checker extends ParseTreeWalker { ); } } else { - const baseClassType = baseClass; - const propMethodInfo: [string, (c: ClassType) => FunctionType | undefined][] = [ - ['fget', (c) => c.fgetInfo?.methodType], - ['fset', (c) => c.fsetInfo?.methodType], - ['fdel', (c) => c.fdelInfo?.methodType], - ]; - - propMethodInfo.forEach((info) => { - const diagAddendum = new DiagnosticAddendum(); - const [methodName, methodAccessor] = info; - const baseClassPropMethod = methodAccessor(baseType as ClassType); - const subclassPropMethod = methodAccessor(overrideType as ClassType); - - // Is the method present on the base class but missing in the subclass? - if (baseClassPropMethod) { - const baseClassMethodType = partiallySpecializeType(baseClassPropMethod, baseClassType); - if (isFunction(baseClassMethodType)) { - if (!subclassPropMethod) { - // The method is missing. - diagAddendum.addMessage( - LocAddendum.propertyMethodMissing().format({ - name: methodName, - }) - ); - const decls = overrideSymbol.getDeclarations(); - if (decls.length > 0) { - const lastDecl = decls[decls.length - 1]; - const diag = this._evaluator.addDiagnostic( - DiagnosticRule.reportIncompatibleMethodOverride, - LocMessage.propertyOverridden().format({ - name: memberName, - className: baseClassType.details.name, - }) + diagAddendum.getString(), - getNameNodeForDeclaration(lastDecl) ?? lastDecl.node - ); - - const origDecl = baseClassMethodType.details.declaration; - if (diag && origDecl) { - diag.addRelatedInfo( - LocAddendum.overriddenMethod(), - origDecl.uri, - origDecl.range - ); - } - } - } else { - const subclassMethodType = partiallySpecializeType(subclassPropMethod, childClassType); - if (isFunction(subclassMethodType)) { - if ( - !this._evaluator.validateOverrideMethod( - baseClassMethodType, - subclassMethodType, - childClassType, - diagAddendum.createAddendum() - ) - ) { - diagAddendum.addMessage( - LocAddendum.propertyMethodIncompatible().format({ - name: methodName, - }) - ); - const decl = subclassMethodType.details.declaration; - if (decl && decl.type === DeclarationType.Function) { - const diag = this._evaluator.addDiagnostic( - DiagnosticRule.reportIncompatibleMethodOverride, - LocMessage.propertyOverridden().format({ - name: memberName, - className: baseClassType.details.name, - }) + diagAddendum.getString(), - decl.node.name - ); - - const origDecl = baseClassMethodType.details.declaration; - if (diag && origDecl) { - diag.addRelatedInfo( - LocAddendum.overriddenMethod(), - origDecl.uri, - origDecl.range - ); - } - } - } - } - } - } - } - }); + this._validatePropertyOverride( + baseClass, + childClassType, + baseType, + overrideType, + overrideSymbol, + memberName + ); } } else { // This check can be expensive, so don't perform it if the corresponding @@ -6785,6 +6882,103 @@ export class Checker extends ParseTreeWalker { } } + private _validatePropertyOverride( + baseClassType: ClassType, + childClassType: ClassType, + baseType: Type, + childType: Type, + overrideSymbol: Symbol, + memberName: string + ) { + const propMethodInfo: [string, (c: ClassType) => FunctionType | undefined][] = [ + ['fget', (c) => c.fgetInfo?.methodType], + ['fset', (c) => c.fsetInfo?.methodType], + ['fdel', (c) => c.fdelInfo?.methodType], + ]; + + propMethodInfo.forEach((info) => { + const diagAddendum = new DiagnosticAddendum(); + const [methodName, methodAccessor] = info; + const baseClassPropMethod = methodAccessor(baseType as ClassType); + const subclassPropMethod = methodAccessor(childType as ClassType); + + // Is the method present on the base class but missing in the subclass? + if (baseClassPropMethod) { + const baseClassMethodType = partiallySpecializeType(baseClassPropMethod, baseClassType); + + if (isFunction(baseClassMethodType)) { + if (!subclassPropMethod) { + // The method is missing. + diagAddendum.addMessage( + LocAddendum.propertyMethodMissing().format({ + name: methodName, + }) + ); + + const decls = overrideSymbol.getDeclarations(); + + if (decls.length > 0) { + const lastDecl = decls[decls.length - 1]; + const diag = this._evaluator.addDiagnostic( + DiagnosticRule.reportIncompatibleMethodOverride, + LocMessage.propertyOverridden().format({ + name: memberName, + className: baseClassType.details.name, + }) + diagAddendum.getString(), + getNameNodeForDeclaration(lastDecl) ?? lastDecl.node + ); + + const origDecl = baseClassMethodType.details.declaration; + if (diag && origDecl) { + diag.addRelatedInfo(LocAddendum.overriddenMethod(), origDecl.uri, origDecl.range); + } + } + } else { + const subclassMethodType = partiallySpecializeType(subclassPropMethod, childClassType); + + if (isFunction(subclassMethodType)) { + if ( + !this._evaluator.validateOverrideMethod( + baseClassMethodType, + subclassMethodType, + childClassType, + diagAddendum.createAddendum() + ) + ) { + diagAddendum.addMessage( + LocAddendum.propertyMethodIncompatible().format({ + name: methodName, + }) + ); + const decl = subclassMethodType.details.declaration; + + if (decl && decl.type === DeclarationType.Function) { + const diag = this._evaluator.addDiagnostic( + DiagnosticRule.reportIncompatibleMethodOverride, + LocMessage.propertyOverridden().format({ + name: memberName, + className: baseClassType.details.name, + }) + diagAddendum.getString(), + decl.node.name + ); + + const origDecl = baseClassMethodType.details.declaration; + if (diag && origDecl) { + diag.addRelatedInfo( + LocAddendum.overriddenMethod(), + origDecl.uri, + origDecl.range + ); + } + } + } + } + } + } + } + }); + } + // Performs checks on a function that is located within a class // and has been determined not to be a property accessor. private _validateMethod(node: FunctionNode, functionType: FunctionType, classNode: ClassNode) { diff --git a/packages/pyright-internal/src/analyzer/codeFlowEngine.ts b/packages/pyright-internal/src/analyzer/codeFlowEngine.ts index 11a9cd9f03..05456ceb25 100644 --- a/packages/pyright-internal/src/analyzer/codeFlowEngine.ts +++ b/packages/pyright-internal/src/analyzer/codeFlowEngine.ts @@ -95,7 +95,7 @@ export interface CodeFlowAnalyzer { } export interface CodeFlowEngine { - createCodeFlowAnalyzer: (typeAtStart: TypeResult | undefined) => CodeFlowAnalyzer; + createCodeFlowAnalyzer: () => CodeFlowAnalyzer; isFlowNodeReachable: (flowNode: FlowNode, sourceFlowNode?: FlowNode, ignoreNoReturn?: boolean) => boolean; narrowConstrainedTypeVar: (flowNode: FlowNode, typeVar: TypeVarType) => Type | undefined; printControlFlowGraph: ( @@ -173,11 +173,7 @@ export function getCodeFlowEngine( // Creates a new code flow analyzer that can be used to narrow the types // of the expressions within an execution context. Each code flow analyzer // instance maintains a cache of types it has already determined. - // The caller should pass a typeAtStart value because the code flow - // analyzer may cache types based on this value, but the typeAtStart - // may vary depending on the context in which the code flow analysis - // is performed. - function createCodeFlowAnalyzer(typeAtStart: TypeResult | undefined): CodeFlowAnalyzer { + function createCodeFlowAnalyzer(): CodeFlowAnalyzer { const flowNodeTypeCacheSet = new Map(); function getFlowNodeTypeCacheForReference(referenceKey: string) { @@ -513,10 +509,6 @@ export function getCodeFlowEngine( } } - if (flowTypeResult && !isFlowNodeReachable(flowNode)) { - flowTypeResult = undefined; - } - return setCacheEntry(curFlowNode, flowTypeResult?.type, !!flowTypeResult?.isIncomplete); } @@ -1226,8 +1218,6 @@ export function getCodeFlowEngine( curFlowNode.flags & (FlowFlags.VariableAnnotation | FlowFlags.Assignment | - FlowFlags.TrueCondition | - FlowFlags.FalseCondition | FlowFlags.WildcardImport | FlowFlags.NarrowForPattern | FlowFlags.ExhaustedMatch) @@ -1235,15 +1225,19 @@ export function getCodeFlowEngine( const typedFlowNode = curFlowNode as | FlowVariableAnnotation | FlowAssignment - | FlowCondition | FlowWildcardImport - | FlowCondition | FlowExhaustedMatch; curFlowNode = typedFlowNode.antecedent; continue; } - if (curFlowNode.flags & (FlowFlags.TrueNeverCondition | FlowFlags.FalseNeverCondition)) { + if ( + curFlowNode.flags & + (FlowFlags.TrueCondition | + FlowFlags.FalseCondition | + FlowFlags.TrueNeverCondition | + FlowFlags.FalseNeverCondition) + ) { const conditionalFlowNode = curFlowNode as FlowCondition; if (conditionalFlowNode.reference) { // Make sure the reference type has a declared type. If not, @@ -1365,7 +1359,7 @@ export function getCodeFlowEngine( // Protect against infinite recursion. if (isReachableRecursionSet.has(flowNode.id)) { - return true; + return false; } isReachableRecursionSet.add(flowNode.id); diff --git a/packages/pyright-internal/src/analyzer/dataClasses.ts b/packages/pyright-internal/src/analyzer/dataClasses.ts index 4d7c459940..8494e90879 100644 --- a/packages/pyright-internal/src/analyzer/dataClasses.ts +++ b/packages/pyright-internal/src/analyzer/dataClasses.ts @@ -995,7 +995,7 @@ function transformDescriptorType(evaluator: TypeEvaluator, type: Type): Type { // the specified class. These entries must be unique and in reverse-MRO // order. Returns true if all of the class types in the hierarchy are // known, false if one or more are unknown. -function addInheritedDataClassEntries(classType: ClassType, entries: DataClassEntry[]) { +export function addInheritedDataClassEntries(classType: ClassType, entries: DataClassEntry[]) { let allAncestorsAreKnown = true; ClassType.getReverseMro(classType).forEach((mroClass) => { diff --git a/packages/pyright-internal/src/analyzer/parseTreeUtils.ts b/packages/pyright-internal/src/analyzer/parseTreeUtils.ts index 7eccb7f7f9..13e33fd594 100644 --- a/packages/pyright-internal/src/analyzer/parseTreeUtils.ts +++ b/packages/pyright-internal/src/analyzer/parseTreeUtils.ts @@ -97,7 +97,7 @@ export function findNodeByPosition( // Returns the deepest node that contains the specified offset. export function findNodeByOffset(node: ParseNode, offset: number): ParseNode | undefined { - if (offset < node.start || offset > TextRange.getEnd(node)) { + if (!TextRange.overlaps(node, offset)) { return undefined; } @@ -109,8 +109,26 @@ export function findNodeByOffset(node: ParseNode, offset: number): ParseNode | u // when there are many siblings, such as statements in a module/suite // or expressions in a list, etc. Otherwise, we will have to traverse // every sibling before finding the correct one. - const index = getIndexContaining(children, offset); + let index = getIndexContaining(children, offset, TextRange.overlaps); + if (index >= 0) { + // Find first sibling that overlaps with the offset. This ensures that + // our binary search result matches what we would have returned via a + // linear search. + let searchIndex = index - 1; + while (searchIndex >= 0) { + const previousChild = children[searchIndex]; + if (previousChild) { + if (TextRange.overlaps(previousChild, offset)) { + index = searchIndex; + } else { + break; + } + } + + searchIndex--; + } + children = [children[index]]; } } diff --git a/packages/pyright-internal/src/analyzer/patternMatching.ts b/packages/pyright-internal/src/analyzer/patternMatching.ts index 656828526a..8740f67fd5 100644 --- a/packages/pyright-internal/src/analyzer/patternMatching.ts +++ b/packages/pyright-internal/src/analyzer/patternMatching.ts @@ -220,9 +220,20 @@ function narrowTypeBasedOnSequencePattern( let canNarrowTuple = entry.isTuple; // Don't attempt to narrow tuples in the negative case if the subject - // contains indeterminate-length entries. - if (!isPositiveTest && entry.isIndeterminateLength) { - canNarrowTuple = false; + // contains indeterminate-length entries or the tuple is of indeterminate + // length. + if (!isPositiveTest) { + if (entry.isIndeterminateLength) { + canNarrowTuple = false; + } + + if ( + isClassInstance(entry.subtype) && + entry.subtype.tupleTypeArguments && + entry.subtype.tupleTypeArguments.some((typeArg) => typeArg.isUnbounded) + ) { + canNarrowTuple = false; + } } // If the subject has an indeterminate length but the pattern does not accept diff --git a/packages/pyright-internal/src/analyzer/typeEvaluator.ts b/packages/pyright-internal/src/analyzer/typeEvaluator.ts index f8bd3aa001..9e1744488d 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluator.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluator.ts @@ -563,6 +563,11 @@ interface CodeFlowAnalyzerCacheEntry { type LogWrapper = any>(func: T) => (...args: Parameters) => ReturnType; +interface SuppressedNodeStackEntry { + node: ParseNode; + suppressedDiags: string[] | undefined; +} + export function createTypeEvaluator( importLookup: ImportLookup, evaluatorOptions: EvaluatorOptions, @@ -571,7 +576,7 @@ export function createTypeEvaluator( const symbolResolutionStack: SymbolResolutionStackEntry[] = []; const asymmetricAccessorAssignmentCache = new Set(); const speculativeTypeTracker = new SpeculativeTypeTracker(); - const suppressedNodeStack: ParseNode[] = []; + const suppressedNodeStack: SuppressedNodeStackEntry[] = []; const assignClassToSelfStack: AssignClassToSelfInfo[] = []; let functionRecursionMap = new Set(); @@ -3276,19 +3281,51 @@ export function createTypeEvaluator( node: ParseNode, range?: TextRange ) { - if (!isDiagnosticSuppressedForNode(node)) { - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - return fileInfo.diagnosticSink.addDiagnosticWithTextRange(diagLevel, message, range || node); + 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; } + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + return fileInfo.diagnosticSink.addDiagnosticWithTextRange(diagLevel, message, range ?? node); + return undefined; } function isDiagnosticSuppressedForNode(node: ParseNode) { - return ( - suppressedNodeStack.some((suppressedNode) => ParseTreeUtils.isNodeContainedWithin(node, suppressedNode)) || - speculativeTypeTracker.isSpeculative(node, /* ignoreIfDiagnosticsAllowed */ true) + 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) { @@ -6236,17 +6273,29 @@ export function createTypeEvaluator( } // Suppress diagnostics for these method calls because they would be redundant. - const callResult = suppressDiagnostics(errorNode, () => { - return validateCallArguments( - errorNode, - argList, - { type: methodType }, - /* typeVarContext */ undefined, - /* skipUnknownArgCheck */ true, - /* inferenceContext */ undefined, - /* signatureTracker */ undefined - ); - }); + const callResult = suppressDiagnostics( + errorNode, + () => { + return validateCallArguments( + errorNode, + argList, + { type: methodType }, + /* typeVarContext */ undefined, + /* skipUnknownArgCheck */ true, + /* inferenceContext */ undefined, + /* signatureTracker */ 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; @@ -6271,34 +6320,6 @@ export function createTypeEvaluator( }; } - // Errors were detected when evaluating the access method call. - if (usage.method === 'set') { - if ( - usage.setType && - isFunction(methodType) && - methodType.details.parameters.length >= 2 && - !usage.setType.isIncomplete - ) { - const setterType = FunctionType.getEffectiveParameterType(methodType, 1); - - diag?.addMessage( - LocAddendum.typeIncompatible().format({ - destType: printType(setterType), - sourceType: printType(usage.setType.type), - }) - ); - } else if (isOverloadedFunction(methodType)) { - diag?.addMessage(LocMessage.noOverload().format({ name: accessMethodName })); - } - } else { - diag?.addMessage( - LocAddendum.descriptorAccessCallFailed().format({ - name: accessMethodName, - className: printType(convertToInstance(methodClassType)), - }) - ); - } - return { type: UnknownType.create(), typeErrors: true, @@ -7542,7 +7563,7 @@ export function createTypeEvaluator( positionalIndexType = makeTupleObject(tupleTypeArgs); } - let argList: FunctionArgument[] = [ + const argList: FunctionArgument[] = [ { argumentCategory: ArgumentCategory.Simple, typeResult: { type: positionalIndexType, isIncomplete: isPositionalIndexTypeIncomplete }, @@ -7588,58 +7609,7 @@ export function createTypeEvaluator( }); }); - let callResult: CallResult | undefined; - - // Speculatively attempt the call. We may need to replace the index - // type with 'int', and we don't want to emit errors before we know - // which type to use. - if (keywordArgs.length === 0 && unpackedDictArgs.length === 0 && positionalArgs.length === 1) { - useSpeculativeMode(node, () => { - callResult = validateCallArguments( - node, - argList, - { type: itemMethodType }, - /* typeVarContext */ undefined, - /* skipUnknownArgCheck */ true, - /* inferenceContext */ undefined, - /* signatureTracker */ undefined - ); - - if (callResult.argumentErrors) { - // If the object supports "__index__" magic method, convert - // the index to an int and try again. - if (isClassInstance(positionalIndexType)) { - const altArgList = [...argList]; - altArgList[0] = { ...altArgList[0] }; - const indexMethod = getBoundMagicMethod(positionalIndexType, '__index__'); - - if (indexMethod) { - const intType = getBuiltInObject(node, 'int'); - if (isClassInstance(intType)) { - altArgList[0].typeResult = { type: intType }; - } - } - - callResult = validateCallArguments( - node, - altArgList, - { type: itemMethodType }, - /* typeVarContext */ undefined, - /* skipUnknownArgCheck */ true, - /* inferenceContext */ undefined, - /* signatureTracker */ undefined - ); - - // We were successful, so replace the arg list. - if (!callResult.argumentErrors) { - argList = altArgList; - } - } - } - }); - } - - callResult = validateCallArguments( + const callResult = validateCallArguments( node, argList, { type: itemMethodType }, @@ -9186,7 +9156,7 @@ export function createTypeEvaluator( 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 (!isDiagnosticSuppressedForNode(errorNode)) { + if (!canSkipDiagnosticForNode(errorNode)) { const functionName = typeResult.type.overloads[0].details.name || ''; const diagAddendum = new DiagnosticAddendum(); const argTypes = argList.map((t) => { @@ -9329,7 +9299,7 @@ export function createTypeEvaluator( // 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 (!isDiagnosticSuppressedForNode(errorNode) && !isTypeIncomplete) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { const result = evaluateUsingBestMatchingOverload( /* skipUnknownArgCheck */ true, /* emitNoOverloadFoundError */ true @@ -10660,7 +10630,7 @@ export function createTypeEvaluator( } if (tooManyPositionals) { - if (!isDiagnosticSuppressedForNode(errorNode) && !isTypeIncomplete) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { addDiagnostic( DiagnosticRule.reportCallIssue, positionParamLimitIndex === 1 @@ -10711,7 +10681,7 @@ export function createTypeEvaluator( argTypeResult.type.paramSpecAccess === 'args' && paramDetails.params[paramIndex].param.category !== ParameterCategory.ArgsList ) { - if (!isDiagnosticSuppressedForNode(errorNode) && !isTypeIncomplete) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { addDiagnostic( DiagnosticRule.reportCallIssue, positionParamLimitIndex === 1 @@ -10790,7 +10760,7 @@ export function createTypeEvaluator( // 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 (!isDiagnosticSuppressedForNode(errorNode) && !isTypeIncomplete) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { addDiagnostic( DiagnosticRule.reportCallIssue, LocMessage.unpackedArgWithVariadicParam(), @@ -10863,7 +10833,7 @@ export function createTypeEvaluator( if (remainingArgCount <= remainingParamCount) { if (remainingArgCount < remainingParamCount) { - if (!isDiagnosticSuppressedForNode(errorNode) && !isTypeIncomplete) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { // Have we run out of arguments and still have parameters left to fill? addDiagnostic( DiagnosticRule.reportCallIssue, @@ -10965,7 +10935,7 @@ export function createTypeEvaluator( } if (argsRemainingCount > 0) { - if (!isDiagnosticSuppressedForNode(errorNode) && !isTypeIncomplete) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { addDiagnostic( DiagnosticRule.reportCallIssue, argsRemainingCount === 1 @@ -10989,11 +10959,16 @@ export function createTypeEvaluator( while (argIndex < argList.length) { if (argList[argIndex].argumentCategory === ArgumentCategory.UnpackedDictionary) { // Verify that the type used in this expression is a SupportsKeysAndGetItem[str, T]. - const argType = getTypeOfArgument( + const argTypeResult = getTypeOfArgument( argList[argIndex], makeInferenceContext(paramDetails.unpackedKwargsTypedDictType), signatureTracker - ).type; + ); + const argType = argTypeResult.type; + + if (argTypeResult.isIncomplete) { + isTypeIncomplete = true; + } if (isAnyOrUnknown(argType)) { unpackedDictionaryArgType = argType; @@ -11061,7 +11036,7 @@ export function createTypeEvaluator( }); if (!diag.isEmpty()) { - if (!isDiagnosticSuppressedForNode(errorNode) && !isTypeIncomplete) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { addDiagnostic( DiagnosticRule.reportCallIssue, LocMessage.unpackedTypedDictArgument() + diag.getString(), @@ -11139,7 +11114,7 @@ export function createTypeEvaluator( } if (!isValidMappingType) { - if (!isDiagnosticSuppressedForNode(errorNode) && !isTypeIncomplete) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { addDiagnostic( DiagnosticRule.reportCallIssue, LocMessage.unpackedDictArgumentNotMapping(), @@ -11164,7 +11139,7 @@ export function createTypeEvaluator( const paramEntry = paramMap.get(paramNameValue); if (paramEntry && !paramEntry.isPositionalOnly) { if (paramEntry.argsReceived > 0) { - if (!isDiagnosticSuppressedForNode(errorNode) && !isTypeIncomplete) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { addDiagnostic( DiagnosticRule.reportCallIssue, LocMessage.paramAlreadyAssigned().format({ name: paramNameValue }), @@ -11216,7 +11191,7 @@ export function createTypeEvaluator( ); trySetActive(argList[argIndex], paramDetails.params[paramDetails.kwargsIndex].param); } else { - if (!isDiagnosticSuppressedForNode(errorNode) && !isTypeIncomplete) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { addDiagnostic( DiagnosticRule.reportCallIssue, LocMessage.paramNameMissing().format({ name: paramName.value }), @@ -11229,7 +11204,7 @@ export function createTypeEvaluator( if (paramSpecArgList) { paramSpecArgList.push(argList[argIndex]); } else { - if (!isDiagnosticSuppressedForNode(errorNode) && !isTypeIncomplete) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { addDiagnostic( DiagnosticRule.reportCallIssue, positionParamLimitIndex === 1 @@ -11321,9 +11296,9 @@ export function createTypeEvaluator( }); if (unassignedParams.length > 0) { - if (!isDiagnosticSuppressedForNode(errorNode)) { + if (!canSkipDiagnosticForNode(errorNode)) { const missingParamNames = unassignedParams.map((p) => `"${p}"`).join(', '); - if (!isDiagnosticSuppressedForNode(errorNode) && !isTypeIncomplete) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { addDiagnostic( DiagnosticRule.reportCallIssue, unassignedParams.length === 1 @@ -11415,7 +11390,7 @@ export function createTypeEvaluator( argParam.argument.argumentCategory !== ArgumentCategory.UnpackedList && !argParam.mapsToVarArgList ) { - if (!isDiagnosticSuppressedForNode(errorNode) && !isTypeIncomplete) { + if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { addDiagnostic( DiagnosticRule.reportCallIssue, LocMessage.typeVarTupleMustBeUnpacked(), @@ -11949,6 +11924,11 @@ export function createTypeEvaluator( paramSpecTypeVarContext.forEach((paramSpecTypeVarContext) => { if (paramSpecTypeVarContext) { specializedReturnType = applySolvedTypeVars(specializedReturnType, paramSpecTypeVarContext); + + // It's possible that one or more of the TypeVars or ParamSpecs + // in the typeVarContext refer to TypeVars that were solved in + // the paramSpecTypeVarContext. Apply these solved TypeVars accordingly. + applySourceContextTypeVars(typeVarContext, paramSpecTypeVarContext); } }); } @@ -12544,7 +12524,7 @@ export function createTypeEvaluator( const fileInfo = AnalyzerNodeInfo.getFileInfo(argParam.errorNode); if ( fileInfo.diagnosticRuleSet.reportArgumentType !== 'none' && - !isDiagnosticSuppressedForNode(argParam.errorNode) && + !canSkipDiagnosticForNode(argParam.errorNode) && !isTypeIncomplete ) { const argTypeText = printType(argType); @@ -14780,9 +14760,7 @@ export function createTypeEvaluator( { dependentType: inferenceContext?.expectedType, allowDiagnostics: - !forceSpeculative && - !isDiagnosticSuppressedForNode(node) && - !inferenceContext?.isTypeIncomplete, + !forceSpeculative && !canSkipDiagnosticForNode(node) && !inferenceContext?.isTypeIncomplete, } ); @@ -20238,7 +20216,7 @@ export function createTypeEvaluator( } // Allocate a new code flow analyzer. - const analyzer = codeFlowEngine.createCodeFlowAnalyzer(typeAtStart); + const analyzer = codeFlowEngine.createCodeFlowAnalyzer(); if (entries) { entries.push({ typeAtStart, codeFlowAnalyzer: analyzer }); } else { @@ -21036,12 +21014,19 @@ export function createTypeEvaluator( } // Disables recording of errors and warnings. - function suppressDiagnostics(node: ParseNode, callback: () => T) { - suppressedNodeStack.push(node); + function suppressDiagnostics( + node: ParseNode, + callback: () => T, + diagCallback?: (suppressedDiags: string[]) => void + ) { + suppressedNodeStack.push({ node, suppressedDiags: diagCallback ? [] : undefined }); try { const result = callback(); - suppressedNodeStack.pop(); + 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 @@ -22558,7 +22543,7 @@ export function createTypeEvaluator( const prevTypeCache = returnTypeInferenceTypeCache; returnTypeInferenceContextStack.push({ functionNode, - codeFlowAnalyzer: codeFlowEngine.createCodeFlowAnalyzer(/* typeAtStart */ undefined), + codeFlowAnalyzer: codeFlowEngine.createCodeFlowAnalyzer(), }); try { @@ -23175,6 +23160,9 @@ export function createTypeEvaluator( destTypeArgs.splice(destTypeArgs.length - 1, 1); } + const srcArgsToCapture = srcTypeArgs.length - destTypeArgs.length + 1; + let skipAdjustSrc = false; + // If we're doing reverse type mappings and the source contains a variadic // TypeVar, we need to adjust the dest so the reverse type mapping assignment // can be performed. @@ -23207,10 +23195,10 @@ export function createTypeEvaluator( isUnbounded: false, }); } + + skipAdjustSrc = true; } } else { - const srcArgsToCapture = srcTypeArgs.length - destTypeArgs.length + 1; - if (destUnboundedOrVariadicIndex >= 0 && srcArgsToCapture >= 0) { // If the dest contains a variadic element, determine which source // args map to this element and package them up into an unpacked tuple. @@ -23243,37 +23231,35 @@ export function createTypeEvaluator( isUnbounded: false, }); } - } else { - // If possible, package up the source entries that correspond to - // the dest unbounded tuple. This isn't possible if the source contains - // an unbounded tuple outside of this range. - if ( - srcUnboundedIndex < 0 || - (srcUnboundedIndex >= destUnboundedOrVariadicIndex && - srcUnboundedIndex < destUnboundedOrVariadicIndex + srcArgsToCapture) - ) { - const removedArgTypes = srcTypeArgs - .splice(destUnboundedOrVariadicIndex, srcArgsToCapture) - .map((t) => { - if ( - isTypeVar(t.type) && - isUnpackedVariadicTypeVar(t.type) && - !t.type.isVariadicInUnion - ) { - return TypeVarType.cloneForUnpacked(t.type, /* isInUnion */ true); - } - return t.type; - }); - srcTypeArgs.splice(destUnboundedOrVariadicIndex, 0, { - type: removedArgTypes.length > 0 ? combineTypes(removedArgTypes) : AnyType.create(), - isUnbounded: false, - }); - } + skipAdjustSrc = true; } } } + if (!skipAdjustSrc && destUnboundedOrVariadicIndex >= 0 && srcArgsToCapture >= 0) { + // If possible, package up the source entries that correspond to + // the dest unbounded tuple. This isn't possible if the source contains + // an unbounded tuple outside of this range. + if ( + srcUnboundedIndex < 0 || + (srcUnboundedIndex >= destUnboundedOrVariadicIndex && + srcUnboundedIndex < destUnboundedOrVariadicIndex + srcArgsToCapture) + ) { + const removedArgTypes = srcTypeArgs.splice(destUnboundedOrVariadicIndex, srcArgsToCapture).map((t) => { + if (isTypeVar(t.type) && isUnpackedVariadicTypeVar(t.type) && !t.type.isVariadicInUnion) { + return TypeVarType.cloneForUnpacked(t.type, /* isInUnion */ true); + } + return t.type; + }); + + srcTypeArgs.splice(destUnboundedOrVariadicIndex, 0, { + type: removedArgTypes.length > 0 ? combineTypes(removedArgTypes) : AnyType.create(), + isUnbounded: false, + }); + } + } + return destTypeArgs.length === srcTypeArgs.length; } @@ -24514,7 +24500,7 @@ export function createTypeEvaluator( destType, concreteSrcType, diag?.createAddendum(), - destTypeVarContext ?? new TypeVarContext(getTypeVarScopeId(destType)), + destTypeVarContext ?? new TypeVarContext(getTypeVarScopeIds(destType)), srcTypeVarContext ?? new TypeVarContext(getTypeVarScopeIds(concreteSrcType)), flags, recursionCount diff --git a/packages/pyright-internal/src/analyzer/typeUtils.ts b/packages/pyright-internal/src/analyzer/typeUtils.ts index c324766d7a..75de2b068a 100644 --- a/packages/pyright-internal/src/analyzer/typeUtils.ts +++ b/packages/pyright-internal/src/analyzer/typeUtils.ts @@ -3114,8 +3114,11 @@ export function computeMroLinearization(classType: ClassType): boolean { // The first class in the MRO is the class itself. const typeVarContext = buildTypeVarContextFromSpecializedClass(classType); - const specializedClassType = applySolvedTypeVars(classType, typeVarContext); - assert(isClass(specializedClassType) || isAny(specializedClassType) || isUnknown(specializedClassType)); + let specializedClassType = applySolvedTypeVars(classType, typeVarContext); + if (!isClass(specializedClassType) && !isAny(specializedClassType) && !isUnknown(specializedClassType)) { + specializedClassType = UnknownType.create(); + } + classType.details.mro.push(specializedClassType); // Helper function that returns true if the specified searchClass @@ -3326,7 +3329,7 @@ export function convertParamSpecValueToType(type: FunctionType): Type { FunctionType.addHigherOrderTypeVarScopeIds(functionType, withoutParamSpec.details.typeVarScopeId); FunctionType.addHigherOrderTypeVarScopeIds(functionType, withoutParamSpec.details.higherOrderTypeVarScopeIds); - withoutParamSpec.details.parameters.forEach((entry) => { + withoutParamSpec.details.parameters.forEach((entry, index) => { FunctionType.addParameter(functionType, { category: entry.category, name: entry.name, @@ -3334,7 +3337,7 @@ export function convertParamSpecValueToType(type: FunctionType): Type { defaultValueExpression: entry.defaultValueExpression, isNameSynthesized: entry.isNameSynthesized, hasDeclaredType: true, - type: entry.type, + type: FunctionType.getEffectiveParameterType(withoutParamSpec, index), }); }); diff --git a/packages/pyright-internal/src/common/diagnostic.ts b/packages/pyright-internal/src/common/diagnostic.ts index dfd35e68bf..f6d83df367 100644 --- a/packages/pyright-internal/src/common/diagnostic.ts +++ b/packages/pyright-internal/src/common/diagnostic.ts @@ -169,6 +169,12 @@ export class DiagnosticAddendum { this._messages.push(message); } + addMessageMultiline(message: string) { + message.split('\n').forEach((line) => { + this._messages.push(line); + }); + } + addTextRange(range: TextRange) { this._range = range; } diff --git a/packages/pyright-internal/src/common/docStringService.ts b/packages/pyright-internal/src/common/docStringService.ts index e2319a6bed..05f08d6b3c 100644 --- a/packages/pyright-internal/src/common/docStringService.ts +++ b/packages/pyright-internal/src/common/docStringService.ts @@ -6,6 +6,7 @@ * Interface for service that parses docstrings and converts them to other formats. */ +import { MarkupKind } from 'vscode-languageserver-types'; import { convertDocStringToMarkdown, convertDocStringToPlainText } from '../analyzer/docStringConversion'; import { extractParameterDocumentation } from '../analyzer/docStringUtils'; @@ -15,6 +16,7 @@ export interface DocStringService { extractParameterDocumentation( functionDocString: string, paramName: string, + format?: MarkupKind, forceLiteral?: boolean ): string | undefined; clone(): DocStringService; diff --git a/packages/pyright-internal/src/common/textRangeCollection.ts b/packages/pyright-internal/src/common/textRangeCollection.ts index 5012f37260..8fdbdf2615 100644 --- a/packages/pyright-internal/src/common/textRangeCollection.ts +++ b/packages/pyright-internal/src/common/textRangeCollection.ts @@ -100,7 +100,11 @@ export class TextRangeCollection { } } -export function getIndexContaining(arr: (T | undefined)[], position: number) { +export function getIndexContaining( + arr: (T | undefined)[], + position: number, + inRange: (item: T, position: number) => boolean = TextRange.contains +) { if (arr.length === 0) { return -1; } @@ -109,25 +113,25 @@ export function getIndexContaining(arr: (T | undefined)[], let max = arr.length - 1; while (min <= max) { const mid = Math.floor(min + (max - min) / 2); - const item = findNonNullElement(arr, mid, min, max); - if (item === undefined) { + const element = findNonNullElement(arr, mid, min, max); + if (element === undefined) { return -1; } - if (TextRange.contains(item, position)) { - return mid; + if (inRange(element.item, position)) { + return element.index; } - const nextItem = findNonNullElement(arr, mid + 1, mid + 1, max); - if (nextItem === undefined) { + const nextElement = findNonNullElement(arr, mid + 1, mid + 1, max); + if (nextElement === undefined) { return -1; } - if (mid < arr.length - 1 && TextRange.getEnd(item) <= position && position < nextItem.start) { + if (mid < arr.length - 1 && TextRange.getEnd(element.item) <= position && position < nextElement.item.start) { return -1; } - if (position < item.start) { + if (position < element.item.start) { max = mid - 1; } else { min = mid + 1; @@ -142,24 +146,24 @@ function findNonNullElement( position: number, min: number, max: number -): T | undefined { +): { index: number; item: T } | undefined { const item = arr[position]; if (item) { - return item; + return { index: position, item }; } // Search forward and backward until it finds non-null value. for (let i = position + 1; i <= max; i++) { - const item = arr[position]; + const item = arr[i]; if (item) { - return item; + return { index: i, item }; } } for (let i = position - 1; i >= min; i--) { - const item = arr[position]; + const item = arr[i]; if (item) { - return item; + return { index: i, item }; } } diff --git a/packages/pyright-internal/src/languageService/autoImporter.ts b/packages/pyright-internal/src/languageService/autoImporter.ts index 5e1b2ff402..b051a3572a 100644 --- a/packages/pyright-internal/src/languageService/autoImporter.ts +++ b/packages/pyright-internal/src/languageService/autoImporter.ts @@ -118,8 +118,8 @@ export function addModuleSymbolsMap(files: readonly SourceFileInfo[], moduleSymb const fileName = stripFileExtension(uri.fileName); // Don't offer imports from files that are named with private - // naming semantics like "_ast.py". - if (SymbolNameUtils.isPrivateOrProtectedName(fileName)) { + // naming semantics like "_ast.py" unless they're in the current userfile list. + if (SymbolNameUtils.isPrivateOrProtectedName(fileName) && !isUserCode(file)) { return; } diff --git a/packages/pyright-internal/src/languageService/hoverProvider.ts b/packages/pyright-internal/src/languageService/hoverProvider.ts index f409c62b47..570b1b500c 100644 --- a/packages/pyright-internal/src/languageService/hoverProvider.ts +++ b/packages/pyright-internal/src/languageService/hoverProvider.ts @@ -19,8 +19,9 @@ import { } from '../analyzer/declaration'; import * as ParseTreeUtils from '../analyzer/parseTreeUtils'; import { SourceMapper } from '../analyzer/sourceMapper'; +import { isBuiltInModule } from '../analyzer/typeDocStringUtils'; import { PrintTypeOptions, TypeEvaluator } from '../analyzer/typeEvaluatorTypes'; -import { doForEachSubtype, isMaybeDescriptorInstance } from '../analyzer/typeUtils'; +import { convertToInstance, doForEachSubtype, isMaybeDescriptorInstance } from '../analyzer/typeUtils'; import { ClassType, Type, @@ -38,6 +39,7 @@ 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, NameNode, ParseNode, ParseNodeType, StringNode } from '../parser/parseNodes'; @@ -49,8 +51,6 @@ import { getToolTipForType, getTypeForToolTip, } from './tooltipUtils'; -import { ServiceProvider } from '../common/serviceProvider'; -import { isBuiltInModule } from '../analyzer/typeDocStringUtils'; export interface HoverTextPart { python?: boolean; @@ -134,8 +134,9 @@ export function getVariableTypeText( ) { let label = declaration.isConstant || evaluator.isFinalVariableDeclaration(declaration) ? 'constant' : 'variable'; - let expandTypeAlias = false; + const expandTypeAlias = false; let typeVarName: string | undefined; + if (type.typeAliasInfo && typeNode.nodeType === ParseNodeType.Name) { const typeAliasInfo = getTypeAliasInfo(type); if (typeAliasInfo?.name === typeNode.value) { @@ -143,8 +144,12 @@ export function getVariableTypeText( label = type.details.isParamSpec ? 'param spec' : 'type variable'; typeVarName = type.details.name; } else { - expandTypeAlias = true; - label = 'type alias'; + // Handle type aliases specially. + const typeText = evaluator.printType(convertToInstance(getTypeForToolTip(evaluator, typeNode)), { + expandTypeAlias: true, + }); + + return `(type) ${name} = ` + typeText; } } } @@ -155,7 +160,7 @@ export function getVariableTypeText( } const typeText = - typeVarName || name + ': ' + evaluator.printType(getTypeForToolTip(evaluator, typeNode), { expandTypeAlias }); + typeVarName ?? name + ': ' + evaluator.printType(getTypeForToolTip(evaluator, typeNode), { expandTypeAlias }); return `(${label}) ` + typeText; } @@ -412,8 +417,9 @@ export class HoverProvider { } case DeclarationType.TypeAlias: { - const typeText = node.value + this._getTypeText(node, { expandTypeAlias: true }); - this._addResultsPart(parts, `(type alias) ${typeText}`, /* python */ true); + const type = convertToInstance(this._getType(node)); + const typeText = this._evaluator.printType(type, { expandTypeAlias: true }); + this._addResultsPart(parts, `(type) ${node.value} = ${typeText}`, /* python */ true); this._addDocumentationPart(parts, node, resolvedDecl); break; } diff --git a/packages/pyright-internal/src/languageService/signatureHelpProvider.ts b/packages/pyright-internal/src/languageService/signatureHelpProvider.ts index 9ce148221a..01de1991e6 100644 --- a/packages/pyright-internal/src/languageService/signatureHelpProvider.ts +++ b/packages/pyright-internal/src/languageService/signatureHelpProvider.ts @@ -140,12 +140,17 @@ export class SignatureHelpProvider { const signatures = signatureHelpResults.signatures.map((sig) => { let paramInfo: ParameterInformation[] = []; if (sig.parameters) { - paramInfo = sig.parameters.map((param) => - ParameterInformation.create( - this._hasSignatureLabelOffsetCapability ? [param.startOffset, param.endOffset] : param.text, - param.documentation - ) - ); + paramInfo = sig.parameters.map((param) => { + return { + label: this._hasSignatureLabelOffsetCapability + ? [param.startOffset, param.endOffset] + : param.text, + documentation: { + kind: this._format, + value: param.documentation ?? '', + }, + }; + }); } const sigInfo = SignatureInformation.create(sig.label, /* documentation */ undefined, ...paramInfo); @@ -244,7 +249,6 @@ export class SignatureHelpProvider { startOffset: label.length, endOffset: label.length + paramString.length, text: paramString, - documentation: this._docStringService.extractParameterDocumentation(functionDocString || '', paramName), }); // Name match for active parameter. The set of parameters from the function @@ -268,6 +272,18 @@ export class SignatureHelpProvider { } } + // Extract the documentation only for the active parameter. + if (activeParameter !== undefined) { + const activeParam = parameters[activeParameter]; + if (activeParam) { + activeParam.documentation = this._docStringService.extractParameterDocumentation( + functionDocString || '', + params[activeParameter].name || '', + this._format + ); + } + } + const sigInfo: SignatureInfo = { label, parameters, diff --git a/packages/pyright-internal/src/localization/package.nls.cs.json b/packages/pyright-internal/src/localization/package.nls.cs.json index bf55083e91..a7d138fc2c 100644 --- a/packages/pyright-internal/src/localization/package.nls.cs.json +++ b/packages/pyright-internal/src/localization/package.nls.cs.json @@ -15,6 +15,7 @@ }, "Diagnostic": { "abstractMethodInvocation": "Metodu „{method}“ nelze volat, protože je abstraktní a neimplementovaná.", + "annotatedMetadataInconsistent": "Annotated metadata type \"{metadataType}\" is not compatible with type \"{type}\"", "annotatedParamCountMismatch": "Počet poznámek parametrů se neshoduje: očekával(o/y) se {expected}, ale přijal(o/y) se {received}.", "annotatedTypeArgMissing": "Byl očekáván jeden argument typu a jedna nebo více poznámek pro Annotated", "annotationBytesString": "Poznámky typu nemůžou používat řetězcové literály bajtů.", @@ -158,6 +159,7 @@ "enumMemberSet": "Člen výčtu {name} se nedá přiřadit.", "enumMemberTypeAnnotation": "Poznámky typu nejsou pro členy výčtu povolené", "exceptionGroupIncompatible": "Syntaxe skupiny výjimek (except*) vyžaduje Python 3.11 nebo novější", + "exceptionGroupTypeIncorrect": "Exception type in except* cannot derive from BaseGroupException", "exceptionTypeIncorrect": "„{type}“ se neodvozuje od BaseException", "exceptionTypeNotClass": "{type} není platná třída výjimky", "exceptionTypeNotInstantiable": "Konstruktor pro výjimku typu {type} vyžaduje jeden nebo více argumentů", diff --git a/packages/pyright-internal/src/localization/package.nls.de.json b/packages/pyright-internal/src/localization/package.nls.de.json index 4d60bee3fc..b9d232311e 100644 --- a/packages/pyright-internal/src/localization/package.nls.de.json +++ b/packages/pyright-internal/src/localization/package.nls.de.json @@ -15,6 +15,7 @@ }, "Diagnostic": { "abstractMethodInvocation": "Die Methode „{method}“ kann nicht aufgerufen werden, da sie abstrakt und nicht implementiert ist.", + "annotatedMetadataInconsistent": "Annotated metadata type \"{metadataType}\" is not compatible with type \"{type}\"", "annotatedParamCountMismatch": "Nicht übereinstimmende Parameteranmerkungsanzahl: {expected} erwartet, aber {received} empfangen", "annotatedTypeArgMissing": "Es wurde ein Typargument und mindestens eine Anmerkung für \"Annotated\" erwartet.", "annotationBytesString": "Typanmerkungen dürfen keine Bytes-Zeichenfolgenliterale verwenden.", @@ -158,6 +159,7 @@ "enumMemberSet": "Das Enumerationselement \"{name}\" kann nicht zugewiesen werden.", "enumMemberTypeAnnotation": "Typanmerkungen sind für Enumerationsmember nicht zulässig", "exceptionGroupIncompatible": "Die Ausnahmegruppensyntax (\"except*\") erfordert Python 3.11 oder höher.", + "exceptionGroupTypeIncorrect": "Exception type in except* cannot derive from BaseGroupException", "exceptionTypeIncorrect": "\"{type}\" ist nicht von BaseException abgeleitet.", "exceptionTypeNotClass": "\"{type}\" ist keine gültige Ausnahmeklasse.", "exceptionTypeNotInstantiable": "Der Konstruktor für den Ausnahmetyp \"{type}\" erfordert mindestens ein Argument.", diff --git a/packages/pyright-internal/src/localization/package.nls.es.json b/packages/pyright-internal/src/localization/package.nls.es.json index c134c1c1c5..3e056930b2 100644 --- a/packages/pyright-internal/src/localization/package.nls.es.json +++ b/packages/pyright-internal/src/localization/package.nls.es.json @@ -15,6 +15,7 @@ }, "Diagnostic": { "abstractMethodInvocation": "No se puede llamar al método \"{method}\" porque es abstracto y no se ha implementado.", + "annotatedMetadataInconsistent": "Annotated metadata type \"{metadataType}\" is not compatible with type \"{type}\"", "annotatedParamCountMismatch": "El recuento de anotaciones del parámetro no coincide: se esperaba {expected}, pero se recibió {received}", "annotatedTypeArgMissing": "Se espera un argumento de tipo y una o más anotaciones para \"Anotado\".", "annotationBytesString": "Las anotaciones de tipo no pueden utilizar literales de cadena de bytes", @@ -158,6 +159,7 @@ "enumMemberSet": "No se puede asignar el miembro de enumeración \"{name}\"", "enumMemberTypeAnnotation": "No se permiten anotaciones de tipo para miembros de enumeración", "exceptionGroupIncompatible": "La sintaxis de grupo de excepciones (\"except*\") requiere Python 3.11 o posterior.", + "exceptionGroupTypeIncorrect": "Exception type in except* cannot derive from BaseGroupException", "exceptionTypeIncorrect": "\"{type}\" no se deriva de BaseException", "exceptionTypeNotClass": "\"{type}\" no es una clase de excepción válida", "exceptionTypeNotInstantiable": "El constructor para el tipo de excepción \"{type}\" requiere uno o más argumentos", diff --git a/packages/pyright-internal/src/localization/package.nls.fr.json b/packages/pyright-internal/src/localization/package.nls.fr.json index f9acfdb327..150be8bca7 100644 --- a/packages/pyright-internal/src/localization/package.nls.fr.json +++ b/packages/pyright-internal/src/localization/package.nls.fr.json @@ -15,6 +15,7 @@ }, "Diagnostic": { "abstractMethodInvocation": "Désolé, nous n’avons pas pu appeler la méthode « {method} », car elle est abstraite et non implémentée", + "annotatedMetadataInconsistent": "Annotated metadata type \"{metadataType}\" is not compatible with type \"{type}\"", "annotatedParamCountMismatch": "Non-concordance du nombre d'annotations de paramètre : attendu {expected} mais reçu {received}", "annotatedTypeArgMissing": "Un argument de type et une ou plusieurs annotations sont attendus pour « Annotation »", "annotationBytesString": "Les annotations de type ne peuvent pas utiliser de littéraux de chaîne d’octets", @@ -158,6 +159,7 @@ "enumMemberSet": "Le membre enum « {name} » ne peut pas être affecté", "enumMemberTypeAnnotation": "Les annotations de type ne sont pas autorisées pour les membres enum", "exceptionGroupIncompatible": "La syntaxe du groupe d’exceptions (« except* ») nécessite Python 3.11 ou version ultérieure", + "exceptionGroupTypeIncorrect": "Exception type in except* cannot derive from BaseGroupException", "exceptionTypeIncorrect": "\"{type}\" ne dérive pas de BaseException", "exceptionTypeNotClass": "« {type} » n’est pas une classe d’exception valide", "exceptionTypeNotInstantiable": "Le constructeur pour le type d’exception « {type} » requiert un ou plusieurs arguments", diff --git a/packages/pyright-internal/src/localization/package.nls.it.json b/packages/pyright-internal/src/localization/package.nls.it.json index 63f8576f3e..984b70cc2a 100644 --- a/packages/pyright-internal/src/localization/package.nls.it.json +++ b/packages/pyright-internal/src/localization/package.nls.it.json @@ -15,6 +15,7 @@ }, "Diagnostic": { "abstractMethodInvocation": "Impossibile chiamare il metodo \"{method}\" perché è astratto e non implementato", + "annotatedMetadataInconsistent": "Annotated metadata type \"{metadataType}\" is not compatible with type \"{type}\"", "annotatedParamCountMismatch": "Numero di annotazioni dei parametro non corrispondente: previsto {expected} ma ricevuto {received}", "annotatedTypeArgMissing": "Previsto un argomento di tipo e una o più annotazioni per \"Annotato\"", "annotationBytesString": "Le annotazioni di tipo non possono usare valori letterali stringa byte", @@ -158,6 +159,7 @@ "enumMemberSet": "Non è possibile assegnare il membro di enumerazione \"{name}\"", "enumMemberTypeAnnotation": "Le annotazioni di tipo non sono consentite per i membri di enumerazione", "exceptionGroupIncompatible": "La sintassi del gruppo di eccezioni (\"except*\") richiede Python 3.11 o versione successiva", + "exceptionGroupTypeIncorrect": "Exception type in except* cannot derive from BaseGroupException", "exceptionTypeIncorrect": "\"{type}\" non deriva da BaseException", "exceptionTypeNotClass": "\"{type}\" non è una classe di eccezione valida", "exceptionTypeNotInstantiable": "Il costruttore per il tipo di eccezione \"{type}\" richiede uno o più argomenti", diff --git a/packages/pyright-internal/src/localization/package.nls.ja.json b/packages/pyright-internal/src/localization/package.nls.ja.json index 5dd9097f54..643c08679f 100644 --- a/packages/pyright-internal/src/localization/package.nls.ja.json +++ b/packages/pyright-internal/src/localization/package.nls.ja.json @@ -15,6 +15,7 @@ }, "Diagnostic": { "abstractMethodInvocation": "メソッド \"{method}\" は抽象メソッドであり、実装されていないため、呼び出すことができません", + "annotatedMetadataInconsistent": "Annotated metadata type \"{metadataType}\" is not compatible with type \"{type}\"", "annotatedParamCountMismatch": "パラメーター注釈数の不一致: {expected} が必要ですが、{received} を受信しました", "annotatedTypeArgMissing": "\"Annotationed\" には 1 つの型引数と 1 つ以上の注釈が必要です", "annotationBytesString": "型注釈では、バイト文字列リテラルは使用できません", @@ -158,6 +159,7 @@ "enumMemberSet": "列挙型メンバー \"{name}\" を割り当てることはできません", "enumMemberTypeAnnotation": "列挙型メンバーには型注釈を使用できません", "exceptionGroupIncompatible": "例外グループの構文 (\"except*\") には Python 3.11 以降が必要です", + "exceptionGroupTypeIncorrect": "Exception type in except* cannot derive from BaseGroupException", "exceptionTypeIncorrect": "\"{type}\" は BaseException から派生していません", "exceptionTypeNotClass": "\"{type}\" は有効な例外クラスではありません", "exceptionTypeNotInstantiable": "例外の種類 \"{type}\" のコンストラクターには 1 つ以上の引数が必要です", diff --git a/packages/pyright-internal/src/localization/package.nls.ko.json b/packages/pyright-internal/src/localization/package.nls.ko.json index b5b9c35ddd..81ddbca512 100644 --- a/packages/pyright-internal/src/localization/package.nls.ko.json +++ b/packages/pyright-internal/src/localization/package.nls.ko.json @@ -15,6 +15,7 @@ }, "Diagnostic": { "abstractMethodInvocation": "메서드 \"{method}\"은(는) 추상적이고 구현되지 않았으므로 호출할 수 없습니다.", + "annotatedMetadataInconsistent": "Annotated metadata type \"{metadataType}\" is not compatible with type \"{type}\"", "annotatedParamCountMismatch": "매개 변수 주석 개수가 일치하지 않습니다. {expected}이)(가) 필요하지만 {received}을(를) 받았습니다.", "annotatedTypeArgMissing": "\"Annotated\"에 대해 하나의 형식 인수와 하나 이상의 주석이 필요합니다.", "annotationBytesString": "형식 주석은 바이트 문자열 리터럴을 사용할 수 없습니다.", @@ -158,6 +159,7 @@ "enumMemberSet": "열거형 멤버 \"{name}\"을(를) 할당할 수 없음", "enumMemberTypeAnnotation": "열거형 멤버에는 형식 주석을 사용할 수 없습니다.", "exceptionGroupIncompatible": "예외 그룹 구문(\"except*\")에는 Python 3.11 이상이 필요합니다.", + "exceptionGroupTypeIncorrect": "Exception type in except* cannot derive from BaseGroupException", "exceptionTypeIncorrect": "‘{type}’은 BaseException에서 파생되지 않습니다.", "exceptionTypeNotClass": "\"{type}\"은(는) 올바른 예외 클래스가 아닙니다.", "exceptionTypeNotInstantiable": "예외 형식 \"{type}\"에 대한 생성자에는 하나 이상의 인수가 필요합니다.", diff --git a/packages/pyright-internal/src/localization/package.nls.pl.json b/packages/pyright-internal/src/localization/package.nls.pl.json index 83fa48a44f..e2010dd594 100644 --- a/packages/pyright-internal/src/localization/package.nls.pl.json +++ b/packages/pyright-internal/src/localization/package.nls.pl.json @@ -15,6 +15,7 @@ }, "Diagnostic": { "abstractMethodInvocation": "Nie można wywołać metody „{method}”, ponieważ jest abstrakcyjna i niezaimplementowana", + "annotatedMetadataInconsistent": "Annotated metadata type \"{metadataType}\" is not compatible with type \"{type}\"", "annotatedParamCountMismatch": "Niezgodność liczby adnotacji parametru; oczekiwano {expected}, a uzyskano {received}", "annotatedTypeArgMissing": "Oczekiwano jednego argumentu typu i co najmniej jednej adnotacji dla wartości „Annotated”", "annotationBytesString": "Adnotacje typu nie mogą używać literałów ciągu bajtów", @@ -158,6 +159,7 @@ "enumMemberSet": "Nie można przypisać składowej wyliczenia „{name}”", "enumMemberTypeAnnotation": "Adnotacje typu nie są dozwolone dla elementów członkowskich wyliczenia", "exceptionGroupIncompatible": "Składnia grupy wyjątków („except*”) wymaga języka Python w wersji 3.11 lub nowszej", + "exceptionGroupTypeIncorrect": "Exception type in except* cannot derive from BaseGroupException", "exceptionTypeIncorrect": "Typ „{type}” nie pochodzi od parametru BaseException", "exceptionTypeNotClass": "Typ „{type}” nie jest prawidłową klasą wyjątku", "exceptionTypeNotInstantiable": "Konstruktor typu wyjątku „{type}” wymaga co najmniej jednego argumentu", diff --git a/packages/pyright-internal/src/localization/package.nls.pt-br.json b/packages/pyright-internal/src/localization/package.nls.pt-br.json index f2f395f5f2..6ac1af8ced 100644 --- a/packages/pyright-internal/src/localization/package.nls.pt-br.json +++ b/packages/pyright-internal/src/localization/package.nls.pt-br.json @@ -15,6 +15,7 @@ }, "Diagnostic": { "abstractMethodInvocation": "O método \"{method}\" não pode ser chamado porque é abstrato e não está implementado", + "annotatedMetadataInconsistent": "Annotated metadata type \"{metadataType}\" is not compatible with type \"{type}\"", "annotatedParamCountMismatch": "Incompatibilidade de contagem de anotações de parâmetro: esperado {expected}, mas recebido {received}", "annotatedTypeArgMissing": "Esperava-se um argumento de tipo e uma ou mais anotações para \"Annotated\"", "annotationBytesString": "Anotações de tipo não podem usar literais de cadeia de caracteres de bytes", @@ -158,6 +159,7 @@ "enumMemberSet": "O membro enumerado \"{name}\" não pode ser atribuído", "enumMemberTypeAnnotation": "Anotações de tipo não são permitidas para membros de enumeração", "exceptionGroupIncompatible": "A sintaxe do grupo de exceção (\"exceto*\") requer o Python 3.11 ou mais recente", + "exceptionGroupTypeIncorrect": "Exception type in except* cannot derive from BaseGroupException", "exceptionTypeIncorrect": "\"{type}\" não deriva de BaseException", "exceptionTypeNotClass": "\"{type}\" não é uma classe de exceção válida", "exceptionTypeNotInstantiable": "O construtor para o tipo de exceção \"{type}\" requer um ou mais argumentos", diff --git a/packages/pyright-internal/src/localization/package.nls.qps-ploc.json b/packages/pyright-internal/src/localization/package.nls.qps-ploc.json index 8028e03007..3a70a9cb28 100644 --- a/packages/pyright-internal/src/localization/package.nls.qps-ploc.json +++ b/packages/pyright-internal/src/localization/package.nls.qps-ploc.json @@ -15,6 +15,7 @@ }, "Diagnostic": { "abstractMethodInvocation": "[fE8MD][นั้Mëthøð \"{mëthøð}\" çæññøt þë çællëð þëçæµsë ït ïs æþstræçt æñð µñïmplëmëñtëðẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्तिृまẤğนั้ढूँ]", + "annotatedMetadataInconsistent": "[iOP70][นั้Æññøtætëð mëtæðætæ tÿpë \"{mëtæðætæTÿpë}\" ïs ñøt çømpætïþlë wïth tÿpë \"{tÿpë}\"Ấğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्तिृまẤğ倪นั้ढूँ]", "annotatedParamCountMismatch": "[VZvZc][นั้Pæræmëtër æññøtætïøñ çøµñt mïsmætçh: ëxpëçtëð {ëxpëçtëð} þµt rëçëïvëð {rëçëïvëð}Ấğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्तिृまẤğ倪İนั้ढूँ]", "annotatedTypeArgMissing": "[mTgtG][นั้Ëxpëçtëð øñë tÿpë ærgµmëñt æñð øñë ør mørë æññøtætïøñs før \"Æññøtætëð\"Ấğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्तिृまนั้ढूँ]", "annotationBytesString": "[W1g86][นั้Tÿpë æññøtætïøñs çæññøt µsë þÿtës strïñg lïtërælsẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰนั้ढूँ]", @@ -158,6 +159,7 @@ "enumMemberSet": "[mBLro][นั้Ëñµm mëmþër \"{ñæmë}\" çæññøt þë æssïgñëðẤğ倪İЂҰक्र्तिृまẤğนั้ढूँ]", "enumMemberTypeAnnotation": "[z8FaL][นั้Tÿpë æññøtætïøñs ærë ñøt ælløwëð før ëñµm mëmþërsẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰนั้ढूँ]", "exceptionGroupIncompatible": "[d0SLP][นั้Ëxçëptïøñ grøµp sÿñtæx (\"ëxçëpt*\") rëqµïrës Pÿthøñ 3.11 ør ñëwërẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्तिृนั้ढूँ]", + "exceptionGroupTypeIncorrect": "[Kanvz][นั้Ëxçëptïøñ tÿpë ïñ ëxçëpt* çæññøt ðërïvë frøm ßæsëGrøµpËxçëptïøñẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्तिृนั้ढूँ]", "exceptionTypeIncorrect": "[G7AZt][นั้\"{tÿpë}\" ðøës ñøt ðërïvë frøm ßæsëËxçëptïøñẤğ倪İЂҰक्र्तिृまẤğ倪นั้ढूँ]", "exceptionTypeNotClass": "[v1FmY][นั้\"{tÿpë}\" ïs ñøt æ vælïð ëxçëptïøñ çlæssẤğ倪İЂҰक्र्तिृまẤğนั้ढूँ]", "exceptionTypeNotInstantiable": "[PfdeG][นั้Çøñstrµçtør før ëxçëptïøñ tÿpë \"{tÿpë}\" rëqµïrës øñë ør mørë ærgµmëñtsẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्तिृまนั้ढूँ]", diff --git a/packages/pyright-internal/src/localization/package.nls.ru.json b/packages/pyright-internal/src/localization/package.nls.ru.json index af03369f26..1077b1a982 100644 --- a/packages/pyright-internal/src/localization/package.nls.ru.json +++ b/packages/pyright-internal/src/localization/package.nls.ru.json @@ -15,6 +15,7 @@ }, "Diagnostic": { "abstractMethodInvocation": "Невозможно вызвать метод \"{method}\", так как он является абстрактным и нереализованным", + "annotatedMetadataInconsistent": "Annotated metadata type \"{metadataType}\" is not compatible with type \"{type}\"", "annotatedParamCountMismatch": "Несоответствие числа аннотаций параметра: ожидается {expected}, но получено {received}", "annotatedTypeArgMissing": "Для \"Annotated\" ожидается один аргумент типа и одна или несколько заметок типа", "annotationBytesString": "Заметки с типом не могут использовать строковые литералы байтов", @@ -158,6 +159,7 @@ "enumMemberSet": "Не удается назначить элемент перечисления \"{name}\"", "enumMemberTypeAnnotation": "Аннотации типов не разрешены для элементов перечисления", "exceptionGroupIncompatible": "Синтаксис группы исключений (\"except*\") можно использовать в Python версии не ранее 3.11", + "exceptionGroupTypeIncorrect": "Exception type in except* cannot derive from BaseGroupException", "exceptionTypeIncorrect": "\"{type}\" не является производным от BaseException", "exceptionTypeNotClass": "\"{type}\" не является допустимым классом исключений", "exceptionTypeNotInstantiable": "Конструктору типа исключения \"{type}\" требуется один или несколько аргументов", diff --git a/packages/pyright-internal/src/localization/package.nls.tr.json b/packages/pyright-internal/src/localization/package.nls.tr.json index 1244bdb972..75a8917590 100644 --- a/packages/pyright-internal/src/localization/package.nls.tr.json +++ b/packages/pyright-internal/src/localization/package.nls.tr.json @@ -15,6 +15,7 @@ }, "Diagnostic": { "abstractMethodInvocation": "\"{method}\" metodu soyut veya uygulanmamış olduğundan çağrılamaz", + "annotatedMetadataInconsistent": "Annotated metadata type \"{metadataType}\" is not compatible with type \"{type}\"", "annotatedParamCountMismatch": "Parametre ek açıklama sayısı uyuşmazlığı: {expected} bekleniyordu ancak {received} alındı", "annotatedTypeArgMissing": "\"Annotated\" için bir tür bağımsız değişkeni ve bir veya daha fazla ek açıklama bekleniyordu", "annotationBytesString": "Tür ek açıklamaları bayt sabit değerli dizeleri kullanamaz", @@ -158,6 +159,7 @@ "enumMemberSet": "Sabit liste üyesi \"{name}\" atanamıyor", "enumMemberTypeAnnotation": "Sabit listesi üyeleri için tür ek açıklamalarına izin verilmiyor", "exceptionGroupIncompatible": "Özel durum grubu söz dizimi (\"except*\") için Python 3.11 veya daha yeni bir sürümü gerekiyor", + "exceptionGroupTypeIncorrect": "Exception type in except* cannot derive from BaseGroupException", "exceptionTypeIncorrect": "\"{type}\", BaseException türevi değil", "exceptionTypeNotClass": "\"{type}\" geçerli bir özel durum sınıfı değil", "exceptionTypeNotInstantiable": "\"{type}\" özel durum türü oluşturucusu bir veya daha fazla bağımsız değişken gerektiriyor", diff --git a/packages/pyright-internal/src/localization/package.nls.zh-cn.json b/packages/pyright-internal/src/localization/package.nls.zh-cn.json index 2879eb28b3..2160e975b6 100644 --- a/packages/pyright-internal/src/localization/package.nls.zh-cn.json +++ b/packages/pyright-internal/src/localization/package.nls.zh-cn.json @@ -15,6 +15,7 @@ }, "Diagnostic": { "abstractMethodInvocation": "无法调用方法“{method}”,因为它是抽象的且未实施", + "annotatedMetadataInconsistent": "Annotated metadata type \"{metadataType}\" is not compatible with type \"{type}\"", "annotatedParamCountMismatch": "参数批注计数不匹配: 应为 {expected},但收到 {received}", "annotatedTypeArgMissing": "“Annotated”应为一个类型参数和一个或多个批注", "annotationBytesString": "类型批注不能使用字节字符串文本", @@ -158,6 +159,7 @@ "enumMemberSet": "无法分配枚举成员“{name}”", "enumMemberTypeAnnotation": "枚举成员不允许使用类型注释", "exceptionGroupIncompatible": "异常组语法 (\"except*\") 需要 Python 3.11 或更高版本", + "exceptionGroupTypeIncorrect": "Exception type in except* cannot derive from BaseGroupException", "exceptionTypeIncorrect": "\"{type}\" 不是派生自 BaseException", "exceptionTypeNotClass": "“{type}”不是有效的异常类", "exceptionTypeNotInstantiable": "异常类型\"{type}\"的构造函数需要一个或多个参数", diff --git a/packages/pyright-internal/src/localization/package.nls.zh-tw.json b/packages/pyright-internal/src/localization/package.nls.zh-tw.json index 355f23988a..4dc52e172e 100644 --- a/packages/pyright-internal/src/localization/package.nls.zh-tw.json +++ b/packages/pyright-internal/src/localization/package.nls.zh-tw.json @@ -15,6 +15,7 @@ }, "Diagnostic": { "abstractMethodInvocation": "無法呼叫方法 \"{method}\",因為它是抽象且未執行", + "annotatedMetadataInconsistent": "Annotated metadata type \"{metadataType}\" is not compatible with type \"{type}\"", "annotatedParamCountMismatch": "參數註釋計數不符: 應為 {expected},但收到 {received}", "annotatedTypeArgMissing": "預期 \"Annotated\" 有一個類型引數和一或多個註釋", "annotationBytesString": "類型註釋無法使用位元組字串常值", @@ -158,6 +159,7 @@ "enumMemberSet": "無法指派列舉成員 \"{name}\"", "enumMemberTypeAnnotation": "列舉成員不允許類型註釋", "exceptionGroupIncompatible": "例外群組語法 (\"except*\") 需要 Python 3.11 或更新版本", + "exceptionGroupTypeIncorrect": "Exception type in except* cannot derive from BaseGroupException", "exceptionTypeIncorrect": "\"{type}\" 不是衍生自 BaseException", "exceptionTypeNotClass": "\"{type}\" 不是有效的例外類別", "exceptionTypeNotInstantiable": "例外類型 \"{type}\" 的建構函式需要一或多個引數", diff --git a/packages/pyright-internal/src/parser/parseNodes.ts b/packages/pyright-internal/src/parser/parseNodes.ts index 3faabec3c8..4e70f4a5c0 100644 --- a/packages/pyright-internal/src/parser/parseNodes.ts +++ b/packages/pyright-internal/src/parser/parseNodes.ts @@ -866,18 +866,22 @@ export namespace BinaryOperationNode { export interface AssignmentExpressionNode extends ParseNodeBase { readonly nodeType: ParseNodeType.AssignmentExpression; name: NameNode; + walrusToken: Token; rightExpression: ExpressionNode; + isParenthesized: boolean; } export namespace AssignmentExpressionNode { - export function create(name: NameNode, rightExpression: ExpressionNode) { + export function create(name: NameNode, walrusToken: Token, rightExpression: ExpressionNode) { const node: AssignmentExpressionNode = { start: name.start, length: name.length, nodeType: ParseNodeType.AssignmentExpression, id: _nextNodeId++, name, + walrusToken, rightExpression, + isParenthesized: false, }; name.parent = node; diff --git a/packages/pyright-internal/src/parser/parser.ts b/packages/pyright-internal/src/parser/parser.ts index 7e932c2933..9a32a9781e 100644 --- a/packages/pyright-internal/src/parser/parser.ts +++ b/packages/pyright-internal/src/parser/parser.ts @@ -3184,7 +3184,7 @@ export class Parser { const rightExpr = this._parseTestExpression(/* allowAssignmentExpression */ false); - return AssignmentExpressionNode.create(leftExpr, rightExpr); + return AssignmentExpressionNode.create(leftExpr, walrusToken, rightExpr); } // or_test: and_test ('or' and_test)* @@ -3910,11 +3910,11 @@ export class Parser { possibleTupleNode.parenthesized = true; } - if (possibleTupleNode.nodeType === ParseNodeType.StringList) { - possibleTupleNode.isParenthesized = true; - } - - if (possibleTupleNode.nodeType === ParseNodeType.Comprehension) { + if ( + possibleTupleNode.nodeType === ParseNodeType.StringList || + possibleTupleNode.nodeType === ParseNodeType.Comprehension || + possibleTupleNode.nodeType === ParseNodeType.AssignmentExpression + ) { possibleTupleNode.isParenthesized = true; } @@ -4140,10 +4140,23 @@ export class Parser { if (this._consumeTokenIfOperator(OperatorType.Power)) { doubleStarExpression = this._parseExpression(/* allowUnpack */ false); } else { - keyExpression = this._parseTestOrStarExpression(/* allowAssignmentExpression */ false); + keyExpression = this._parseTestOrStarExpression(/* allowAssignmentExpression */ true); + + // Allow walrus operators in this context only for Python 3.10 and newer. + // Older versions of Python generated a syntax error in this context. + let isWalrusAllowed = this._getLanguageVersion().isGreaterOrEqualTo(pythonVersion3_10); if (this._consumeTokenIfType(TokenType.Colon)) { valueExpression = this._parseTestExpression(/* allowAssignmentExpression */ false); + isWalrusAllowed = false; + } + + if ( + !isWalrusAllowed && + keyExpression.nodeType === ParseNodeType.AssignmentExpression && + !keyExpression.isParenthesized + ) { + this._addSyntaxError(LocMessage.walrusNotAllowed(), keyExpression.walrusToken); } } diff --git a/packages/pyright-internal/src/tests/checker.test.ts b/packages/pyright-internal/src/tests/checker.test.ts index 32bee1f677..10ccbd9853 100644 --- a/packages/pyright-internal/src/tests/checker.test.ts +++ b/packages/pyright-internal/src/tests/checker.test.ts @@ -167,7 +167,7 @@ test('With2', () => { test('With3', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['with3.py']); - TestUtils.validateResults(analysisResults, 5); + TestUtils.validateResults(analysisResults, 6); }); test('With4', () => { @@ -551,7 +551,7 @@ test('UninitializedVariable2', () => { // Enable it as an error. configOptions.diagnosticRuleSet.reportUninitializedInstanceVariable = 'error'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['uninitializedVariable2.py'], configOptions); - TestUtils.validateResults(analysisResults, 2); + TestUtils.validateResults(analysisResults, 3); }); test('Deprecated1', () => { diff --git a/packages/pyright-internal/src/tests/fourslash/hover.docstring.alias.fourslash.ts b/packages/pyright-internal/src/tests/fourslash/hover.docstring.alias.fourslash.ts index 11b405dcfd..a3ad98ea8a 100644 --- a/packages/pyright-internal/src/tests/fourslash/hover.docstring.alias.fourslash.ts +++ b/packages/pyright-internal/src/tests/fourslash/hover.docstring.alias.fourslash.ts @@ -32,9 +32,9 @@ //// helper.verifyHover('markdown', { - marker1: '```python\n(type alias) AliasA: type[ClassA]\n```\n---\nAliasA doc string\n\nClassA doc string', - marker2: '```python\n(type alias) AliasA: type[ClassA]\n```\n---\nAliasA doc string\n\nClassA doc string', - marker3: '```python\n(type alias) AliasB: type[ClassB]\n```\n---\nAliasB alone doc string', - marker4: '```python\n(type alias) AliasC: type[ClassC]\n```\n---\nAliasC doc string\n\nClassC doc string', - marker5: '```python\n(type alias) AliasD: type[ClassD]\n```\n---\nAliasD alone doc string', + marker1: '```python\n(type) AliasA = ClassA\n```\n---\nAliasA doc string\n\nClassA doc string', + marker2: '```python\n(type) AliasA = ClassA\n```\n---\nAliasA doc string\n\nClassA doc string', + marker3: '```python\n(type) AliasB = ClassB\n```\n---\nAliasB alone doc string', + marker4: '```python\n(type) AliasC = ClassC\n```\n---\nAliasC doc string\n\nClassC doc string', + marker5: '```python\n(type) AliasD = ClassD\n```\n---\nAliasD alone doc string', }); diff --git a/packages/pyright-internal/src/tests/fourslash/hover.init.fourslash.ts b/packages/pyright-internal/src/tests/fourslash/hover.init.fourslash.ts index 9be5ddbfab..f7c8af8a75 100644 --- a/packages/pyright-internal/src/tests/fourslash/hover.init.fourslash.ts +++ b/packages/pyright-internal/src/tests/fourslash/hover.init.fourslash.ts @@ -32,7 +32,7 @@ helper.verifyHover('markdown', { marker1: '```python\nclass C1(name: str = "hello")\n```\n---\n\\_\\_init\\_\\_ docs', - marker2: '```python\n(type alias) unionType: type[C1] | type[C2]\n```', + marker2: '```python\n(type) unionType = C1 | C2\n```', marker3: '```python\nclass G(value: int)\n```', marker4: '```python\nclass G(value: int)\n```', marker5: '```python\nclass C1(name: str = "hello")\n```\n---\n\\_\\_init\\_\\_ docs', diff --git a/packages/pyright-internal/src/tests/fourslash/hover.variable.docString.fourslash.ts b/packages/pyright-internal/src/tests/fourslash/hover.variable.docString.fourslash.ts index 07120d0855..4fb9964b14 100644 --- a/packages/pyright-internal/src/tests/fourslash/hover.variable.docString.fourslash.ts +++ b/packages/pyright-internal/src/tests/fourslash/hover.variable.docString.fourslash.ts @@ -44,7 +44,6 @@ helper.verifyHover('markdown', { marker2: '```python\n(variable) def func(float) -> float\n```\n---\nA given function', marker3: '```python\n(variable) y: Literal[2]\n```\n---\ntest y', marker4: '```python\n(variable) z: int\n```\n---\ntest z', - marker5: - "```python\n(type alias) SomeType: type[List[int | str]]\n```\n---\nHere's some documentation about SomeType", + marker5: "```python\n(type) SomeType = List[int | str]\n```\n---\nHere's some documentation about SomeType", marker6: '```python\n(variable) x: Literal[123670029844611072]\n```', }); diff --git a/packages/pyright-internal/src/tests/parseTreeUtils.test.ts b/packages/pyright-internal/src/tests/parseTreeUtils.test.ts index 58073e2587..3b3388f238 100644 --- a/packages/pyright-internal/src/tests/parseTreeUtils.test.ts +++ b/packages/pyright-internal/src/tests/parseTreeUtils.test.ts @@ -9,6 +9,7 @@ import assert from 'assert'; import { + findNodeByOffset, getDottedName, getDottedNameWithGivenNodeAsLastName, getFirstAncestorOrSelfOfKind, @@ -318,6 +319,104 @@ test('printExpression', () => { } }); +test('findNodeByOffset', () => { + const code = ` +//// class A: +//// def read(self): pass +//// +//// class B(A): +//// x1 = 1 +//// def r[|/*marker*/|] +//// + `; + + const state = parseAndGetTestState(code).state; + const range = state.getRangeByMarkerName('marker')!; + const sourceFile = state.program.getBoundSourceFile(range.marker!.fileUri)!; + + const node = findNodeByOffset(sourceFile.getParseResults()!.parserOutput.parseTree, range.pos); + assert.strictEqual(node?.nodeType, ParseNodeType.Name); + assert.strictEqual((node as NameNode).value, 'r'); +}); + +test('findNodeByOffset with binary search', () => { + const code = ` +//// class A: +//// def read(self): pass +//// +//// class B(A): +//// x1 = 1 +//// x2 = 2 +//// x3 = 3 +//// x4 = 4 +//// x5 = 5 +//// x6 = 6 +//// x7 = 7 +//// x8 = 8 +//// x9 = 9 +//// x10 = 10 +//// x11 = 11 +//// x12 = 12 +//// x13 = 13 +//// x14 = 14 +//// x15 = 15 +//// x16 = 16 +//// x17 = 17 +//// x18 = 18 +//// x19 = 19 +//// def r[|/*marker*/|] +//// + `; + + const state = parseAndGetTestState(code).state; + const range = state.getRangeByMarkerName('marker')!; + const sourceFile = state.program.getBoundSourceFile(range.marker!.fileUri)!; + + const node = findNodeByOffset(sourceFile.getParseResults()!.parserOutput.parseTree, range.pos); + assert.strictEqual(node?.nodeType, ParseNodeType.Name); + assert.strictEqual((node as NameNode).value, 'r'); +}); + +test('findNodeByOffset with binary search choose earliest match', () => { + const code = ` +//// class A: +//// def read(self): pass +//// +//// class B(A): +//// x1 = 1 +//// x2 = 2 +//// x3 = 3 +//// x4 = 4 +//// x5 = 5 +//// x6 = 6 +//// x7 = 7 +//// x8 = 8 +//// x9 = 9 +//// x10 = 10 +//// x11 = 11 +//// x12 = 12 +//// x13 = 13 +//// x14 = 14 +//// x15 = 15 +//// x16 = 16 +//// x17 = 17 +//// x18 = 18 +//// x19 = 19 +//// def r[|/*marker*/|] +//// x20 = 20 +//// x21 = 21 +//// + `; + + const state = parseAndGetTestState(code).state; + const range = state.getRangeByMarkerName('marker')!; + const sourceFile = state.program.getBoundSourceFile(range.marker!.fileUri)!; + + const node = findNodeByOffset(sourceFile.getParseResults()!.parserOutput.parseTree, range.pos); + assert.strictEqual(node?.nodeType, ParseNodeType.Name); + assert.strictEqual((node as NameNode).value, 'r'); +}); + function testNodeRange(state: TestState, markerName: string, type: ParseNodeType, includeTrailingBlankLines = false) { const range = state.getRangeByMarkerName(markerName)!; const sourceFile = state.program.getBoundSourceFile(range.marker!.fileUri)!; diff --git a/packages/pyright-internal/src/tests/samples/assignmentExpr7.py b/packages/pyright-internal/src/tests/samples/assignmentExpr7.py index 4f76ab2d0e..741963a3e2 100644 --- a/packages/pyright-internal/src/tests/samples/assignmentExpr7.py +++ b/packages/pyright-internal/src/tests/samples/assignmentExpr7.py @@ -1,5 +1,7 @@ -# This sample tests assignment expressions used within arguments +# This sample tests assignment expressions used within arguments. +from dataclasses import dataclass +from typing import Mapping import collections @@ -10,3 +12,12 @@ def method1(self, key): # This should generate an error because walrus operators # are not allowed with named arguments. b = list(iterable = keys := [k for k in sorted(self.data) if k >= key]) + + +@dataclass +class DC1: + x: str + + +def func1(mapping: Mapping[str, dict]): + return [DC1(temp := "x", **mapping[temp])] diff --git a/packages/pyright-internal/src/tests/samples/callbackProtocol11.py b/packages/pyright-internal/src/tests/samples/callbackProtocol11.py new file mode 100644 index 0000000000..0c6242024e --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/callbackProtocol11.py @@ -0,0 +1,21 @@ +# This sample tests the case where a callback protocol uses a function- +# scoped type variable. + +from typing import Generic, Protocol, TypeVar + +T = TypeVar("T") +T_co = TypeVar("T_co", covariant=True) +U_co = TypeVar("U_co", covariant=True) + + +class A(Generic[T_co, U_co]): ... + + +class BProto(Protocol): + def __call__(self) -> A[list[T], T]: ... + + +def func1() -> BProto: + def make_a() -> A[list[T], T]: ... + + return make_a diff --git a/packages/pyright-internal/src/tests/samples/constrainedTypeVar13.py b/packages/pyright-internal/src/tests/samples/constrainedTypeVar13.py index 18f64591f7..e7362b9e33 100644 --- a/packages/pyright-internal/src/tests/samples/constrainedTypeVar13.py +++ b/packages/pyright-internal/src/tests/samples/constrainedTypeVar13.py @@ -49,21 +49,21 @@ def meth1( # This should generate an error. return [0] - if cond: + if cond or 3 > 2: if isinstance(val1, str): # This should generate an error. return [0] else: return [0] - if cond: + if cond or 3 > 2: if isinstance(val3, B): return [B()] else: # This should generate an error. return [C()] - if cond: + if cond or 3 > 2: if not isinstance(val3, B) and not isinstance(val3, C): return [A()] diff --git a/packages/pyright-internal/src/tests/samples/constructor30.py b/packages/pyright-internal/src/tests/samples/constructor30.py new file mode 100644 index 0000000000..4ede66b658 --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/constructor30.py @@ -0,0 +1,31 @@ +# This sample tests the case where a class is parameterized by a ParamSpec +# which is inferred by a call to the constructor, and the passed value +# is a generic function whose types are informed by additional parameters +# also passed to the constructor. + +from typing import Callable, Generic, ParamSpec, TypeVar + +P = ParamSpec("P") +T = TypeVar("T") + + +class ABase: ... + + +class A(ABase): ... + + +TA = TypeVar("TA", bound=ABase) + + +class B(Generic[P, T]): + def __init__( + self, _type: Callable[P, T], *args: P.args, **kwargs: P.kwargs + ) -> None: ... + + +def func1(t: type[TA]) -> TA: ... + + +b = B(func1, A) +reveal_type(b, expected_text="B[(t: type[A]), A]") diff --git a/packages/pyright-internal/src/tests/samples/matchSequence1.py b/packages/pyright-internal/src/tests/samples/matchSequence1.py index 04c7c89b84..0c99ca2266 100644 --- a/packages/pyright-internal/src/tests/samples/matchSequence1.py +++ b/packages/pyright-internal/src/tests/samples/matchSequence1.py @@ -2,8 +2,22 @@ # described in PEP 634) that contain sequence patterns. from enum import Enum -from typing import Any, Generic, Iterator, List, Literal, Protocol, Reversible, Sequence, Tuple, TypeVar, Union -from typing_extensions import Unpack # pyright: ignore[reportMissingModuleSource] +from typing import ( + Any, + Generic, + Iterator, + List, + Literal, + Protocol, + Reversible, + Sequence, + Tuple, + TypeVar, + Union, +) + +from typing_extensions import Unpack # pyright: ignore[reportMissingModuleSource] + def test_unknown(value_to_match): match value_to_match: @@ -25,13 +39,13 @@ def test_unknown(value_to_match): reveal_type(d1, expected_text="Unknown") reveal_type(d2, expected_text="list[Unknown]") reveal_type(d3, expected_text="Unknown") - + case 3, *e1: reveal_type(e1, expected_text="list[Unknown]") - + case "hi", *f1: reveal_type(f1, expected_text="list[Unknown]") - + case *g1, "hi": reveal_type(g1, expected_text="list[Unknown]") @@ -56,9 +70,11 @@ def test_reversible(value_to_match: Reversible[int]): _T_co = TypeVar("_T_co", covariant=True) + class SeqProto(Protocol[_T_co]): def __reversed__(self) -> Iterator[_T_co]: ... + def test_protocol(value_to_match: SeqProto[str]): match value_to_match: case [*a1]: @@ -67,7 +83,6 @@ def test_protocol(value_to_match: SeqProto[str]): reveal_type(b1, expected_text="SeqProto[str]") - def test_list(value_to_match: List[str]): match value_to_match: case a1, a2: @@ -90,19 +105,20 @@ def test_list(value_to_match: List[str]): reveal_type(d2, expected_text="list[str]") reveal_type(d3, expected_text="str") reveal_type(value_to_match, expected_text="List[str]") - + case 3, *e1: reveal_type(e1, expected_text="Never") reveal_type(value_to_match, expected_text="Never") - + case "hi", *f1: reveal_type(f1, expected_text="list[str]") reveal_type(value_to_match, expected_text="List[str]") - + case *g1, "hi": reveal_type(g1, expected_text="list[str]") reveal_type(value_to_match, expected_text="List[str]") + def test_open_ended_tuple(value_to_match: Tuple[str, ...]): match value_to_match: case a1, a2: @@ -125,19 +141,20 @@ def test_open_ended_tuple(value_to_match: Tuple[str, ...]): reveal_type(d2, expected_text="list[str]") reveal_type(d3, expected_text="str") reveal_type(value_to_match, expected_text="Tuple[str, ...]") - + case 3, *e1: reveal_type(e1, expected_text="Never") reveal_type(value_to_match, expected_text="Never") - + case "hi", *f1: reveal_type(f1, expected_text="list[str]") reveal_type(value_to_match, expected_text="Tuple[str, ...]") - + case *g1, "hi": reveal_type(g1, expected_text="list[str]") reveal_type(value_to_match, expected_text="Tuple[str, ...]") + def test_definite_tuple(value_to_match: Tuple[int, str, float, complex]): match value_to_match: case a1, a2, a3, a4 if value_to_match[0] == 0: @@ -162,11 +179,11 @@ def test_definite_tuple(value_to_match: Tuple[int, str, float, complex]): reveal_type(d2, expected_text="list[str | float]") reveal_type(d3, expected_text="complex") reveal_type(value_to_match, expected_text="Tuple[int, str, float, complex]") - + case 3, *e1: reveal_type(e1, expected_text="list[str | float | complex]") reveal_type(value_to_match, expected_text="Tuple[int, str, float, complex]") - + case "hi", *f1: reveal_type(f1, expected_text="Never") reveal_type(value_to_match, expected_text="Never") @@ -174,49 +191,86 @@ def test_definite_tuple(value_to_match: Tuple[int, str, float, complex]): case *g1, 3j: reveal_type(g1, expected_text="list[int | str | float]") reveal_type(value_to_match, expected_text="Tuple[int, str, float, complex]") - + case *h1, "hi": reveal_type(h1, expected_text="Never") reveal_type(value_to_match, expected_text="Never") -def test_union(value_to_match: Union[Tuple[complex, complex], Tuple[int, str, float, complex], List[str], Tuple[float, ...], Any]): +def test_union( + value_to_match: Union[ + Tuple[complex, complex], + Tuple[int, str, float, complex], + List[str], + Tuple[float, ...], + Any, + ], +): match value_to_match: case a1, a2, a3, a4 if value_to_match[0] == 0: reveal_type(a1, expected_text="int | str | float | Any") reveal_type(a2, expected_text="str | float | Any") reveal_type(a3, expected_text="float | str | Any") reveal_type(a4, expected_text="complex | str | float | Any") - reveal_type(value_to_match, expected_text="tuple[int, str, float, complex] | List[str] | tuple[float, float, float, float] | Sequence[Any]") + reveal_type( + value_to_match, + expected_text="tuple[int, str, float, complex] | List[str] | tuple[float, float, float, float] | Sequence[Any]", + ) case *b1, b2 if value_to_match[0] == 0: - reveal_type(b1, expected_text="list[complex] | list[int | str | float] | list[str] | list[float] | list[Any]") + reveal_type( + b1, + expected_text="list[complex] | list[int | str | float] | list[str] | list[float] | list[Any]", + ) reveal_type(b2, expected_text="complex | str | float | Any") - reveal_type(value_to_match, expected_text="Tuple[complex, complex] | Tuple[int, str, float, complex] | List[str] | Tuple[float, ...] | Sequence[Any]") + reveal_type( + value_to_match, + expected_text="Tuple[complex, complex] | Tuple[int, str, float, complex] | List[str] | Tuple[float, ...] | Sequence[Any]", + ) case c1, *c2 if value_to_match[0] == 0: reveal_type(c1, expected_text="complex | int | str | float | Any") - reveal_type(c2, expected_text="list[complex] | list[str | float | complex] | list[str] | list[float] | list[Any]") - reveal_type(value_to_match, expected_text="Tuple[complex, complex] | Tuple[int, str, float, complex] | List[str] | Tuple[float, ...] | Sequence[Any]") + reveal_type( + c2, + expected_text="list[complex] | list[str | float | complex] | list[str] | list[float] | list[Any]", + ) + reveal_type( + value_to_match, + expected_text="Tuple[complex, complex] | Tuple[int, str, float, complex] | List[str] | Tuple[float, ...] | Sequence[Any]", + ) case d1, *d2, d3 if value_to_match[0] == 0: reveal_type(d1, expected_text="complex | int | str | float | Any") - reveal_type(d2, expected_text="list[Any] | list[str | float] | list[str] | list[float]") + reveal_type( + d2, + expected_text="list[Any] | list[str | float] | list[str] | list[float]", + ) reveal_type(d3, expected_text="complex | str | float | Any") - reveal_type(value_to_match, expected_text="Tuple[complex, complex] | Tuple[int, str, float, complex] | List[str] | Tuple[float, ...] | Sequence[Any]") - + reveal_type( + value_to_match, + expected_text="Tuple[complex, complex] | Tuple[int, str, float, complex] | List[str] | Tuple[float, ...] | Sequence[Any]", + ) + case 3, e1: reveal_type(e1, expected_text="complex | float | Any") - reveal_type(value_to_match, expected_text="tuple[Literal[3], complex] | tuple[Literal[3], float] | Sequence[Any]") - + reveal_type( + value_to_match, + expected_text="tuple[Literal[3], complex] | tuple[Literal[3], float] | Sequence[Any]", + ) + case "hi", *f1: reveal_type(f1, expected_text="list[str] | list[Any]") reveal_type(value_to_match, expected_text="List[str] | Sequence[Any]") - + case *g1, 3j: - reveal_type(g1, expected_text="list[complex] | list[int | str | float] | list[Any]") - reveal_type(value_to_match, expected_text="tuple[complex, complex] | Tuple[int, str, float, complex] | Sequence[Any]") - + reveal_type( + g1, expected_text="list[complex] | list[int | str | float] | list[Any]" + ) + reveal_type( + value_to_match, + expected_text="tuple[complex, complex] | Tuple[int, str, float, complex] | Sequence[Any]", + ) + case *h1, "hi": reveal_type(h1, expected_text="list[str] | list[Any]") reveal_type(value_to_match, expected_text="List[str] | Sequence[Any]") @@ -226,6 +280,7 @@ class SupportsLessThan(Protocol): def __lt__(self, __other: Any) -> bool: ... def __le__(self, __other: Any) -> bool: ... + SupportsLessThanT = TypeVar("SupportsLessThanT", bound=SupportsLessThan) @@ -234,23 +289,23 @@ def sort(seq: List[SupportsLessThanT]) -> List[SupportsLessThanT]: case [] | [_]: reveal_type(seq, expected_text="List[SupportsLessThanT@sort]") return seq - + case [x, y] if x <= y: reveal_type(seq, expected_text="List[SupportsLessThanT@sort]") return seq - + case [x, y]: reveal_type(seq, expected_text="List[SupportsLessThanT@sort]") return [y, x] - + case [x, y, z] if x <= y <= z: reveal_type(seq, expected_text="List[SupportsLessThanT@sort]") return seq - + case [x, y, z] if x > y > z: reveal_type(seq, expected_text="List[SupportsLessThanT@sort]") return [z, y, x] - + case [p, *rest]: a = sort([x for x in rest if x <= p]) b = sort([x for x in rest if p < x]) @@ -266,6 +321,7 @@ def test_exceptions(seq: Union[str, bytes, bytearray]): reveal_type(y, expected_text="Never") return seq + def test_object1(seq: object): match seq: case (a1, a2) as a3: @@ -292,31 +348,32 @@ def test_object1(seq: object): reveal_type(d3, expected_text="Unknown") reveal_type(d4, expected_text="Sequence[Unknown]") reveal_type(seq, expected_text="Sequence[Unknown]") - + case (3, *e1) as e2: reveal_type(e1, expected_text="list[Unknown]") reveal_type(e2, expected_text="Sequence[Unknown]") reveal_type(seq, expected_text="Sequence[Unknown]") - - case ("hi", *f1) as f2: + + case ("hi", *f1) as f2: reveal_type(f1, expected_text="list[Unknown]") reveal_type(f2, expected_text="Sequence[Unknown]") - reveal_type(seq, expected_text="Sequence[Unknown]") - + reveal_type(seq, expected_text="Sequence[Unknown]") + case (*g1, "hi") as g2: reveal_type(g1, expected_text="list[Unknown]") - reveal_type(g2, expected_text="Sequence[Unknown]") - reveal_type(seq, expected_text="Sequence[Unknown]") + reveal_type(g2, expected_text="Sequence[Unknown]") + reveal_type(seq, expected_text="Sequence[Unknown]") - case [1, "hi", True] as h1: + case [1, "hi", True] as h1: reveal_type(h1, expected_text="Sequence[int | str | bool]") reveal_type(seq, expected_text="Sequence[int | str | bool]") case [1, i1] as i2: reveal_type(i1, expected_text="Unknown") - reveal_type(i2, expected_text="Sequence[Unknown]") + reveal_type(i2, expected_text="Sequence[Unknown]") reveal_type(seq, expected_text="Sequence[Unknown]") + def test_object2(value_to_match: object): match value_to_match: case [*a1]: @@ -333,21 +390,26 @@ def test_sequence(value_to_match: Sequence[Any]): reveal_type(b1, expected_text="Never") +_T = TypeVar("_T") -_T = TypeVar('_T') class A(Generic[_T]): a: _T + class B: ... + + class C: ... + AAlias = A AInt = A[int] BOrC = B | C + def test_illegal_type_alias(m: object): match m: case AAlias(a=i): @@ -363,9 +425,10 @@ def test_illegal_type_alias(m: object): case BOrC(a=i): pass + def test_negative_narrowing1(subj: tuple[Literal[0]] | tuple[Literal[1]]): match subj: - case (1,*a) | (*a): + case (1, *a) | (*a): reveal_type(subj, expected_text="tuple[Literal[1]] | tuple[Literal[0]]") reveal_type(a, expected_text="list[Any] | list[int]") @@ -376,7 +439,7 @@ def test_negative_narrowing1(subj: tuple[Literal[0]] | tuple[Literal[1]]): def test_negative_narrowing2(subj: tuple[int, ...]): match subj: - case (1,*a): + case (1, *a): reveal_type(subj, expected_text="tuple[int, ...]") reveal_type(a, expected_text="list[int]") @@ -448,7 +511,7 @@ class MyEnum(Enum): def test_tuple_with_subpattern( subj: Literal[MyEnum.A] | tuple[Literal[MyEnum.B], int] - | tuple[Literal[MyEnum.C], str] + | tuple[Literal[MyEnum.C], str], ): match subj: case MyEnum.A: @@ -462,7 +525,7 @@ def test_tuple_with_subpattern( def test_unbounded_tuple( - subj: tuple[int] | tuple[str, str] | tuple[int, Unpack[tuple[str, ...]], complex] + subj: tuple[int] | tuple[str, str] | tuple[int, Unpack[tuple[str, ...]], complex], ): match subj: case (x,): @@ -489,9 +552,20 @@ def test_unbounded_tuple_2(subj: tuple[int, str, Unpack[tuple[range, ...]]]) -> case [1, "", *ts2]: reveal_type(ts2, expected_text="list[range]") + def test_unbounded_tuple_3(subj: tuple[int, ...]): match subj: case []: return case x: reveal_type(x, expected_text="tuple[int, ...]") + + +def test_unbounded_tuple_4(subj: tuple[str, ...]): + match subj: + case x, "": + reveal_type(subj, expected_text="tuple[str, Literal['']]") + case (x,): + reveal_type(subj, expected_text="tuple[str]") + case x: + reveal_type(subj, expected_text="tuple[str, ...]") diff --git a/packages/pyright-internal/src/tests/samples/methodOverride3.py b/packages/pyright-internal/src/tests/samples/methodOverride3.py index 64d7687897..9c40919b46 100644 --- a/packages/pyright-internal/src/tests/samples/methodOverride3.py +++ b/packages/pyright-internal/src/tests/samples/methodOverride3.py @@ -7,96 +7,78 @@ class A1: - def func1(self, a: int) -> str: - ... + def func1(self, a: int) -> str: ... class A2: - def func1(self, a: int, b: int = 3) -> str: - ... + def func1(self, a: int, b: int = 3) -> str: ... # This should generate an error because func1 is incompatible. -class ASub(A1, A2): - ... +class ASub(A1, A2): ... class B1: - def func1(self) -> int: - ... + def func1(self) -> int: ... class B2: - def func1(self) -> float: - ... + def func1(self) -> float: ... -class BSub(B1, B2): - ... +class BSub(B1, B2): ... class C1: - def func1(self) -> float: - ... + def func1(self) -> float: ... class C2: - def func1(self) -> int: - ... + def func1(self) -> int: ... # This should generate an error because func1 is incompatible. -class CSub(C1, C2): - ... +class CSub(C1, C2): ... class D1: - def func1(self, a: int) -> None: - ... + def func1(self, a: int) -> None: ... class D2: - def func1(self, b: int) -> None: - ... + def func1(self, b: int) -> None: ... # This should generate an error because func1 is incompatible. -class DSub(D1, D2): - ... +class DSub(D1, D2): ... _T_E = TypeVar("_T_E") class E1(Generic[_T_E]): - def func1(self, a: _T_E) -> None: - ... + def func1(self, a: _T_E) -> None: ... class E2(Generic[_T_E]): - def func1(self, a: _T_E) -> None: - ... + def func1(self, a: _T_E) -> None: ... -class ESub(E1[int], E2[int]): - ... +class ESub(E1[int], E2[int]): ... _T_F = TypeVar("_T_F") class F1(Generic[_T_F]): - def do_stuff(self) -> Iterable[_T_F]: - ... + def do_stuff(self) -> Iterable[_T_F]: ... class F2(F1[_T_F]): - def do_stuff(self) -> Iterable[_T_F]: - ... + def do_stuff(self) -> Iterable[_T_F]: ... -class F3(F1[_T_F]): - ... +class F3(F1[_T_F]): ... class FSub1(F3[int], F2[int]): @@ -116,43 +98,77 @@ class FSub3(F2[int], F1[int]): class G1(Generic[_P, _R]): - def f(self, *args: _P.args, **kwargs: _P.kwargs) -> _R: - ... + def f(self, *args: _P.args, **kwargs: _P.kwargs) -> _R: ... - def g(self) -> _R: - ... + def g(self) -> _R: ... class G2(G1[_P, _R]): # This should generate an error because f is missing ParamSpec parameters. - def f(self) -> _R: - ... + def f(self) -> _R: ... - def g(self, *args: _P.args, **kwargs: _P.kwargs) -> _R: - ... + def g(self, *args: _P.args, **kwargs: _P.kwargs) -> _R: ... class G3(G1[[], _R]): - def f(self) -> _R: - ... + def f(self) -> _R: ... - def g(self) -> _R: - ... + def g(self) -> _R: ... class G4(G1[[int, int], str]): - def f(self, a: int, b: int, /) -> str: - ... + def f(self, a: int, b: int, /) -> str: ... - def g(self) -> str: - ... + def g(self) -> str: ... class G5(G1[[], str]): # This should generate an error because the specialized # signature of f in the base class has no positional parameters. - def f(self, a: int, b: int) -> str: - ... + def f(self, a: int, b: int) -> str: ... - def g(self) -> str: - ... + def g(self) -> str: ... + + +class H1: + @property + def prop1(self) -> int: + return 3 + + @property + def prop2(self) -> int: + return 3 + + @prop2.setter + def prop2(self, val: int) -> None: + pass + + @property + def prop3(self) -> int: + return 3 + + @prop3.setter + def prop3(self, val: int) -> None: + pass + + +class H2: + @property + def prop1(self) -> str: + return "" + + @property + def prop2(self) -> int: + return 3 + + @property + def prop3(self) -> int: + return 3 + + @prop3.setter + def prop3(self, val: str) -> None: + pass + + +# This should generate three errors: prop1, prop2 and prop3. +class H(H2, H1): ... diff --git a/packages/pyright-internal/src/tests/samples/overload7.py b/packages/pyright-internal/src/tests/samples/overload7.py index 20dce82c43..b9c7ba9674 100644 --- a/packages/pyright-internal/src/tests/samples/overload7.py +++ b/packages/pyright-internal/src/tests/samples/overload7.py @@ -254,7 +254,7 @@ def func14(target: Callable[..., _T14]) -> Wrapper1[_T14]: ... def func14( - target: Callable[..., Awaitable[_T14]] | Callable[..., _T14] + target: Callable[..., Awaitable[_T14]] | Callable[..., _T14], ) -> Wrapper1[_T14]: ... @@ -361,3 +361,10 @@ def func20(*args: int) -> int: ... def func20(*args: int, **kwargs: int) -> int: ... + + +@overload +def func21(x: tuple[()], /) -> None: ... +@overload +def func21(x: tuple[object], /) -> None: ... +def func21(x: tuple[object, ...], /) -> None: ... diff --git a/packages/pyright-internal/src/tests/samples/tryExcept4.py b/packages/pyright-internal/src/tests/samples/tryExcept4.py index 74cba7b332..6a0cbdab32 100644 --- a/packages/pyright-internal/src/tests/samples/tryExcept4.py +++ b/packages/pyright-internal/src/tests/samples/tryExcept4.py @@ -1,7 +1,10 @@ # This sample validates that the exception type provided # within a raise statement is valid. -a: bool = True +from random import random + + +a: bool = True if random() > 0.5 else False class CustomException1(BaseException): @@ -11,10 +14,10 @@ def __init__(self, code: int): # This should generate an error because CustomException1 # requires an argument to instantiate. -if a: +if a or 2 > 1: raise CustomException1 -if a: +if a or 2 > 1: raise CustomException1(3) @@ -24,5 +27,5 @@ class CustomException2: # This should generate an error because # the exception doesn't derive from BaseException. -if a: +if a or 2 > 1: raise CustomException2 diff --git a/packages/pyright-internal/src/tests/samples/uninitializedVariable2.py b/packages/pyright-internal/src/tests/samples/uninitializedVariable2.py index 31010a7f58..a2214e9594 100644 --- a/packages/pyright-internal/src/tests/samples/uninitializedVariable2.py +++ b/packages/pyright-internal/src/tests/samples/uninitializedVariable2.py @@ -2,8 +2,9 @@ # to a concrete implementation of an abstract base class that defines # (but does not assign) variables. -from abc import ABC -from typing import final +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from typing import NamedTuple, final class Abstract1(ABC): @@ -48,3 +49,20 @@ class Abstract3(Abstract1): @final class G(Abstract3): pass + + +class H(NamedTuple): + x: int + + +@dataclass +class IAbstract(ABC): + p1: str + p2: int = field(init=False) + + +@final +@dataclass +# This should generate an error because p2 is uninitialized. +class I(IAbstract): + p3: int diff --git a/packages/pyright-internal/src/tests/samples/uninitializedVariableBased.py b/packages/pyright-internal/src/tests/samples/uninitializedVariableBased.py deleted file mode 100644 index b347bb6207..0000000000 --- a/packages/pyright-internal/src/tests/samples/uninitializedVariableBased.py +++ /dev/null @@ -1,7 +0,0 @@ -from typing import NamedTuple - -class Foo(NamedTuple): - a: int # no error - -class Bar: - b: str # error diff --git a/packages/pyright-internal/src/tests/samples/unreachable1.py b/packages/pyright-internal/src/tests/samples/unreachable1.py index d3a38067a0..d67513bec0 100644 --- a/packages/pyright-internal/src/tests/samples/unreachable1.py +++ b/packages/pyright-internal/src/tests/samples/unreachable1.py @@ -1,8 +1,8 @@ # This sample tests the detection and reporting of unreachable code. -from abc import abstractmethod import os import sys +from abc import abstractmethod from typing import NoReturn @@ -109,3 +109,19 @@ def func10(): return # This should be marked unreachable. b = e.errno + + +def func11(obj: str) -> list: + if isinstance(obj, str): + return [] + else: + # This should be marked as unreachable. + return obj + + +def func12(obj: str) -> list: + if isinstance(obj, str): + return [] + + # This should be marked as unreachable. + return obj diff --git a/packages/pyright-internal/src/tests/samples/unreachableAssertNever.py b/packages/pyright-internal/src/tests/samples/unreachableAssertNever.py new file mode 100644 index 0000000000..7eb8afb620 --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/unreachableAssertNever.py @@ -0,0 +1,18 @@ +from typing import Literal, assert_never + +def foo(value: Literal[1]): + if value == 1: + ... + else: + assert_never(value) # not unreachable + ... # unreachable +def bar(value: Literal[1]): + if value == 1: + ... + else: + assert_never('value') # unreachable (argument type is wrong) +def baz(value: Literal[1]): + if value == 1: + ... + else: + print(value) # unreachable (function argument type is not Never) \ No newline at end of file diff --git a/packages/pyright-internal/src/tests/samples/with3.py b/packages/pyright-internal/src/tests/samples/with3.py index 60a6fa535c..ff466089a1 100644 --- a/packages/pyright-internal/src/tests/samples/with3.py +++ b/packages/pyright-internal/src/tests/samples/with3.py @@ -17,8 +17,7 @@ class A: raise RuntimeError() return - # This should generate an error because - # the code is not unreachable. + # This should generate an error. c = "hi" + 3 with memoryview(x): diff --git a/packages/pyright-internal/src/tests/typeEvaluator1.test.ts b/packages/pyright-internal/src/tests/typeEvaluator1.test.ts index 9f6332bc5a..38b3bee7ff 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator1.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator1.test.ts @@ -26,7 +26,7 @@ import * as TestUtils from './testUtils'; test('Unreachable1', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['unreachable1.py']); - TestUtils.validateResults(analysisResults, 0, 0, 2, 1, 4); + TestUtils.validateResults(analysisResults, 0, 0, 2, 1, 6); }); test('Builtins1', () => { diff --git a/packages/pyright-internal/src/tests/typeEvaluator2.test.ts b/packages/pyright-internal/src/tests/typeEvaluator2.test.ts index b3e8548137..e1d043ed6a 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator2.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator2.test.ts @@ -74,6 +74,12 @@ test('CallbackProtocol10', () => { TestUtils.validateResults(analysisResults, 0); }); +test('CallbackProtocol11', () => { + const analysisResults = TestUtils.typeAnalyzeSampleFiles(['callbackProtocol11.py']); + + TestUtils.validateResults(analysisResults, 0); +}); + test('Assignment1', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['assignment1.py']); diff --git a/packages/pyright-internal/src/tests/typeEvaluator3.test.ts b/packages/pyright-internal/src/tests/typeEvaluator3.test.ts index 8d758e872d..d073010949 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator3.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator3.test.ts @@ -910,7 +910,7 @@ test('MethodOverride3', () => { configOptions.diagnosticRuleSet.reportIncompatibleMethodOverride = 'error'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['methodOverride3.py'], configOptions); - TestUtils.validateResults(analysisResults, 5); + TestUtils.validateResults(analysisResults, 8); }); test('MethodOverride4', () => { diff --git a/packages/pyright-internal/src/tests/typeEvaluator6.test.ts b/packages/pyright-internal/src/tests/typeEvaluator6.test.ts index 0a9fd47d7c..c8ba668e66 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator6.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator6.test.ts @@ -773,6 +773,12 @@ test('Constructor29', () => { TestUtils.validateResults(analysisResults, 0); }); +test('Constructor30', () => { + const analysisResults = TestUtils.typeAnalyzeSampleFiles(['constructor30.py']); + + TestUtils.validateResults(analysisResults, 0); +}); + test('ConstructorCallable1', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['constructorCallable1.py']); diff --git a/packages/pyright-internal/src/tests/typeEvaluatorBased.test.ts b/packages/pyright-internal/src/tests/typeEvaluatorBased.test.ts index aed2b3b856..a13d1f91b0 100644 --- a/packages/pyright-internal/src/tests/typeEvaluatorBased.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluatorBased.test.ts @@ -10,7 +10,7 @@ test('reportUnreachable', () => { configOptions.diagnosticRuleSet.reportUnreachable = 'error'; const analysisResults = typeAnalyzeSampleFiles(['unreachable1.py'], configOptions); validateResultsButBased(analysisResults, { - errors: [78, 89, 106, 110].map((line) => ({ code: DiagnosticRule.reportUnreachable, line })), + errors: [78, 89, 106, 110, 118, 126].map((line) => ({ code: DiagnosticRule.reportUnreachable, line })), infos: [{ line: 95 }, { line: 98 }], unusedCodes: [{ line: 102 }], }); @@ -25,6 +25,21 @@ test('reportUnreachable TYPE_CHECKING', () => { validateResultsButBased(analysisResults, {}); }); +test('unreachable assert_never', () => { + const configOptions = new ConfigOptions(Uri.empty()); + configOptions.diagnosticRuleSet.reportUnreachable = 'error'; + const analysisResults = typeAnalyzeSampleFiles(['unreachableAssertNever.py'], configOptions); + + validateResultsButBased(analysisResults, { + errors: [ + { code: DiagnosticRule.reportUnreachable, line: 7 }, + { code: DiagnosticRule.reportUnreachable, line: 12 }, + { code: DiagnosticRule.reportArgumentType, line: 12 }, + { code: DiagnosticRule.reportUnreachable, line: 17 }, + ], + }); +}); + test('default typeCheckingMode=all', () => { // there's a better test for this in `config.test.ts` which tests it in a more complete way. // the logic for loading the config seems very convoluted and messy. the default typeCheckingMode @@ -36,12 +51,20 @@ test('default typeCheckingMode=all', () => { const analysisResults = typeAnalyzeSampleFiles(['unreachable1.py'], configOptions); validateResultsButBased(analysisResults, { errors: [ - ...[78, 89, 106, 110].map((line) => ({ code: DiagnosticRule.reportUnreachable, line })), + ...[78, 89, 106, 110, 118, 126].map((line) => ({ code: DiagnosticRule.reportUnreachable, line })), { line: 16, code: DiagnosticRule.reportUninitializedInstanceVariable }, { line: 19, code: DiagnosticRule.reportUnknownParameterType }, { line: 33, code: DiagnosticRule.reportUnknownParameterType }, { line: 94, code: DiagnosticRule.reportUnnecessaryComparison }, { line: 102, code: DiagnosticRule.reportUnusedVariable }, + { line: 113, code: DiagnosticRule.reportUnknownParameterType }, + { line: 113, code: DiagnosticRule.reportMissingTypeArgument }, + { line: 114, code: DiagnosticRule.reportUnnecessaryIsInstance }, + { line: 115, code: DiagnosticRule.reportUnknownVariableType }, + { line: 121, code: DiagnosticRule.reportUnknownParameterType }, + { line: 121, code: DiagnosticRule.reportMissingTypeArgument }, + { line: 122, code: DiagnosticRule.reportUnnecessaryIsInstance }, + { line: 123, code: DiagnosticRule.reportUnknownVariableType }, ], infos: [{ line: 95 }, { line: 98 }], }); @@ -94,12 +117,3 @@ test('subscript context manager types on 3.8', () => { ], }); }); - -test('no reportUninitializedInstanceVariable on NamedTuple', () => { - const configOptions = new ConfigOptions(Uri.empty()); - configOptions.diagnosticRuleSet.reportUninitializedInstanceVariable = 'error'; - const analysisResults = typeAnalyzeSampleFiles(['uninitializedVariableBased.py'], configOptions); - validateResultsButBased(analysisResults, { - errors: [{ code: DiagnosticRule.reportUninitializedInstanceVariable, line: 6 }], - }); -}); diff --git a/packages/pyright/package-lock.json b/packages/pyright/package-lock.json index 69736bc46a..a77e70768a 100644 --- a/packages/pyright/package-lock.json +++ b/packages/pyright/package-lock.json @@ -1,12 +1,12 @@ { "name": "basedpyright", - "version": "1.1.368", + "version": "1.1.369", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "basedpyright", - "version": "1.1.368", + "version": "1.1.369", "license": "MIT", "bin": { "pyright": "index.js", diff --git a/packages/pyright/package.json b/packages/pyright/package.json index 1334273510..59e95e8317 100644 --- a/packages/pyright/package.json +++ b/packages/pyright/package.json @@ -2,7 +2,7 @@ "name": "basedpyright", "displayName": "basedpyright", "description": "a pyright fork with various type checking improvements, improved vscode support and pylance features built into the language server", - "version": "1.1.368", + "version": "1.1.369", "license": "MIT", "author": { "name": "detachhead" diff --git a/packages/vscode-pyright/package-lock.json b/packages/vscode-pyright/package-lock.json index 2cb82ac94f..84592b36ba 100644 --- a/packages/vscode-pyright/package-lock.json +++ b/packages/vscode-pyright/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-pyright", - "version": "1.1.368", + "version": "1.1.369", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-pyright", - "version": "1.1.368", + "version": "1.1.369", "license": "MIT", "dependencies": { "@vscode/python-extension": "^1.0.5", @@ -1056,12 +1056,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1784,9 +1784,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -4612,12 +4612,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browserslist": { @@ -5126,9 +5126,9 @@ } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" diff --git a/packages/vscode-pyright/package.json b/packages/vscode-pyright/package.json index 2aa40b21ad..09ea248c33 100644 --- a/packages/vscode-pyright/package.json +++ b/packages/vscode-pyright/package.json @@ -2,7 +2,7 @@ "name": "vscode-pyright", "displayName": "BasedPyright", "description": "VS Code static type checking for Python (but based)", - "version": "1.1.368", + "version": "1.1.369", "private": true, "license": "MIT", "author": {