Added new config switch "reportUnknownParameter" to control reporting of unknown input and return parameter types.

This commit is contained in:
Eric Traut 2019-04-28 01:51:22 -07:00
parent a442c60c95
commit 93595c1720
7 changed files with 97 additions and 11 deletions

View File

@ -151,6 +151,12 @@
"title": "Controls reporting of invalid escape sequences used within string literals",
"default": "none"
},
"reportUnknownParameter": {
"$id": "#/properties/reportUnknownParameter",
"$ref": "#/definitions/diagnostic",
"title": "Controls reporting input and return parameters whose types are unknown",
"default": "none"
},
"pythonVersion": {
"$id": "#/properties/pythonVersion",
"type": "string",

View File

@ -58,6 +58,8 @@ The following settings control pyright's diagnostic output (warnings or errors).
**reportInvalidStringEscapeSequence** [boolean or string, optional]: Generate or suppress diagnostics for invalid escape sequences used within string literals. The Python specification indicates that such sequences will generate a syntax error in future versions. The default value for this setting is 'warning'.
**reportUnknownParameter** [boolean or string, optional]: Generate or suppress diagnostics for input or return parameters that have an unknown type. The default value for this setting is 'none'.
## Execution Environment Options
Pyright allows multiple “execution environments” to be defined for different portions of your source tree. For example, a subtree may be designed to run with different import search paths or a different version of the python interpreter than the rest of the source base.

View File

@ -245,6 +245,7 @@ export abstract class SemanticAnalyzer extends ParseTreeWalker {
let functionFlags = FunctionTypeFlags.None;
if (node.name.nameToken.value === '__new__') {
functionFlags |= FunctionTypeFlags.StaticMethod;
functionFlags |= FunctionTypeFlags.ConstructorMethod;
functionFlags &= ~FunctionTypeFlags.InstanceMethod;
}

View File

@ -298,7 +298,11 @@ export class TypeAnalyzer extends ParseTreeWalker {
}
this.walk(param.typeAnnotation);
} else if (index === 0 && (functionType.isInstanceMethod() || functionType.isClassMethod())) {
} else if (index === 0 && (
functionType.isInstanceMethod() ||
functionType.isClassMethod() ||
functionType.isConstructorMethod())) {
// Specify type of "self" or "cls" parameter for instance or class methods
// if the type is not explicitly provided.
if (containingClassType) {
@ -314,7 +318,7 @@ export class TypeAnalyzer extends ParseTreeWalker {
if (functionType.setParameterType(index, new ObjectType(specializedClassType))) {
this._setAnalysisChanged();
}
} else if (functionType.isClassMethod()) {
} else if (functionType.isClassMethod() || functionType.isConstructorMethod()) {
// For class methods, the cls parameter is allowed to skip the
// abstract class test because the caller is possibly passing
// in a non-abstract subclass.
@ -327,6 +331,13 @@ export class TypeAnalyzer extends ParseTreeWalker {
}
}
}
} else {
// There is no annotation, and we can't infer the type.
if (param.name && param.category === ParameterCategory.Simple) {
this._addDiagnostic(this._fileInfo.configOptions.reportUnknownParameter,
`Type of '${ param.name.nameToken.value }' is unknown`,
param.name);
}
}
});
@ -337,11 +348,26 @@ export class TypeAnalyzer extends ParseTreeWalker {
}
this.walk(node.returnTypeAnnotation);
} else if (this._fileInfo.isStubFile) {
// If a return type annotation is missing in a stub file, assume
// it's an "unknown" type. In normal source files, we can infer the
// type from the implementation.
functionType.setDeclaredReturnType(UnknownType.create());
} else {
let inferredReturnType: Type = UnknownType.create();
if (this._fileInfo.isStubFile) {
// If a return type annotation is missing in a stub file, assume
// it's an "unknown" type. In normal source files, we can infer the
// type from the implementation.
functionType.setDeclaredReturnType(inferredReturnType);
} else {
inferredReturnType = functionType.getInferredReturnType().getType();
}
if (inferredReturnType instanceof UnknownType) {
this._addDiagnostic(this._fileInfo.configOptions.reportUnknownParameter,
`Return type is unknown`, node.name);
} else if (TypeUtils.containsUnknown(inferredReturnType)) {
this._addDiagnostic(this._fileInfo.configOptions.reportUnknownParameter,
`Return type '${ inferredReturnType.asString() }' is partially unknown`,
node.name);
}
}
let functionScope = this._enterScope(node, () => {

View File

@ -1045,6 +1045,45 @@ export class TypeUtils {
this._getMembersForClassRecursive(classType, symbolTable, includeInstanceVars);
}
static containsUnknown(type: Type, recursionCount = 0): boolean {
if (recursionCount > MaxTypeRecursion) {
return false;
}
if (type instanceof UnknownType) {
return true;
}
if (type instanceof UnionType) {
for (const subtype of type.getTypes()) {
if (this.containsUnknown(subtype, recursionCount + 1)) {
return true;
}
}
return false;
}
if (type instanceof ObjectType) {
return this.containsUnknown(type.getClassType(), recursionCount + 1);
}
if (type instanceof ClassType) {
const typeArgs = type.getTypeArguments();
if (typeArgs) {
for (const argType of typeArgs) {
if (this.containsUnknown(argType, recursionCount + 1)) {
return true;
}
}
}
return false;
}
return false;
}
private static _getMembersForClassRecursive(classType: ClassType,
symbolTable: SymbolTable, includeInstanceVars: boolean,
recursionCount = 0) {

View File

@ -619,10 +619,11 @@ export interface FunctionParameter {
export enum FunctionTypeFlags {
None = 0,
InstanceMethod = 1,
ClassMethod = 2,
StaticMethod = 4,
AbstractMethod = 8,
DisableDefaultChecks = 16
ConstructorMethod = 2,
ClassMethod = 4,
StaticMethod = 8,
AbstractMethod = 16,
DisableDefaultChecks = 32
}
interface FunctionDetails {
@ -711,6 +712,10 @@ export class FunctionType extends Type {
this._functionDetails.flags |= FunctionTypeFlags.InstanceMethod;
}
isConstructorMethod(): boolean {
return (this._functionDetails.flags & FunctionTypeFlags.ConstructorMethod) !== 0;
}
isStaticMethod(): boolean {
return (this._functionDetails.flags & FunctionTypeFlags.StaticMethod) !== 0;
}

View File

@ -125,6 +125,9 @@ export class ConfigOptions {
// Report usage of invalid escape sequences in string literals?
reportInvalidStringEscapeSequence: DiagnosticLevel = 'warning';
// Report usage of unknown input or return parameters?
reportUnknownParameter: DiagnosticLevel = 'none';
//---------------------------------------------------------------
// Parsing and Import Resolution Settings
@ -285,6 +288,10 @@ export class ConfigOptions {
this.reportInvalidStringEscapeSequence = this._convertDiagnosticLevel(
configObj.reportInvalidStringEscapeSequence, 'reportInvalidStringEscapeSequence', 'warning');
// Read the "reportUnknownParameter" entry.
this.reportUnknownParameter = this._convertDiagnosticLevel(
configObj.reportUnknownParameter, 'reportUnknownParameter', 'none');
// Read the "venvPath".
this.venvPath = undefined;
if (configObj.venvPath !== undefined) {