From bb04ffa6bef8ad8a7bfe9aec5e9ef1803d2e28bb Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 19 Oct 2020 13:30:06 -0700 Subject: [PATCH] Added new diagnostic rule "reportInvalidTypeVarUse" that controls reporting of TypeVars that appear only once in a function signature. By default it is off in basic type checking mode but on in strict mode. --- docs/configuration.md | 2 ++ packages/pyright-internal/src/analyzer/checker.ts | 9 +++++++-- .../pyright-internal/src/common/configOptions.ts | 14 ++++++++++++++ .../pyright-internal/src/common/diagnosticRules.ts | 1 + .../pyright-internal/src/tests/samples/typeVar5.py | 2 ++ .../src/tests/typeEvaluator2.test.ts | 4 ++-- packages/vscode-pyright/package.json | 11 +++++++++++ .../schemas/pyrightconfig.schema.json | 6 ++++++ 8 files changed, 45 insertions(+), 4 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index a811ff6a5..d870fa516 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -112,6 +112,8 @@ The following settings control pyright’s diagnostic output (warnings or errors **reportMissingTypeArgument** [boolean or string, optional]: Generate or suppress diagnostics when a generic class is used without providing explicit or implicit type arguments. The default value for this setting is 'none'. +**reportInvalidTypeVarUse** [boolean or string, optional]: Generate or suppress diagnostics when a TypeVar is used inappropriately (e.g. if a TypeVar appears only once) within a generic function signature. The default value for this setting is 'none'. + **reportCallInDefaultInitializer** [boolean or string, optional]: Generate or suppress diagnostics for function calls within a default value initialization expression. Such calls can mask expensive operations that are performed at module initialization time. The default value for this setting is 'none'. **reportUnnecessaryIsInstance** [boolean or string, optional]: Generate or suppress diagnostics for 'isinstance' or 'issubclass' calls where the result is statically determined to be always true or always false. Such calls are often indicative of a programming error. The default value for this setting is 'none'. diff --git a/packages/pyright-internal/src/analyzer/checker.ts b/packages/pyright-internal/src/analyzer/checker.ts index 0a2fcf72a..11aa35c14 100644 --- a/packages/pyright-internal/src/analyzer/checker.ts +++ b/packages/pyright-internal/src/analyzer/checker.ts @@ -796,6 +796,11 @@ export class Checker extends ParseTreeWalker { // Verifies that each local type variable is used more than once. private _validateFunctionTypeVarUsage(node: FunctionNode) { + // Skip this check entirely if it's disabled. + if (this._fileInfo.diagnosticRuleSet.reportInvalidTypeVarUse === 'none') { + return; + } + const localTypeVarUsage = new Map(); const nameWalker = new ParseTreeUtils.NameNodeWalker((nameNode) => { @@ -827,8 +832,8 @@ export class Checker extends ParseTreeWalker { localTypeVarUsage.forEach((nameNodes) => { if (nameNodes.length === 1) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, - DiagnosticRule.reportGeneralTypeIssues, + this._fileInfo.diagnosticRuleSet.reportInvalidTypeVarUse, + DiagnosticRule.reportInvalidTypeVarUse, Localizer.Diagnostic.typeVarUsedOnlyOnce().format({ name: nameNodes[0].value, }), diff --git a/packages/pyright-internal/src/common/configOptions.ts b/packages/pyright-internal/src/common/configOptions.ts index 2bcd7d470..0d944e02f 100644 --- a/packages/pyright-internal/src/common/configOptions.ts +++ b/packages/pyright-internal/src/common/configOptions.ts @@ -192,6 +192,9 @@ export interface DiagnosticRuleSet { // Report usage of generic class without explicit type arguments? reportMissingTypeArgument: DiagnosticLevel; + // Report improper usage of type variables within function signatures? + reportInvalidTypeVarUse: DiagnosticLevel; + // Report usage of function call within default value // initialization expression? reportCallInDefaultInitializer: DiagnosticLevel; @@ -277,6 +280,7 @@ export function getDiagLevelDiagnosticRules() { DiagnosticRule.reportUnknownVariableType, DiagnosticRule.reportUnknownMemberType, DiagnosticRule.reportMissingTypeArgument, + DiagnosticRule.reportInvalidTypeVarUse, DiagnosticRule.reportCallInDefaultInitializer, DiagnosticRule.reportUnnecessaryIsInstance, DiagnosticRule.reportUnnecessaryCast, @@ -338,6 +342,7 @@ export function getOffDiagnosticRuleSet(): DiagnosticRuleSet { reportUnknownVariableType: 'none', reportUnknownMemberType: 'none', reportMissingTypeArgument: 'none', + reportInvalidTypeVarUse: 'none', reportCallInDefaultInitializer: 'none', reportUnnecessaryIsInstance: 'none', reportUnnecessaryCast: 'none', @@ -395,6 +400,7 @@ export function getBasicDiagnosticRuleSet(): DiagnosticRuleSet { reportUnknownVariableType: 'none', reportUnknownMemberType: 'none', reportMissingTypeArgument: 'none', + reportInvalidTypeVarUse: 'none', reportCallInDefaultInitializer: 'none', reportUnnecessaryIsInstance: 'none', reportUnnecessaryCast: 'none', @@ -452,6 +458,7 @@ export function getStrictDiagnosticRuleSet(): DiagnosticRuleSet { reportUnknownVariableType: 'error', reportUnknownMemberType: 'error', reportMissingTypeArgument: 'error', + reportInvalidTypeVarUse: 'error', reportCallInDefaultInitializer: 'none', reportUnnecessaryIsInstance: 'error', reportUnnecessaryCast: 'error', @@ -1020,6 +1027,13 @@ export class ConfigOptions { defaultSettings.reportMissingTypeArgument ), + // Read the "reportInvalidTypeVarUse" entry. + reportInvalidTypeVarUse: this._convertDiagnosticLevel( + configObj.reportInvalidTypeVarUse, + DiagnosticRule.reportInvalidTypeVarUse, + defaultSettings.reportInvalidTypeVarUse + ), + // Read the "reportCallInDefaultInitializer" entry. reportCallInDefaultInitializer: this._convertDiagnosticLevel( configObj.reportCallInDefaultInitializer, diff --git a/packages/pyright-internal/src/common/diagnosticRules.ts b/packages/pyright-internal/src/common/diagnosticRules.ts index 37abf86ee..775e406ff 100644 --- a/packages/pyright-internal/src/common/diagnosticRules.ts +++ b/packages/pyright-internal/src/common/diagnosticRules.ts @@ -49,6 +49,7 @@ export enum DiagnosticRule { reportUnknownVariableType = 'reportUnknownVariableType', reportUnknownMemberType = 'reportUnknownMemberType', reportMissingTypeArgument = 'reportMissingTypeArgument', + reportInvalidTypeVarUse = 'reportInvalidTypeVarUse', reportCallInDefaultInitializer = 'reportCallInDefaultInitializer', reportUnnecessaryIsInstance = 'reportUnnecessaryIsInstance', reportUnnecessaryCast = 'reportUnnecessaryCast', diff --git a/packages/pyright-internal/src/tests/samples/typeVar5.py b/packages/pyright-internal/src/tests/samples/typeVar5.py index ed0033321..be2ce68eb 100644 --- a/packages/pyright-internal/src/tests/samples/typeVar5.py +++ b/packages/pyright-internal/src/tests/samples/typeVar5.py @@ -2,6 +2,8 @@ # a generic function. A TypeVar must appear at least twice to be # considered legitimate. +# pyright: reportInvalidTypeVarUse=true + from typing import Dict, Generic, List, TypeVar diff --git a/packages/pyright-internal/src/tests/typeEvaluator2.test.ts b/packages/pyright-internal/src/tests/typeEvaluator2.test.ts index b493ef71e..7228cd9e4 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator2.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator2.test.ts @@ -878,7 +878,7 @@ test('ParamSpec1', () => { configOptions.defaultPythonVersion = PythonVersion.V3_10; const results = TestUtils.typeAnalyzeSampleFiles(['paramSpec1.py'], configOptions); - TestUtils.validateResults(results, 8); + TestUtils.validateResults(results, 7); }); test('ParamSpec2', () => { @@ -906,7 +906,7 @@ test('ParamSpec4', () => { configOptions.defaultPythonVersion = PythonVersion.V3_10; const results = TestUtils.typeAnalyzeSampleFiles(['paramSpec4.py'], configOptions); - TestUtils.validateResults(results, 7); + TestUtils.validateResults(results, 5); }); test('ClassVar1', () => { diff --git a/packages/vscode-pyright/package.json b/packages/vscode-pyright/package.json index a2c44d7a1..8de1e465e 100644 --- a/packages/vscode-pyright/package.json +++ b/packages/vscode-pyright/package.json @@ -477,6 +477,17 @@ "error" ] }, + "reportInvalidTypeVarUse": { + "type": "string", + "description": "Diagnostics for improper use of type variables in a function signature.", + "default": "none", + "enum": [ + "none", + "information", + "warning", + "error" + ] + }, "reportCallInDefaultInitializer": { "type": "string", "description": "Diagnostics for function calls within a default value initialization expression. Such calls can mask expensive operations that are performed at module initialization time.", diff --git a/packages/vscode-pyright/schemas/pyrightconfig.schema.json b/packages/vscode-pyright/schemas/pyrightconfig.schema.json index b0aa5a3dd..f8b696325 100644 --- a/packages/vscode-pyright/schemas/pyrightconfig.schema.json +++ b/packages/vscode-pyright/schemas/pyrightconfig.schema.json @@ -321,6 +321,12 @@ "title": "Controls reporting generic class reference with missing type arguments", "default": "none" }, + "reportInvalidTypeVarUse": { + "$id": "#/properties/reportInvalidTypeVarUse", + "$ref": "#/definitions/diagnostic", + "title": "Controls reporting improper use of type variables within function signatures", + "default": "none" + }, "reportCallInDefaultInitializer": { "$id": "#/properties/reportCallInDefaultInitializer", "$ref": "#/definitions/diagnostic",