From 1601a177cdedc35d4d61ed98c622bd0d3754e9a7 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Thu, 19 Nov 2020 16:59:24 -0800 Subject: [PATCH] Added new diagnostic rule "reportFunctionMemberAccess" that reports an attempt to access, set or delete non-standard attributes of function objects. --- docs/configuration.md | 2 ++ .../src/analyzer/typeEvaluator.ts | 31 +++++++++++++++---- .../src/common/configOptions.ts | 14 +++++++++ .../src/common/diagnosticRules.ts | 1 + .../src/tests/samples/function13.py | 18 +++++++++++ .../src/tests/typeEvaluator1.test.ts | 12 +++++++ packages/vscode-pyright/package.json | 11 +++++++ .../schemas/pyrightconfig.schema.json | 6 ++++ 8 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 packages/pyright-internal/src/tests/samples/function13.py diff --git a/docs/configuration.md b/docs/configuration.md index a811ff6a5..c45ed9901 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -50,6 +50,8 @@ The following settings control pyright’s diagnostic output (warnings or errors **reportPropertyTypeMismatch** [boolean or string, optional]: Generate or suppress diagnostics for properties where the type of the value passed to the setter is not assignable to the value returned by the getter. Such mismatches violate the intended use of properties, which are meant to act like variables. The default value for this setting is 'error'. +**reportFunctionMemberAccess** [boolean or string, optional]: Generate or suppress diagnostics for non-standard member accesses for functions. The default value for this setting is 'none'. + **reportMissingImports** [boolean or string, optional]: Generate or suppress diagnostics for imports that have no corresponding imported python file or type stub file. The default value for this setting is 'none', although pyright can do a much better job of static type checking if type stub files are provided for all imports. **reportMissingModuleSource** [boolean or string, optional]: Generate or suppress diagnostics for imports that have no corresponding source file. This happens when a type stub is found, but the module source file was not found, indicating that the code may fail at runtime when using this execution environment. Type checking will be done using the type stub. The default value for this setting is 'warning'. diff --git a/packages/pyright-internal/src/analyzer/typeEvaluator.ts b/packages/pyright-internal/src/analyzer/typeEvaluator.ts index b03526894..afe5fd1be 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluator.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluator.ts @@ -3019,6 +3019,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions EvaluatorFlags.None ); writeTypeCache(node.memberName, memberType.type); + writeTypeCache(node, memberType.type); break; } @@ -3630,9 +3631,15 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions case TypeCategory.Function: case TypeCategory.OverloadedFunction: { - // TODO - not yet sure what to do about members of functions, - // which have associated dictionaries. - type = AnyType.create(); + const functionObj = getBuiltInObject(node, 'function'); + + // The "__defaults__" member is not currently defined in the "function" + // class, so we'll special-case it here. + if (functionObj && memberName !== '__defaults__') { + type = getTypeFromMemberAccessWithBaseType(node, { type: functionObj, node }, usage, flags).type; + } else { + type = AnyType.create(); + } break; } @@ -3656,13 +3663,25 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions diag = usage.setExpectedTypeDiag; } + const isFunctionRule = + isFunction(baseType) || + isOverloadedFunction(baseType) || + (isObject(baseType) && ClassType.isBuiltIn(baseType.classType, 'function')); + const [ruleSet, rule] = isFunctionRule + ? [fileInfo.diagnosticRuleSet.reportFunctionMemberAccess, DiagnosticRule.reportFunctionMemberAccess] + : [fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues]; + addDiagnostic( - fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, - DiagnosticRule.reportGeneralTypeIssues, + ruleSet, + rule, diagMessage.format({ name: memberName, type: printType(baseType) }) + diag.getString(), node.memberName ); - type = UnknownType.create(); + + // If this is member access on a function, use "Any" so if the + // reportFunctionMemberAccess rule is disabled, we don't trigger + // additional reportUnknownMemberType diagnostics. + type = isFunctionRule ? AnyType.create() : UnknownType.create(); } // Should we specialize the class? diff --git a/packages/pyright-internal/src/common/configOptions.ts b/packages/pyright-internal/src/common/configOptions.ts index 2bcd7d470..2a5717fad 100644 --- a/packages/pyright-internal/src/common/configOptions.ts +++ b/packages/pyright-internal/src/common/configOptions.ts @@ -96,6 +96,9 @@ export interface DiagnosticRuleSet { // Report mismatch in types between property getter and setter? reportPropertyTypeMismatch: DiagnosticLevel; + // Report the use of unknown member accesses on function objects? + reportFunctionMemberAccess: DiagnosticLevel; + // Report missing imports? reportMissingImports: DiagnosticLevel; @@ -246,6 +249,7 @@ export function getDiagLevelDiagnosticRules() { return [ DiagnosticRule.reportGeneralTypeIssues, DiagnosticRule.reportPropertyTypeMismatch, + DiagnosticRule.reportFunctionMemberAccess, DiagnosticRule.reportMissingImports, DiagnosticRule.reportMissingModuleSource, DiagnosticRule.reportMissingTypeStubs, @@ -307,6 +311,7 @@ export function getOffDiagnosticRuleSet(): DiagnosticRuleSet { enableTypeIgnoreComments: true, reportGeneralTypeIssues: 'none', reportPropertyTypeMismatch: 'none', + reportFunctionMemberAccess: 'none', reportMissingImports: 'warning', reportMissingModuleSource: 'warning', reportMissingTypeStubs: 'none', @@ -364,6 +369,7 @@ export function getBasicDiagnosticRuleSet(): DiagnosticRuleSet { enableTypeIgnoreComments: true, reportGeneralTypeIssues: 'error', reportPropertyTypeMismatch: 'error', + reportFunctionMemberAccess: 'none', reportMissingImports: 'error', reportMissingModuleSource: 'warning', reportMissingTypeStubs: 'none', @@ -421,6 +427,7 @@ export function getStrictDiagnosticRuleSet(): DiagnosticRuleSet { enableTypeIgnoreComments: true, // Not overridden by strict mode reportGeneralTypeIssues: 'error', reportPropertyTypeMismatch: 'error', + reportFunctionMemberAccess: 'error', reportMissingImports: 'error', reportMissingModuleSource: 'warning', reportMissingTypeStubs: 'error', @@ -803,6 +810,13 @@ export class ConfigOptions { defaultSettings.reportPropertyTypeMismatch ), + // Read the "reportFunctionMemberAccess" entry. + reportFunctionMemberAccess: this._convertDiagnosticLevel( + configObj.reportFunctionMemberAccess, + DiagnosticRule.reportFunctionMemberAccess, + defaultSettings.reportFunctionMemberAccess + ), + // Read the "reportMissingImports" entry. reportMissingImports: this._convertDiagnosticLevel( configObj.reportMissingImports, diff --git a/packages/pyright-internal/src/common/diagnosticRules.ts b/packages/pyright-internal/src/common/diagnosticRules.ts index 37abf86ee..5beb0beec 100644 --- a/packages/pyright-internal/src/common/diagnosticRules.ts +++ b/packages/pyright-internal/src/common/diagnosticRules.ts @@ -18,6 +18,7 @@ export enum DiagnosticRule { reportGeneralTypeIssues = 'reportGeneralTypeIssues', reportPropertyTypeMismatch = 'reportPropertyTypeMismatch', + reportFunctionMemberAccess = 'reportFunctionMemberAccess', reportMissingImports = 'reportMissingImports', reportMissingModuleSource = 'reportMissingModuleSource', reportMissingTypeStubs = 'reportMissingTypeStubs', diff --git a/packages/pyright-internal/src/tests/samples/function13.py b/packages/pyright-internal/src/tests/samples/function13.py new file mode 100644 index 000000000..571360bb0 --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/function13.py @@ -0,0 +1,18 @@ +# This sample tests the reportFunctionMemberAccess diagnostic rule. + + +def func1(): + pass + + +a = func1.__annotations__ +b = func1.__class__ + +# This should generate an error +c = func1.bar + +# This should generate an error +func1.baz = 3 + +# This should generate an error +del func1.baz diff --git a/packages/pyright-internal/src/tests/typeEvaluator1.test.ts b/packages/pyright-internal/src/tests/typeEvaluator1.test.ts index 00b92834e..2e283d235 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator1.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator1.test.ts @@ -518,6 +518,18 @@ test('Function12', () => { TestUtils.validateResults(analysisResults, 0, 0, 0, 2); }); +test('Function13', () => { + // Analyze with reportFunctionMemberAccess disabled. + const analysisResult1 = TestUtils.typeAnalyzeSampleFiles(['function13.py']); + TestUtils.validateResults(analysisResult1, 0); + + // Analyze with reportFunctionMemberAccess enabled. + const configOptions = new ConfigOptions('.'); + configOptions.diagnosticRuleSet.reportFunctionMemberAccess = 'error'; + const analysisResult2 = TestUtils.typeAnalyzeSampleFiles(['function13.py'], configOptions); + TestUtils.validateResults(analysisResult2, 3); +}); + test('Annotations1', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['annotations1.py']); diff --git a/packages/vscode-pyright/package.json b/packages/vscode-pyright/package.json index 90d64ef22..c39ca8017 100644 --- a/packages/vscode-pyright/package.json +++ b/packages/vscode-pyright/package.json @@ -136,6 +136,17 @@ "error" ] }, + "reportFunctionMemberAccess": { + "type": "string", + "description": "Diagnostics for member accesses on functions.", + "default": "none", + "enum": [ + "none", + "information", + "warning", + "error" + ] + }, "reportMissingImports": { "type": "string", "description": "Diagnostics for imports that have no corresponding imported python file or type stub file.", diff --git a/packages/vscode-pyright/schemas/pyrightconfig.schema.json b/packages/vscode-pyright/schemas/pyrightconfig.schema.json index b0aa5a3dd..f46694d1b 100644 --- a/packages/vscode-pyright/schemas/pyrightconfig.schema.json +++ b/packages/vscode-pyright/schemas/pyrightconfig.schema.json @@ -135,6 +135,12 @@ "title": "Controls reporting of property getter/setter type mismatches", "default": "error" }, + "reportFunctionMemberAccess": { + "$id": "#/properties/reportFunctionMemberAccess", + "$ref": "#/definitions/diagnostic", + "title": "Controls reporting of member accesses on function objects", + "default": "none" + }, "reportMissingImports": { "$id": "#/properties/reportMissingImports", "$ref": "#/definitions/diagnostic",