diff --git a/client/schemas/pyrightconfig.schema.json b/client/schemas/pyrightconfig.schema.json index fff654ba9..368c442da 100644 --- a/client/schemas/pyrightconfig.schema.json +++ b/client/schemas/pyrightconfig.schema.json @@ -97,6 +97,12 @@ "title": "Controls reporting of attempts to call a variable with Optional type", "default": "none" }, + "reportOptionalIterable": { + "$id": "#/properties/reportOptionalIterable", + "$ref": "#/definitions/diagnostic", + "title": "Controls reporting of attempts to use an Optional type as an iterable value", + "default": "none" + }, "reportUntypedFunctionDecorator": { "$id": "#/properties/reportUntypedFunctionDecorator", "$ref": "#/definitions/diagnostic", diff --git a/docs/configuration.md b/docs/configuration.md index 8f94f796f..633051954 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -40,6 +40,8 @@ The following settings control pyright's diagnostic output (warnings or errors). **reportOptionalCall** [boolean or string, optional]: Generate or suppress diagnostics for an attempt to call a variable with an Optional type. The default value for this setting is 'none'. +**reportOptionalIterable** [boolean or string, optional]: Generate or suppress diagnostics for an attempt to use an Optional type as an iterable value (e.g. within a `for` statement). The default value for this setting is 'none'. + **reportUntypedFunctionDecorator** [boolean or string, optional]: Generate or suppress diagnostics for function decorators that have no type annotations. These obscure the function type, defeating many type analysis features. **reportUntypedClassDecorator** [boolean or string, optional]: Generate or suppress diagnostics for class decorators that have no type annotations. These obscure the class type, defeating many type analysis features. diff --git a/server/src/analyzer/expressionEvaluator.ts b/server/src/analyzer/expressionEvaluator.ts index ea6b2204a..7bac6ede0 100644 --- a/server/src/analyzer/expressionEvaluator.ts +++ b/server/src/analyzer/expressionEvaluator.ts @@ -235,8 +235,11 @@ export class ExpressionEvaluator { const iterMethodName = isAsync ? '__aiter__' : '__iter__'; const nextMethodName = isAsync ? '__anext__' : '__next__'; - // TODO - tighten this up, perhaps with a configuration switch. - if (type instanceof UnionType) { + if (type instanceof UnionType && type.getTypes().some(t => t instanceof NoneType)) { + this._addDiagnostic( + this._configOptions.reportOptionalIterable, + `Object of type 'None' cannot be used as iterable value`, + errorNode); type = type.removeOptional(); } diff --git a/server/src/common/configOptions.ts b/server/src/common/configOptions.ts index 6ad84e30c..331809b25 100644 --- a/server/src/common/configOptions.ts +++ b/server/src/common/configOptions.ts @@ -94,6 +94,9 @@ export class ConfigOptions { // Report attempts to call a Optional type? reportOptionalCall: DiagnosticLevel = 'none'; + // Report attempts to use an Optional type as an iterable? + reportOptionalIterable: DiagnosticLevel = 'none'; + // Report untyped function decorators that obscure the function type? reportUntypedFunctionDecorator: DiagnosticLevel = 'none'; @@ -218,6 +221,10 @@ export class ConfigOptions { this.reportOptionalCall = this._convertDiagnosticLevel( configObj.reportOptionalCall, 'reportOptionalCall', 'none'); + // Read the "reportOptionalIterable" entry. + this.reportOptionalIterable = this._convertDiagnosticLevel( + configObj.reportOptionalIterable, 'reportOptionalIterable', 'none'); + // Read the "reportUntypedFunctionDecorator" entry. this.reportUntypedFunctionDecorator = this._convertDiagnosticLevel( configObj.reportUntypedFunctionDecorator, 'reportUntypedFunctionDecorator', 'none'); diff --git a/server/src/tests/samples/optional1.py b/server/src/tests/samples/optional1.py index 47b87e5ca..db16d7b8c 100644 --- a/server/src/tests/samples/optional1.py +++ b/server/src/tests/samples/optional1.py @@ -36,3 +36,8 @@ if 1: # this should generate an error. c[2] + +# If "reportOptionalIterable" is enabled, +# this should generate an error. +for val in c: + pass diff --git a/server/src/tests/typeAnalyzer.test.ts b/server/src/tests/typeAnalyzer.test.ts index 3ef4376f3..d96d4e221 100644 --- a/server/src/tests/typeAnalyzer.test.ts +++ b/server/src/tests/typeAnalyzer.test.ts @@ -198,15 +198,17 @@ test('Optional1', () => { configOptions.reportOptionalSubscript = 'warning'; configOptions.reportOptionalMemberAccess = 'warning'; configOptions.reportOptionalCall = 'warning'; + configOptions.reportOptionalIterable = 'warning'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['optional1.py'], configOptions); - validateResults(analysisResults, 0, 3); + validateResults(analysisResults, 0, 4); // Turn on errors. configOptions.reportOptionalSubscript = 'error'; configOptions.reportOptionalMemberAccess = 'error'; configOptions.reportOptionalCall = 'error'; + configOptions.reportOptionalIterable = 'error'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['optional1.py'], configOptions); - validateResults(analysisResults, 3); + validateResults(analysisResults, 4); }); test('Tuples1', () => {