diff --git a/docs/configuration.md b/docs/configuration.md index a39088bb4..9428455da 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -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 you’re actively contributing updates to typeshed. diff --git a/packages/pyright-internal/src/analyzer/staticExpressions.ts b/packages/pyright-internal/src/analyzer/staticExpressions.ts index 9f4649f57..b000a0ca3 100644 --- a/packages/pyright-internal/src/analyzer/staticExpressions.ts +++ b/packages/pyright-internal/src/analyzer/staticExpressions.ts @@ -118,11 +118,15 @@ export function evaluateStaticBoolExpression( } } else { // Handle the special case of == 'X' or != '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; diff --git a/packages/pyright-internal/src/tests/samples/staticExpressions1.py b/packages/pyright-internal/src/tests/samples/staticExpressions1.py index 692786a0a..63a4416ae 100644 --- a/packages/pyright-internal/src/tests/samples/staticExpressions1.py +++ b/packages/pyright-internal/src/tests/samples/staticExpressions1.py @@ -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!" diff --git a/packages/pyright-internal/src/tests/typeEvaluator1.test.ts b/packages/pyright-internal/src/tests/typeEvaluator1.test.ts index f478c6151..5e1678715 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator1.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator1.test.ts @@ -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);