From 1568a2d485669c4eccfd92f026de766089e918c3 Mon Sep 17 00:00:00 2001 From: detachhead Date: Thu, 14 Nov 2024 22:30:59 +1000 Subject: [PATCH 1/3] report explicit usages of the `Any` type --- packages/pyright-internal/src/analyzer/checker.ts | 4 ++++ packages/pyright-internal/src/localization/localize.ts | 1 + .../pyright-internal/src/localization/package.nls.en-us.json | 3 ++- packages/pyright-internal/src/tests/reportAny.test.ts | 4 ++++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/pyright-internal/src/analyzer/checker.ts b/packages/pyright-internal/src/analyzer/checker.ts index b71ccdec2a..f5a39a3948 100644 --- a/packages/pyright-internal/src/analyzer/checker.ts +++ b/packages/pyright-internal/src/analyzer/checker.ts @@ -1481,6 +1481,10 @@ export class Checker extends ParseTreeWalker { const type = this._evaluator.getType(node); this._reportDeprecatedUseForType(node, type); + if (type && isAny(type) && type.props?.specialForm) { + this._evaluator.addDiagnostic(DiagnosticRule.reportAny, LocMessage.explicitAny(), node); + } + return true; } diff --git a/packages/pyright-internal/src/localization/localize.ts b/packages/pyright-internal/src/localization/localize.ts index 1bb06f8f1e..27ed3c00d3 100644 --- a/packages/pyright-internal/src/localization/localize.ts +++ b/packages/pyright-internal/src/localization/localize.ts @@ -1241,6 +1241,7 @@ export namespace Localizer { new ParameterizedString<{ classes: string }>(getRawString('Diagnostic.multipleInheritance')); export const unannotatedClassAttribute = () => new ParameterizedString<{ name: string }>(getRawString('Diagnostic.unannotatedClassAttribute')); + export const explicitAny = () => getRawString('Diagnostic.explicitAny'); } export namespace DiagnosticAddendum { diff --git a/packages/pyright-internal/src/localization/package.nls.en-us.json b/packages/pyright-internal/src/localization/package.nls.en-us.json index 121d1b9d9d..034c5b3457 100644 --- a/packages/pyright-internal/src/localization/package.nls.en-us.json +++ b/packages/pyright-internal/src/localization/package.nls.en-us.json @@ -1730,7 +1730,8 @@ "implicitRelativeImport": "Import from `{importName}` is implicitly relative and will not work if this file is imported as a module", "invalidCast": "Conversion of type `{fromType}` to type `{toType}` may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to `object` first.", "multipleInheritance": "Multiple inheritance is not allowed because the following base classes contain `__init__` or `__new__` methods that may not get called: {classes}", - "unannotatedClassAttribute": "Type annotation for attribute `{name}` is required because this class is not decorated with `@final`" + "unannotatedClassAttribute": "Type annotation for attribute `{name}` is required because this class is not decorated with `@final`", + "explicitAny": "Type `Any` is not allowed" }, "DiagnosticAddendum": { "annotatedNotAllowed": { diff --git a/packages/pyright-internal/src/tests/reportAny.test.ts b/packages/pyright-internal/src/tests/reportAny.test.ts index 00048b7c68..dc84809423 100644 --- a/packages/pyright-internal/src/tests/reportAny.test.ts +++ b/packages/pyright-internal/src/tests/reportAny.test.ts @@ -12,6 +12,8 @@ test('reportAny', () => { validateResultsButBased(analysisResults, { errors: [ { line: 3, code: DiagnosticRule.reportAny, message: LocMessage.returnTypeAny() }, + { line: 3, code: DiagnosticRule.reportAny, message: LocMessage.explicitAny() }, + { line: 3, code: DiagnosticRule.reportAny, message: LocMessage.explicitAny() }, { line: 3, code: DiagnosticRule.reportAny, @@ -20,6 +22,7 @@ test('reportAny', () => { { line: 4, code: DiagnosticRule.reportAny }, { line: 5, code: DiagnosticRule.reportAny, message: LocMessage.returnTypeAny() }, { line: 7, code: DiagnosticRule.reportAny, message: LocMessage.typeAny().format({ name: 'bar' }) }, + { line: 7, code: DiagnosticRule.reportAny, message: LocMessage.explicitAny() }, { line: 9, code: DiagnosticRule.reportAny, @@ -40,6 +43,7 @@ test('reportAny', () => { code: DiagnosticRule.reportAny, message: LocMessage.lambdaReturnTypeAny(), }, + { line: 15, code: DiagnosticRule.reportAny, message: LocMessage.explicitAny() }, { line: 18, code: DiagnosticRule.reportAny, From 7fdea20ce48092121865420dce81fb61acf4eab2 Mon Sep 17 00:00:00 2001 From: detachhead Date: Thu, 14 Nov 2024 22:47:04 +1000 Subject: [PATCH 2/3] fix new `Any` errors in `localization_helper.py` --- based_build/localization_helper.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/based_build/localization_helper.py b/based_build/localization_helper.py index 113421b0f9..0a01e5794c 100644 --- a/based_build/localization_helper.py +++ b/based_build/localization_helper.py @@ -2,7 +2,7 @@ import json from pathlib import Path -from typing import TYPE_CHECKING, Any, ClassVar, Dict, TypedDict, Union, cast, final +from typing import TYPE_CHECKING, ClassVar, Dict, TypedDict, Union, cast, final from rich.highlighter import ReprHighlighter from rich.text import Text @@ -14,6 +14,8 @@ from typing_extensions import override if TYPE_CHECKING: + from collections.abc import Mapping + from textual.widgets.tree import TreeNode highlighter = ReprHighlighter() @@ -41,7 +43,7 @@ def read_locfile(language: str = "en-us") -> dict[str, LocMessages]: LOCDATA_EN_US = read_locfile() -def diff_keys(orig: dict[str, Any], comp: dict[str, Any]): +def diff_keys(orig: Mapping[str, object], comp: Mapping[str, object]): msgs: list[str] = [] orig_set = set(orig.keys()) comp_set = set(comp.keys()) From 88de06eb9c405d2398e0c43161f96d81d0ea9e8e Mon Sep 17 00:00:00 2001 From: detachhead Date: Thu, 14 Nov 2024 23:03:20 +1000 Subject: [PATCH 3/3] create a separate diagnostic rule for `reportExplicitAny` for backwards compatibility --- .../new-diagnostic-rules.md | 16 +++++++++++++++- docs/configuration.md | 4 +++- .../pyright-internal/src/analyzer/checker.ts | 2 +- .../src/common/configOptions.ts | 8 ++++++++ .../src/common/diagnosticRules.ts | 1 + .../src/tests/reportAny.test.ts | 18 ++++++++++++++---- packages/vscode-pyright/package.json | 19 ++++++++++++++++++- .../schemas/pyrightconfig.schema.json | 13 ++++++++++++- 8 files changed, 72 insertions(+), 9 deletions(-) diff --git a/docs/benefits-over-pyright/new-diagnostic-rules.md b/docs/benefits-over-pyright/new-diagnostic-rules.md index 806d3ec6f5..e5c1c69388 100644 --- a/docs/benefits-over-pyright/new-diagnostic-rules.md +++ b/docs/benefits-over-pyright/new-diagnostic-rules.md @@ -35,7 +35,21 @@ def foo(bar, baz: Any) -> Any: print(baz) # no error ``` -basedpyright introduces the `reportAny` option, which will report an error on usages of anything typed as `Any`. +basedpyright introduces the `reportAny` option, which will report an error on usages of anything typed as `Any`: + +```py +def foo(baz: Any) -> Any: + print(baz) # error: reportAny +``` + +## `reportExplicitAny` + +similar to [`reportAny`](#reportany), however this rule bans usages of the `Any` type itself rather than expressions that are typed as `Any`: + +```py +def foo(baz: Any) -> Any: # error: reportExplicitAny + print(baz) # error: reportAny +``` ## `reportIgnoreCommentWithoutRule` diff --git a/docs/configuration.md b/docs/configuration.md index cb65b14d73..e38eb3156a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -269,7 +269,9 @@ the following additional options are not available in regular pyright: - **reportUnreachable** [boolean or string, optional]: Generate or suppress diagnostics for unreachable code. -- **reportAny** [boolean or string, optional]: Ban all usages of the `Any` type. this accounts for all scenarios not covered by the `reportUnknown*` rules (since "Unknown" isn't a real type, but a distinction pyright makes to disallow the `Any` type only in certain circumstances). +- **reportAny** [boolean or string, optional]: Generate or suppress diagnostics for expressions that have the `Any` type. this accounts for all scenarios not covered by the `reportUnknown*` rules (since "Unknown" isn't a real type, but a distinction pyright makes to disallow the `Any` type only in certain circumstances). + +- **reportExplicitAny** [boolean or string, optional]: Ban all explicit usages of the `Any` type. While `reportAny` bans expressions typed as `Any`, this rule bans using the `Any` type directly eg. in a type annotation. - **reportIgnoreCommentWithoutRule** [boolean or string, optional]: Enforce that all `# type:ignore`/`# pyright:ignore` comments specify a rule in brackets (eg. `# pyright:ignore[reportAny]`) diff --git a/packages/pyright-internal/src/analyzer/checker.ts b/packages/pyright-internal/src/analyzer/checker.ts index f5a39a3948..34964b16b0 100644 --- a/packages/pyright-internal/src/analyzer/checker.ts +++ b/packages/pyright-internal/src/analyzer/checker.ts @@ -1482,7 +1482,7 @@ export class Checker extends ParseTreeWalker { this._reportDeprecatedUseForType(node, type); if (type && isAny(type) && type.props?.specialForm) { - this._evaluator.addDiagnostic(DiagnosticRule.reportAny, LocMessage.explicitAny(), node); + this._evaluator.addDiagnostic(DiagnosticRule.reportExplicitAny, LocMessage.explicitAny(), node); } return true; diff --git a/packages/pyright-internal/src/common/configOptions.ts b/packages/pyright-internal/src/common/configOptions.ts index 67b6f24dd5..4a3e8ccacb 100644 --- a/packages/pyright-internal/src/common/configOptions.ts +++ b/packages/pyright-internal/src/common/configOptions.ts @@ -413,6 +413,7 @@ export interface DiagnosticRuleSet { failOnWarnings: boolean; reportUnreachable: DiagnosticLevel; reportAny: DiagnosticLevel; + reportExplicitAny: DiagnosticLevel; reportIgnoreCommentWithoutRule: DiagnosticLevel; reportPrivateLocalImportUsage: DiagnosticLevel; reportImplicitRelativeImport: DiagnosticLevel; @@ -540,6 +541,7 @@ export function getDiagLevelDiagnosticRules() { DiagnosticRule.reportImplicitOverride, DiagnosticRule.reportUnreachable, DiagnosticRule.reportAny, + DiagnosticRule.reportExplicitAny, DiagnosticRule.reportIgnoreCommentWithoutRule, DiagnosticRule.reportInvalidCast, DiagnosticRule.reportImplicitRelativeImport, @@ -673,6 +675,7 @@ export function getOffDiagnosticRuleSet(): DiagnosticRuleSet { failOnWarnings: false, reportUnreachable: 'hint', reportAny: 'none', + reportExplicitAny: 'none', reportIgnoreCommentWithoutRule: 'none', reportPrivateLocalImportUsage: 'none', reportImplicitRelativeImport: 'none', @@ -787,6 +790,7 @@ export function getBasicDiagnosticRuleSet(): DiagnosticRuleSet { failOnWarnings: false, reportUnreachable: 'hint', reportAny: 'none', + reportExplicitAny: 'none', reportIgnoreCommentWithoutRule: 'none', reportPrivateLocalImportUsage: 'none', reportImplicitRelativeImport: 'none', @@ -901,6 +905,7 @@ export function getStandardDiagnosticRuleSet(): DiagnosticRuleSet { failOnWarnings: false, reportUnreachable: 'hint', reportAny: 'none', + reportExplicitAny: 'none', reportIgnoreCommentWithoutRule: 'none', reportPrivateLocalImportUsage: 'none', reportImplicitRelativeImport: 'none', @@ -1014,6 +1019,7 @@ export const getRecommendedDiagnosticRuleSet = (): DiagnosticRuleSet => ({ failOnWarnings: true, reportUnreachable: 'warning', reportAny: 'warning', + reportExplicitAny: 'warning', reportIgnoreCommentWithoutRule: 'warning', reportPrivateLocalImportUsage: 'warning', reportImplicitRelativeImport: 'error', @@ -1124,6 +1130,7 @@ export const getAllDiagnosticRuleSet = (): DiagnosticRuleSet => ({ failOnWarnings: true, reportUnreachable: 'error', reportAny: 'error', + reportExplicitAny: 'error', reportIgnoreCommentWithoutRule: 'error', reportPrivateLocalImportUsage: 'error', reportImplicitRelativeImport: 'error', @@ -1235,6 +1242,7 @@ export function getStrictDiagnosticRuleSet(): DiagnosticRuleSet { failOnWarnings: false, reportUnreachable: 'hint', reportAny: 'none', + reportExplicitAny: 'none', reportIgnoreCommentWithoutRule: 'none', reportPrivateLocalImportUsage: 'none', reportImplicitRelativeImport: 'none', diff --git a/packages/pyright-internal/src/common/diagnosticRules.ts b/packages/pyright-internal/src/common/diagnosticRules.ts index 27e2d766d4..332f799ab4 100644 --- a/packages/pyright-internal/src/common/diagnosticRules.ts +++ b/packages/pyright-internal/src/common/diagnosticRules.ts @@ -108,6 +108,7 @@ export enum DiagnosticRule { failOnWarnings = 'failOnWarnings', reportUnreachable = 'reportUnreachable', reportAny = 'reportAny', + reportExplicitAny = 'reportExplicitAny', reportIgnoreCommentWithoutRule = 'reportIgnoreCommentWithoutRule', reportPrivateLocalImportUsage = 'reportPrivateLocalImportUsage', reportImplicitRelativeImport = 'reportImplicitRelativeImport', diff --git a/packages/pyright-internal/src/tests/reportAny.test.ts b/packages/pyright-internal/src/tests/reportAny.test.ts index dc84809423..5ca931c84e 100644 --- a/packages/pyright-internal/src/tests/reportAny.test.ts +++ b/packages/pyright-internal/src/tests/reportAny.test.ts @@ -12,8 +12,6 @@ test('reportAny', () => { validateResultsButBased(analysisResults, { errors: [ { line: 3, code: DiagnosticRule.reportAny, message: LocMessage.returnTypeAny() }, - { line: 3, code: DiagnosticRule.reportAny, message: LocMessage.explicitAny() }, - { line: 3, code: DiagnosticRule.reportAny, message: LocMessage.explicitAny() }, { line: 3, code: DiagnosticRule.reportAny, @@ -22,7 +20,6 @@ test('reportAny', () => { { line: 4, code: DiagnosticRule.reportAny }, { line: 5, code: DiagnosticRule.reportAny, message: LocMessage.returnTypeAny() }, { line: 7, code: DiagnosticRule.reportAny, message: LocMessage.typeAny().format({ name: 'bar' }) }, - { line: 7, code: DiagnosticRule.reportAny, message: LocMessage.explicitAny() }, { line: 9, code: DiagnosticRule.reportAny, @@ -43,7 +40,6 @@ test('reportAny', () => { code: DiagnosticRule.reportAny, message: LocMessage.lambdaReturnTypeAny(), }, - { line: 15, code: DiagnosticRule.reportAny, message: LocMessage.explicitAny() }, { line: 18, code: DiagnosticRule.reportAny, @@ -52,3 +48,17 @@ test('reportAny', () => { ], }); }); + +test('reportExplicitAny', () => { + const configOptions = new ConfigOptions(Uri.empty()); + configOptions.diagnosticRuleSet.reportExplicitAny = 'error'; + const analysisResults = typeAnalyzeSampleFiles(['reportAny.py'], configOptions); + + validateResultsButBased(analysisResults, { + errors: [3, 3, 7, 15].map((lineNumber) => ({ + line: lineNumber, + code: DiagnosticRule.reportExplicitAny, + message: LocMessage.explicitAny(), + })), + }); +}); diff --git a/packages/vscode-pyright/package.json b/packages/vscode-pyright/package.json index d5be145980..134c01b870 100644 --- a/packages/vscode-pyright/package.json +++ b/packages/vscode-pyright/package.json @@ -1596,7 +1596,24 @@ "string", "boolean" ], - "description": "Diagnostics for anything with the `Any` type", + "description": "Diagnostics for expressions with the `Any` type", + "default": "none", + "enum": [ + "none", + "hint", + "information", + "warning", + "error", + true, + false + ] + }, + "reportExplicitAny": { + "type": [ + "string", + "boolean" + ], + "description": "Diagnostics for type annotations that use the `Any` type", "default": "none", "enum": [ "none", diff --git a/packages/vscode-pyright/schemas/pyrightconfig.schema.json b/packages/vscode-pyright/schemas/pyrightconfig.schema.json index 93f4736908..29c00344c3 100644 --- a/packages/vscode-pyright/schemas/pyrightconfig.schema.json +++ b/packages/vscode-pyright/schemas/pyrightconfig.schema.json @@ -512,7 +512,12 @@ }, "reportAny": { "$ref": "#/definitions/diagnostic", - "title": "Controls reporting of values typed as `Any`", + "title": "Controls reporting of expressions typed as `Any`", + "default": "none" + }, + "reportExplicitAny": { + "$ref": "#/definitions/diagnostic", + "title": "Controls reporting of type annotations that use the `Any` type", "default": "none" }, "reportIgnoreCommentWithoutRule": { @@ -920,6 +925,9 @@ "reportAny": { "$ref": "#/definitions/reportAny" }, + "reportExplicitAny": { + "$ref": "#/definitions/reportExplicitAny" + }, "reportIgnoreCommentWithoutRule": { "$ref": "#/definitions/reportIgnoreCommentWithoutRule" }, @@ -1268,6 +1276,9 @@ "reportAny": { "$ref": "#/definitions/reportAny" }, + "reportExplicitAny": { + "$ref": "#/definitions/reportExplicitAny" + }, "reportIgnoreCommentWithoutRule": { "$ref": "#/definitions/reportIgnoreCommentWithoutRule" },