From 9a08af039fe763894e27e28017e279f0523d7ae8 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Thu, 18 Apr 2019 19:59:59 -0700 Subject: [PATCH] Added "reportOptionalContextManager" config switch. --- client/schemas/pyrightconfig.schema.json | 6 ++++++ docs/configuration.md | 2 ++ server/src/analyzer/typeAnalyzer.ts | 11 ++++++++++- server/src/common/configOptions.ts | 7 +++++++ server/src/tests/samples/optional1.py | 12 ++++++++++++ server/src/tests/typeAnalyzer.test.ts | 6 ++++-- 6 files changed, 41 insertions(+), 3 deletions(-) diff --git a/client/schemas/pyrightconfig.schema.json b/client/schemas/pyrightconfig.schema.json index 6d42ce1fd..ab449aa04 100644 --- a/client/schemas/pyrightconfig.schema.json +++ b/client/schemas/pyrightconfig.schema.json @@ -109,6 +109,12 @@ "title": "Controls reporting of attempts to use an Optional type as an iterable value", "default": "none" }, + "reportOptionalContextManager": { + "$id": "#/properties/reportOptionalContextManager", + "$ref": "#/definitions/diagnostic", + "title": "Controls reporting of attempts to use an Optional type as a parameter to a with statement", + "default": "none" + }, "reportUntypedFunctionDecorator": { "$id": "#/properties/reportUntypedFunctionDecorator", "$ref": "#/definitions/diagnostic", diff --git a/docs/configuration.md b/docs/configuration.md index ade1617cf..ee94eaee8 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -44,6 +44,8 @@ The following settings control pyright's diagnostic output (warnings or errors). **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'. +**reportOptionalContextManager** [boolean or string, optional]: Generate or suppress diagnostics for an attempt to use an Optional type as a context manager (as a parameter to a `with` 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/typeAnalyzer.ts b/server/src/analyzer/typeAnalyzer.ts index bd1e31f17..1812beab6 100644 --- a/server/src/analyzer/typeAnalyzer.ts +++ b/server/src/analyzer/typeAnalyzer.ts @@ -623,7 +623,16 @@ export class TypeAnalyzer extends ParseTreeWalker { }); node.withItems.forEach(item => { - const exprType = this._getTypeOfExpression(item.expression); + let exprType = this._getTypeOfExpression(item.expression); + + if (exprType instanceof UnionType && exprType.getTypes().some(t => t instanceof NoneType)) { + this._addDiagnostic( + this._fileInfo.configOptions.reportOptionalContextManager, + `Object of type 'None' cannot be used with 'with'`, + node); + exprType = exprType.removeOptional(); + } + const enterMethodName = node.isAsync ? '__aenter__' : '__enter__'; const scopedType = TypeUtils.doForSubtypes(exprType, subtype => { diff --git a/server/src/common/configOptions.ts b/server/src/common/configOptions.ts index ebb4236bb..b425668c2 100644 --- a/server/src/common/configOptions.ts +++ b/server/src/common/configOptions.ts @@ -100,6 +100,9 @@ export class ConfigOptions { // Report attempts to use an Optional type as an iterable? reportOptionalIterable: DiagnosticLevel = 'none'; + // Report attempts to use an Optional type in a "with" statement? + reportOptionalContextManager: DiagnosticLevel = 'none'; + // Report untyped function decorators that obscure the function type? reportUntypedFunctionDecorator: DiagnosticLevel = 'none'; @@ -245,6 +248,10 @@ export class ConfigOptions { this.reportOptionalIterable = this._convertDiagnosticLevel( configObj.reportOptionalIterable, 'reportOptionalIterable', 'none'); + // Read the "reportOptionalContextManager" entry. + this.reportOptionalContextManager = this._convertDiagnosticLevel( + configObj.reportOptionalContextManager, 'reportOptionalContextManager', '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 db16d7b8c..bccee9cb0 100644 --- a/server/src/tests/samples/optional1.py +++ b/server/src/tests/samples/optional1.py @@ -7,6 +7,9 @@ class Foo: def do_stuff(self): pass + def __enter__(self): + return 3 + a = None if 1: a = Foo() @@ -41,3 +44,12 @@ c[2] # this should generate an error. for val in c: pass + +# If "reportOptionalContextManager" is enabled, +# this should generate an error. +cm = None +if 1: + cm = Foo() +with cm as val: + pass + diff --git a/server/src/tests/typeAnalyzer.test.ts b/server/src/tests/typeAnalyzer.test.ts index ec113d170..08539db44 100644 --- a/server/src/tests/typeAnalyzer.test.ts +++ b/server/src/tests/typeAnalyzer.test.ts @@ -199,16 +199,18 @@ test('Optional1', () => { configOptions.reportOptionalMemberAccess = 'warning'; configOptions.reportOptionalCall = 'warning'; configOptions.reportOptionalIterable = 'warning'; + configOptions.reportOptionalContextManager = 'warning'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['optional1.py'], configOptions); - validateResults(analysisResults, 0, 4); + validateResults(analysisResults, 0, 5); // Turn on errors. configOptions.reportOptionalSubscript = 'error'; configOptions.reportOptionalMemberAccess = 'error'; configOptions.reportOptionalCall = 'error'; configOptions.reportOptionalIterable = 'error'; + configOptions.reportOptionalContextManager = 'error'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['optional1.py'], configOptions); - validateResults(analysisResults, 4); + validateResults(analysisResults, 5); }); test('Private1', () => {