Implemented new diagnostic rule reportUnusedCallResult that checks whether a call expression's results are consumed. If the results are None or Any, no diagnostic is produced.

This commit is contained in:
Eric Traut 2020-11-25 19:50:31 -08:00
parent aca23b2ae6
commit cf1a5790d2
10 changed files with 129 additions and 0 deletions

View File

@ -134,6 +134,8 @@ The following settings control pyrights diagnostic output (warnings or errors
**reportUnsupportedDunderAll** [boolean or string, optional]: Generate or suppress diagnostics for statements that define or manipulate `__all__` in a way that is not allowed by a static type checker, thus rendering the contents of `__all__` to be unknown or incorrect. The default value for this setting is 'warning'.
**reportUnusedCallResult** [boolean or string, optional]: Generate or suppress diagnostics for call statements whose return value is not used in any way and is not None. The default value for this setting is 'none'.
## Execution Environment Options
Pyright allows multiple “execution environments” to be defined for different portions of your source tree. For example, a subtree may be designed to run with different import search paths or a different version of the python interpreter than the rest of the source base.

View File

@ -421,6 +421,28 @@ export class Checker extends ParseTreeWalker {
);
}
if (this._fileInfo.diagnosticRuleSet.reportUnusedCallResult !== 'none') {
if (node.parent?.nodeType === ParseNodeType.StatementList) {
const returnType = this._evaluator.getType(node);
if (
returnType &&
!isNone(returnType) &&
!isNoReturnType(returnType) &&
!isNever(returnType) &&
!isAnyOrUnknown(returnType)
) {
this._evaluator.addDiagnostic(
this._fileInfo.diagnosticRuleSet.reportUnusedCallResult,
DiagnosticRule.reportUnusedCallResult,
Localizer.Diagnostic.unusedCallResult().format({
type: this._evaluator.printType(returnType, /* expandTypeAlias */ false),
}),
node
);
}
}
}
return true;
}

View File

@ -229,6 +229,10 @@ export interface DiagnosticRuleSet {
// Report operations on __all__ symbol that are not supported
// by a static type checker.
reportUnsupportedDunderAll: DiagnosticLevel;
// Report cases where a call expression's return result is not
// None and is not used in any way.
reportUnusedCallResult: DiagnosticLevel;
}
export function cloneDiagnosticRuleSet(diagSettings: DiagnosticRuleSet): DiagnosticRuleSet {
@ -295,6 +299,7 @@ export function getDiagLevelDiagnosticRules() {
DiagnosticRule.reportUnboundVariable,
DiagnosticRule.reportInvalidStubStatement,
DiagnosticRule.reportUnsupportedDunderAll,
DiagnosticRule.reportUnusedCallResult,
];
}
@ -358,6 +363,7 @@ export function getOffDiagnosticRuleSet(): DiagnosticRuleSet {
reportUndefinedVariable: 'warning',
reportInvalidStubStatement: 'none',
reportUnsupportedDunderAll: 'none',
reportUnusedCallResult: 'none',
};
return diagSettings;
@ -417,6 +423,7 @@ export function getBasicDiagnosticRuleSet(): DiagnosticRuleSet {
reportUndefinedVariable: 'error',
reportInvalidStubStatement: 'none',
reportUnsupportedDunderAll: 'warning',
reportUnusedCallResult: 'none',
};
return diagSettings;
@ -476,6 +483,7 @@ export function getStrictDiagnosticRuleSet(): DiagnosticRuleSet {
reportUndefinedVariable: 'error',
reportInvalidStubStatement: 'error',
reportUnsupportedDunderAll: 'error',
reportUnusedCallResult: 'none',
};
return diagSettings;
@ -1111,6 +1119,13 @@ export class ConfigOptions {
DiagnosticRule.reportUnsupportedDunderAll,
defaultSettings.reportUnsupportedDunderAll
),
// Read the "reportUnusedCallResult" entry.
reportUnusedCallResult: this._convertDiagnosticLevel(
configObj.reportUnusedCallResult,
DiagnosticRule.reportUnusedCallResult,
defaultSettings.reportUnusedCallResult
),
};
// Read the "venvPath".

View File

