Added new diagnostic rule "reportFunctionMemberAccess" that reports an attempt to access, set or delete non-standard attributes of function objects.

This commit is contained in:
Eric Traut 2020-11-19 16:59:24 -08:00
parent 05e118a4b3
commit 1601a177cd
8 changed files with 89 additions and 6 deletions

View File

@ -50,6 +50,8 @@ The following settings control pyrights 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'.

View File

@ -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?

View File

@ -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,

View File

@ -18,6 +18,7 @@ export enum DiagnosticRule {
reportGeneralTypeIssues = 'reportGeneralTypeIssues',
reportPropertyTypeMismatch = 'reportPropertyTypeMismatch',
reportFunctionMemberAccess = 'reportFunctionMemberAccess',
reportMissingImports = 'reportMissingImports',
reportMissingModuleSource = 'reportMissingModuleSource',
reportMissingTypeStubs = 'reportMissingTypeStubs',

View File

@ -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

View File

@ -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']);

View File

@ -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.",

View File

@ -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",