Extended the defineConstant mechanism to work with conditional statements that contain member access expressions that reference a defined member name. This addresses https://github.com/microsoft/pyright/issues/4060.

This commit is contained in:
Eric Traut 2022-10-18 15:28:17 -07:00
parent 2af65e0de6
commit 8538998719
4 changed files with 48 additions and 16 deletions

View File

@ -16,7 +16,7 @@ Relative paths specified within the config file are relative to the config file
**strict** [array of paths, optional]: Paths of directories or files that should use “strict” analysis if they are included. This is the same as manually adding a “# pyright: strict” comment. In strict mode, most type-checking rules are enabled. Refer to [this table](https://github.com/microsoft/pyright/blob/main/docs/configuration.md#diagnostic-rule-defaults) for details about which rules are enabled in strict mode. Paths may contain wildcard characters ** (a directory or multiple levels of directories), * (a sequence of zero or more characters), or ? (a single character).
**defineConstant** [map of constants to values (boolean or string), optional]: Set of identifiers that should be assumed to contain a constant value wherever used within this program. For example, `{ "DEBUG": true }` indicates that pyright should assume that the identifier `DEBUG` will always be equal to `True`. If this identifier is used within a conditional expression (such as `if not DEBUG:`) pyright will use the indicated value to determine whether the guarded block is reachable or not.
**defineConstant** [map of constants to values (boolean or string), optional]: Set of identifiers that should be assumed to contain a constant value wherever used within this program. For example, `{ "DEBUG": true }` indicates that pyright should assume that the identifier `DEBUG` will always be equal to `True`. If this identifier is used within a conditional expression (such as `if not DEBUG:`) pyright will use the indicated value to determine whether the guarded block is reachable or not. Member expressions that reference one of these constants (e.g. `my_module.DEBUG`) are also supported.
**typeshedPath** [path, optional]: Path to a directory that contains typeshed type stub files. Pyright ships with a bundled copy of typeshed type stubs. If you want to use a different version of typeshed stubs, you can clone the [typeshed github repo](https://github.com/python/typeshed) to a local directory and reference the location with this path. This option is useful if youre actively contributing updates to typeshed.

View File

@ -118,11 +118,15 @@ export function evaluateStaticBoolExpression(
}
} else {
// Handle the special case of <definedConstant> == 'X' or <definedConstant> != 'X'.
if (
node.leftExpression.nodeType === ParseNodeType.Name &&
node.rightExpression.nodeType === ParseNodeType.StringList
) {
const constantValue = definedConstants.get(node.leftExpression.value);
if (node.rightExpression.nodeType === ParseNodeType.StringList) {
let constantValue: string | number | boolean | undefined;
if (node.leftExpression.nodeType === ParseNodeType.Name) {
constantValue = definedConstants.get(node.leftExpression.value);
} else if (node.leftExpression.nodeType === ParseNodeType.MemberAccess) {
constantValue = definedConstants.get(node.leftExpression.memberName.value);
}
if (constantValue !== undefined && typeof constantValue === 'string') {
const comparisonStringName = node.rightExpression.strings.map((s) => s.value).join('');
return _evaluateStringBinaryOperation(node.operator, constantValue, comparisonStringName);
@ -144,14 +148,20 @@ export function evaluateStaticBoolExpression(
if (constant !== undefined) {
return !!constant;
}
} else if (
typingImportAliases &&
node.nodeType === ParseNodeType.MemberAccess &&
node.memberName.value === 'TYPE_CHECKING' &&
node.leftExpression.nodeType === ParseNodeType.Name &&
typingImportAliases.some((alias) => alias === (node.leftExpression as NameNode).value)
) {
return true;
} else if (node.nodeType === ParseNodeType.MemberAccess) {
if (
typingImportAliases &&
node.memberName.value === 'TYPE_CHECKING' &&
node.leftExpression.nodeType === ParseNodeType.Name &&
typingImportAliases.some((alias) => alias === (node.leftExpression as NameNode).value)
) {
return true;
}
const constant = definedConstants.get(node.memberName.value);
if (constant !== undefined) {
return !!constant;
}
}
return undefined;

View File

@ -50,3 +50,25 @@ if DEFINED_STR == "hi!":
x = 1
else:
x = "error!"
class Dummy:
DEFINED_FALSE: bool
DEFINED_TRUE: bool
DEFINED_STR: str
dummy = Dummy()
if dummy.DEFINED_TRUE:
x = 1
else:
x = "error!"
if not dummy.DEFINED_FALSE:
x = 1
else:
x = "error!"
if dummy.DEFINED_STR == "hi!":
x = 1
else:
x = "error!"

View File

@ -1374,13 +1374,13 @@ test('StaticExpressions1', () => {
configOptions.defaultPythonPlatform = 'windows';
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['staticExpressions1.py'], configOptions);
TestUtils.validateResults(analysisResults1, 6);
TestUtils.validateResults(analysisResults1, 9);
configOptions.defaultPythonVersion = PythonVersion.V3_11;
configOptions.defaultPythonPlatform = 'Linux';
const analysisResults2 = TestUtils.typeAnalyzeSampleFiles(['staticExpressions1.py'], configOptions);
TestUtils.validateResults(analysisResults2, 3);
TestUtils.validateResults(analysisResults2, 6);
configOptions.defineConstant.set('DEFINED_TRUE', true);
configOptions.defineConstant.set('DEFINED_FALSE', false);