Extended reportCallInDefaultInitializer diagnostic check to disallow list, set or dict expressions in default argument expression.

This commit is contained in:
Eric Traut 2021-09-16 21:22:31 -07:00
parent 209e9283ca
commit 14ad02575f
5 changed files with 52 additions and 14 deletions

View File

@ -130,7 +130,7 @@ The following settings control pyrights diagnostic output (warnings or errors
**reportInvalidTypeVarUse** [boolean or string, optional]: Generate or suppress diagnostics when a TypeVar is used inappropriately (e.g. if a TypeVar appears only once) within a generic function signature. The default value for this setting is 'none'.
**reportCallInDefaultInitializer** [boolean or string, optional]: Generate or suppress diagnostics for function calls within a default value initialization expression. Such calls can mask expensive operations that are performed at module initialization time. The default value for this setting is 'none'.
**reportCallInDefaultInitializer** [boolean or string, optional]: Generate or suppress diagnostics for function calls, list expressions, set expressions, or dictionary expressions within a default value initialization expression. Such calls can mask expensive operations that are performed at module initialization time. The default value for this setting is 'none'.
**reportUnnecessaryIsInstance** [boolean or string, optional]: Generate or suppress diagnostics for 'isinstance' or 'issubclass' calls where the result is statically determined to be always true. Such calls are often indicative of a programming error. The default value for this setting is 'none'.

View File

@ -31,6 +31,7 @@ import {
CaseNode,
ClassNode,
DelNode,
DictionaryNode,
ErrorNode,
ExceptNode,
FormatStringNode,
@ -45,6 +46,7 @@ import {
isExpressionNode,
LambdaNode,
ListComprehensionNode,
ListNode,
MatchNode,
MemberAccessNode,
ModuleNode,
@ -55,6 +57,7 @@ import {
ParseNodeType,
RaiseNode,
ReturnNode,
SetNode,
SliceNode,
StatementListNode,
StatementNode,
@ -568,14 +571,7 @@ export class Checker extends ParseTreeWalker {
override visitCall(node: CallNode): boolean {
this._validateIsInstanceCall(node);
if (ParseTreeUtils.isWithinDefaultParamInitializer(node) && !this._fileInfo.isStubFile) {
this._evaluator.addDiagnostic(
this._fileInfo.diagnosticRuleSet.reportCallInDefaultInitializer,
DiagnosticRule.reportCallInDefaultInitializer,
Localizer.Diagnostic.defaultValueContainsCall(),
node
);
}
this._validateIllegalDefaultParamInitializer(node);
if (
this._fileInfo.diagnosticRuleSet.reportUnusedCallResult !== 'none' ||
@ -638,6 +634,21 @@ export class Checker extends ParseTreeWalker {
return true;
}
override visitList(node: ListNode): boolean {
this._validateIllegalDefaultParamInitializer(node);
return true;
}
override visitSet(node: SetNode): boolean {
this._validateIllegalDefaultParamInitializer(node);
return true;
}
override visitDictionary(node: DictionaryNode): boolean {
this._validateIllegalDefaultParamInitializer(node);
return true;
}
override visitListComprehension(node: ListComprehensionNode): boolean {
this._scopedNodes.push(node);
return true;
@ -1120,6 +1131,19 @@ export class Checker extends ParseTreeWalker {
return false;
}
private _validateIllegalDefaultParamInitializer(node: ParseNode) {
if (this._fileInfo.diagnosticRuleSet.reportCallInDefaultInitializer !== 'none') {
if (ParseTreeUtils.isWithinDefaultParamInitializer(node) && !this._fileInfo.isStubFile) {
this._evaluator.addDiagnostic(
this._fileInfo.diagnosticRuleSet.reportCallInDefaultInitializer,
DiagnosticRule.reportCallInDefaultInitializer,
Localizer.Diagnostic.defaultValueContainsCall(),
node
);
}
}
}
// Determines whether the types of the two operands for an == or != operation
// have overlapping types.
private _validateComparisonTypes(node: BinaryOperationNode) {

View File

@ -62,7 +62,7 @@
"dataClassFieldWithDefault": "Fields without default values cannot appear after fields with default values",
"declaredReturnTypePartiallyUnknown": "Declared return type, \"{returnType}\", is partially unknown",
"declaredReturnTypeUnknown": "Declared return type is unknown",
"defaultValueContainsCall": "Function calls within default value initializer are not permitted",
"defaultValueContainsCall": "Function calls and mutable objects not allowed within parameter default value expression",
"defaultValueNotAllowed": "Parameter with \"*\" or \"**\" cannot have default value",
"defaultValueNotEllipsis": "Default values in stub files should be specified as \"...\"",
"delTargetExpr": "Expression cannot be deleted",

View File

@ -176,14 +176,14 @@ test('Mro3', () => {
test('DefaultInitializer1', () => {
const configOptions = new ConfigOptions('.');
// By default, optional diagnostics are ignored.
// By default, the reportCallInDefaultInitializer is disabled.
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['defaultInitializer1.py'], configOptions);
TestUtils.validateResults(analysisResults, 0);
// Turn on errors.
configOptions.diagnosticRuleSet.reportCallInDefaultInitializer = 'error';
analysisResults = TestUtils.typeAnalyzeSampleFiles(['defaultInitializer1.py'], configOptions);
TestUtils.validateResults(analysisResults, 2);
TestUtils.validateResults(analysisResults, 5);
});
test('UnnecessaryIsInstance1', () => {

View File

@ -1,8 +1,9 @@
# This sample tests the type analyzer's reporting of issues
# with parameter default initializer expressions.
# with parameter default initializer expressions. This is
# covered by the reportCallInDefaultInitializer diagnostic rule.
def foo(
def func1(
a=None,
# This should generate an error
b=dict(),
@ -10,3 +11,16 @@ def foo(
c=max(3, 4),
):
return 3
def func2(
a=None,
# This should generate an error
b={},
# This should generate an error
c=[],
# This should generate an error
d={1, 2, 3},
e=(1, 2, 3),
):
return 3