Added a new configuration option enableReachabilityAnalysis. It is off by default when typeCheckingMode is "off" but otherwise on by default. When disabled, it causes pyright not to identify code blocks that are determined to be unreachable via type analysis. Code blocks that are determined to be unreachable via non-type information are still displayed as such. (#8546)

This commit is contained in:
Eric Traut 2024-07-25 19:13:15 -07:00 committed by GitHub
parent 5dd321a280
commit 06d28c39d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 51 additions and 6 deletions

View File

@ -59,6 +59,8 @@ Relative paths specified within the config file are relative to the config file
- <a name="deprecateTypingAliases"></a> **deprecateTypingAliases** [boolean]: PEP 585 indicates that aliases to types in standard collections that were introduced solely to support generics are deprecated as of Python 3.9. This switch controls whether these are treated as deprecated. This applies only when pythonVersion is 3.9 or newer. The default value for this setting is `false` but may be switched to `true` in the future.
- <a name="enableReachabilityAnalysis"></a> **enableReachabilityAnalysis** [boolean]: If enabled, code that is determined to be unreachable by type analysis is reported using a tagged hint. This setting does not affect code that is determined to be unreachable regardless of type analysis; such code is always reported as unreachable. This setting also has no effect when when using the command-line version of pyright because it never emits tagged hints for unreachable code.
- <a name="enableExperimentalFeatures"></a> **enableExperimentalFeatures** [boolean]: Enables a set of experimental (mostly undocumented) features that correspond to proposed or exploratory changes to the Python typing standard. These features will likely change or be removed, so they should not be used except for experimentation purposes. The default value for this setting is `false`.
- <a name="disableBytesTypePromotions"></a> **disableBytesTypePromotions** [boolean]: Disables legacy behavior where `bytearray` and `memoryview` are considered subtypes of `bytes`. [PEP 688](https://peps.python.org/pep-0688/#no-special-meaning-for-bytes) deprecates this behavior, but this switch is provided to restore the older behavior. The default value for this setting is `false`.
@ -343,6 +345,7 @@ The following table lists the default severity levels for each diagnostic rule w
| analyzeUnannotatedFunctions | true | true | true | true |
| strictParameterNoneValue | true | true | true | true |
| enableTypeIgnoreComments | true | true | true | true |
| enableReachabilityAnalysis | false | true | true | true |
| disableBytesTypePromotions | false | false | false | true |
| strictListInference | false | false | false | true |
| strictDictionaryInference | false | false | false | true |

View File

@ -619,3 +619,4 @@ def func3(x: Literal[1, 2]):
print("unreachable")
```
Code that is determined to be unreachable is reported through the use of “tagged hints”. These are special diagnostics that tell a language client to display the code in a visually distinctive manner, typically with a grayed-out appearance. Code determined to be unreachable using non-type information is always reported through this mechanism. Code determined to be unreachable using type analysis is reported only if “enableReachabilityAnalysis” is enabled in the configuration.

View File

@ -121,6 +121,7 @@ import {
FunctionArgument,
FunctionTypeResult,
MemberAccessDeprecationInfo,
Reachability,
TypeEvaluator,
TypeResult,
} from './typeEvaluatorTypes';
@ -2821,13 +2822,14 @@ export class Checker extends ParseTreeWalker {
// No need to report unreachable more than once since the first time
// covers all remaining statements in the statement list.
if (!reportedUnreachable) {
if (!this._evaluator.isNodeReachable(statement, prevStatement)) {
const reachability = this._evaluator.getNodeReachability(statement, prevStatement);
if (reachability !== Reachability.Reachable) {
// Create a text range that covers the next statement through
// the end of the statement list.
const start = statement.start;
const lastStatement = statements[statements.length - 1];
const end = TextRange.getEnd(lastStatement);
this._evaluator.addUnreachableCode(statement, { start, length: end - start });
this._evaluator.addUnreachableCode(statement, reachability, { start, length: end - start });
reportedUnreachable = true;
}
@ -7557,7 +7559,11 @@ export class Checker extends ParseTreeWalker {
LocMessage.unreachableExcept() + diagAddendum.getString(),
except.d.typeExpr
);
this._evaluator.addUnreachableCode(except, except.d.exceptSuite);
this._evaluator.addUnreachableCode(
except,
Reachability.UnreachableByAnalysis,
except.d.exceptSuite
);
}
}

View File

@ -3124,10 +3124,23 @@ export function createTypeEvaluator(
}
}
function addUnreachableCode(node: ParseNode, textRange: TextRange) {
function addUnreachableCode(node: ParseNode, reachability: Reachability, textRange: TextRange) {
if (reachability === Reachability.Reachable) {
return;
}
if (!isDiagnosticSuppressedForNode(node)) {
const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
fileInfo.diagnosticSink.addUnreachableCodeWithTextRange(LocMessage.unreachableCode(), textRange);
const reportTypeReachability = fileInfo.diagnosticRuleSet.enableReachabilityAnalysis;
if (reachability === Reachability.UnreachableAlways || reportTypeReachability) {
fileInfo.diagnosticSink.addUnreachableCodeWithTextRange(
reachability === Reachability.UnreachableAlways
? LocMessage.unreachableCode()
: LocMessage.unreachableCodeType(),
textRange
);
}
}
}

View File

@ -674,7 +674,7 @@ export interface TypeEvaluator {
addInformation: (message: string, node: ParseNode, range?: TextRange) => Diagnostic | undefined;
addUnusedCode: (node: ParseNode, textRange: TextRange) => void;
addUnreachableCode: (node: ParseNode, textRange: TextRange) => void;
addUnreachableCode: (node: ParseNode, reachability: Reachability, textRange: TextRange) => void;
addDeprecated: (message: string, node: ParseNode) => void;
addDiagnostic: (

View File

@ -116,6 +116,9 @@ export interface DiagnosticRuleSet {
// Enable support for type: ignore comments?
enableTypeIgnoreComments: boolean;
// Use tagged hints to identify unreachable code via type analysis?
enableReachabilityAnalysis: boolean;
// No longer treat bytearray and memoryview as subclasses of bytes?
disableBytesTypePromotions: boolean;
@ -405,6 +408,7 @@ export function getBooleanDiagnosticRules(includeNonOverridable = false) {
// want to override it in strict mode or support
// it within pyright comments.
boolRules.push(DiagnosticRule.enableTypeIgnoreComments);
boolRules.push(DiagnosticRule.enableReachabilityAnalysis);
}
return boolRules;
@ -518,6 +522,7 @@ export function getOffDiagnosticRuleSet(): DiagnosticRuleSet {
strictParameterNoneValue: true,
enableExperimentalFeatures: false,
enableTypeIgnoreComments: true,
enableReachabilityAnalysis: false,
deprecateTypingAliases: false,
disableBytesTypePromotions: false,
reportGeneralTypeIssues: 'none',
@ -620,6 +625,7 @@ export function getBasicDiagnosticRuleSet(): DiagnosticRuleSet {
strictParameterNoneValue: true,
enableExperimentalFeatures: false,
enableTypeIgnoreComments: true,
enableReachabilityAnalysis: true,
deprecateTypingAliases: false,
disableBytesTypePromotions: false,
reportGeneralTypeIssues: 'error',
@ -722,6 +728,7 @@ export function getStandardDiagnosticRuleSet(): DiagnosticRuleSet {
strictParameterNoneValue: true,
enableExperimentalFeatures: false,
enableTypeIgnoreComments: true,
enableReachabilityAnalysis: true,
deprecateTypingAliases: false,
disableBytesTypePromotions: false,
reportGeneralTypeIssues: 'error',
@ -824,6 +831,7 @@ export function getStrictDiagnosticRuleSet(): DiagnosticRuleSet {
strictParameterNoneValue: true,
enableExperimentalFeatures: false,
enableTypeIgnoreComments: true, // Not overridden by strict mode
enableReachabilityAnalysis: true, // Not overridden by strict mode
deprecateTypingAliases: false,
disableBytesTypePromotions: true,
reportGeneralTypeIssues: 'error',

View File

@ -18,6 +18,7 @@ export enum DiagnosticRule {
strictParameterNoneValue = 'strictParameterNoneValue',
enableExperimentalFeatures = 'enableExperimentalFeatures',
enableTypeIgnoreComments = 'enableTypeIgnoreComments',
enableReachabilityAnalysis = 'enableReachabilityAnalysis',
deprecateTypingAliases = 'deprecateTypingAliases',
disableBytesTypePromotions = 'disableBytesTypePromotions',

View File

@ -1149,6 +1149,7 @@ export namespace Localizer {
export const unpackOperatorNotAllowed = () => getRawString('Diagnostic.unpackOperatorNotAllowed');
export const unpackTuplesIllegal = () => getRawString('Diagnostic.unpackTuplesIllegal');
export const unreachableCode = () => getRawString('Diagnostic.unreachableCode');
export const unreachableCodeType = () => getRawString('Diagnostic.unreachableCodeType');
export const unreachableExcept = () => getRawString('Diagnostic.unreachableExcept');
export const unsupportedDunderAllOperation = () => getRawString('Diagnostic.unsupportedDunderAllOperation');
export const unusedCallResult = () =>

View File

@ -596,6 +596,7 @@
"unpackOperatorNotAllowed": "Unpack operation is not allowed in this context",
"unpackTuplesIllegal": "Unpack operation not allowed in tuples prior to Python 3.8",
"unreachableCode": "Code is unreachable",
"unreachableCodeType": "Type analysis indicates code is unreachable",
"unreachableExcept": "Except clause is unreachable because exception is already handled",
"unsupportedDunderAllOperation": "Operation on \"__all__\" is not supported, so exported symbol list may be incorrect",
"unusedCallResult": "Result of call expression is of type \"{type}\" and is not used; assign to variable \"_\" if this is intentional",

View File

@ -89,6 +89,11 @@
"title": "Allow \"# type: ignore\" comments",
"default": true
},
"enableReachabilityAnalysis": {
"type": "boolean",
"title": "Identify code determined to be unreachable through type analysis",
"default": true
},
"deprecateTypingAliases": {
"type": "boolean",
"title": "Treat typing-specific aliases to standard types as deprecated",
@ -607,6 +612,9 @@
"enableTypeIgnoreComments": {
"$ref": "#/definitions/enableTypeIgnoreComments"
},
"enableReachabilityAnalysis": {
"$ref": "#/definitions/enableReachabilityAnalysis"
},
"deprecateTypingAliases": {
"$ref": "#/definitions/deprecateTypingAliases"
},
@ -922,6 +930,9 @@
"enableTypeIgnoreComments": {
"$ref": "#/definitions/enableTypeIgnoreComments"
},
"enableReachabilityAnalysis": {
"$ref": "#/definitions/enableReachabilityAnalysis"
},
"deprecateTypingAliases": {
"$ref": "#/definitions/deprecateTypingAliases"
},