@ -60,4 +60,5 @@ export enum DiagnosticRule {
reportUnboundVariable = 'reportUnboundVariable',
reportInvalidStubStatement = 'reportInvalidStubStatement',
reportUnsupportedDunderAll = 'reportUnsupportedDunderAll',
reportUnusedCallResult = 'reportUnusedCallResult',
}

View File

@ -657,6 +657,10 @@ export namespace Localizer {
export const unreachableCode = () => getRawString('Diagnostic.unreachableCode');
export const unsupportedDunderAllAssignment = () => getRawString('Diagnostic.unsupportedDunderAllAssignment');
export const unsupportedDunderAllOperation = () => getRawString('Diagnostic.unsupportedDunderAllOperation');
export const unusedCallResult = () =>
new ParameterizedString<{ type: string }>(
getRawString('Diagnostic.unusedCallResult')
);
export const varAnnotationIllegal = () => getRawString('Diagnostic.varAnnotationIllegal');
export const walrusIllegal = () => getRawString('Diagnostic.walrusIllegal');
export const walrusNotAllowed = () => getRawString('Diagnostic.walrusNotAllowed');

View File

@ -328,6 +328,7 @@
"unreachableCode": "Code is unreachable",
"unsupportedDunderAllAssignment": "Expression assigned to \"__all__\" is not supported, so exported symbol list may be incorrect; use list or tuple of string literal values in assignment",
"unsupportedDunderAllOperation": "Operation on \"__all__\" is not supported, so exported symbol list may not be incorrect",
"unusedCallResult": "Result of call expression is of type \"{type}\" and is not used; assign to variable \"_\" if this is intentional",
"varAnnotationIllegal": "Type annotations for variables requires Python 3.6 or newer; use type comment for compatibility with previous versions",
"walrusIllegal": "Operator \":=\" requires Python 3.8 or newer",
"walrusNotAllowed": "Operator \":=\" not allowed in this context",

View File

@ -0,0 +1,54 @@
# This sample tests the reportUnusedCallResult diagnostic rule.
from typing import Any, Iterable, List, Union
def func1():
pass
def func2():
raise RuntimeError()
def func3() -> Any:
pass
def func4():
return 3
def func5(a: int) -> Union[int, List[int]]:
if a < 0:
return 5
return [3]
def func6() -> Iterable[int]:
return []
func1()
func2()
func3()
# This should generate a diagnostic if reportUnusedCallResult is enabled.
func4()
# This should generate a diagnostic if reportUnusedCallResult is enabled.
func5(3)
# This should generate a diagnostic if reportUnusedCallResult is enabled.
func6()
_, _ = func5(3), func6()
_ = func5(3)
_ = func5(func4())
for _ in func6():
pass

View File

@ -1211,3 +1211,16 @@ test('ClassGetItem1', () => {
TestUtils.validateResults(analysisResults, 0, 1);
});
test('UnusedCallResult1', () => {
const configOptions = new ConfigOptions('.');
// By default, this is disabled.
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unusedCallResult1.py'], configOptions);
TestUtils.validateResults(analysisResults, 0);
// Enable it as an error.
configOptions.diagnosticRuleSet.reportUnusedCallResult = 'error';
analysisResults = TestUtils.typeAnalyzeSampleFiles(['unusedCallResult1.py'], configOptions);
TestUtils.validateResults(analysisResults, 3);
});

View File

@ -587,6 +587,17 @@
"error"
]
},
"reportUnusedCallResult": {
"type": "string",
"description": "Diagnostics for call expressions whose results are not consumed and are not None.",
"default": "none",
"enum": [
"none",
"information",
"warning",
"error"
]
},
"reportUnsupportedDunderAll": {
"type": "string",
"description": "Diagnostics for unsupported operations performed on __all__.",

View File

@ -387,6 +387,12 @@
"title": "Controls reporting of unsupported operations performed on __all__",
"default": "warning"
},
"reportUnusedCallResult": {
"$id": "#/properties/reportUnusedCallResult",
"$ref": "#/definitions/diagnostic",
"title": "Controls reporting of call expressions whose results are not consumed",
"default": "none"
},
"pythonVersion": {
"$id": "#/properties/pythonVersion",
"type": "string",