Added "--skipunannotated" option for command-line version of pyright. If specified, pyright skips type analysis of functions and methods that have no parameter or return type annotations. Return types of functions are also never inferred from the function implementation. This matches the default behavior of mypy and allows for more efficient analysis of complex code bases that are only partially annotated.

This commit is contained in:
Eric Traut 2021-10-22 23:11:03 -07:00
parent 94a700b562
commit ef773d2407
8 changed files with 67 additions and 25 deletions

View File

@ -15,6 +15,7 @@ Pyright can be run as either a VS Code extension or as a node-based command-line
| -p, --project `<FILE OR DIRECTORY>` | Use the configuration file at this location |
| --pythonplatform `<PLATFORM>` | Analyze for platform (Darwin, Linux, Windows) |
| --pythonversion `<VERSION>` | Analyze for version (3.3, 3.4, etc.) |
| --skipunannotated | Skip type analysis of unannotated functions? |
| --stats | Print detailed performance stats |
| -t, --typeshed-path `<DIRECTORY>` | Use typeshed type stubs at this location (2) |
| -v, --venv-path `<DIRECTORY>` | Directory that contains virtual environments (3) |

View File

@ -1887,6 +1887,15 @@ export function getFullStatementRange(statementNode: ParseNode, tokenizerOutput:
return { start: range.start, end: { line: range.end.line + 1, character: 0 } };
}
export function isUnannotatedFunction(node: FunctionNode) {
return (
node.returnTypeAnnotation === undefined &&
node.parameters.every(
(param) => param.typeAnnotation === undefined && param.typeAnnotationComment === undefined
)
);
}
function _getEndPositionIfMultipleStatementsAreOnSameLine(
range: Range,
tokenPosition: number,

View File

@ -742,6 +742,7 @@ export class Program {
printTypeFlags: Program._getPrintTypeFlags(this._configOptions),
logCalls: this._configOptions.logTypeEvaluationTime,
minimumLoggingThreshold: this._configOptions.typeEvaluationTimeThreshold,
analyzeUnannotatedFunctions: this._configOptions.analyzeUnannotatedFunctions,
},
this._logTracker,
this._configOptions.logTypeEvaluationTime

View File

@ -622,6 +622,8 @@ export class AnalyzerService {
configOptions.applyDiagnosticOverrides(commandLineOptions.diagnosticSeverityOverrides);
}
configOptions.analyzeUnannotatedFunctions = commandLineOptions.analyzeUnannotatedFunctions ?? true;
const reportDuplicateSetting = (settingName: string, configValue: number | string | boolean) => {
const settingSource = commandLineOptions.fromVsCodeExtension
? 'the client settings'

View File

@ -506,6 +506,7 @@ export interface EvaluatorOptions {
printTypeFlags: TypePrinter.PrintTypeFlags;
logCalls: boolean;
minimumLoggingThreshold: number;
analyzeUnannotatedFunctions: boolean;
}
export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions: EvaluatorOptions): TypeEvaluator {
@ -3193,6 +3194,17 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
let isIncomplete = false;
const allowForwardReferences = (flags & EvaluatorFlags.AllowForwardReferences) !== 0 || fileInfo.isStubFile;
if (!evaluatorOptions.analyzeUnannotatedFunctions) {
const containingFunction = ParseTreeUtils.getEnclosingFunction(node);
if (containingFunction && ParseTreeUtils.isUnannotatedFunction(containingFunction)) {
return {
node,
type: AnyType.create(),
isIncomplete: false,
};
}
}
// Look for the scope that contains the value definition and
// see if it has a declared type.
const symbolWithScope = lookUpSymbolRecursive(node, name, !allowForwardReferences);
@ -17270,29 +17282,32 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
} else if (type.details.declaration) {
const functionNode = type.details.declaration.node;
const codeFlowComplexity = AnalyzerNodeInfo.getCodeFlowComplexity(functionNode);
// Skip return type inference if we are in "skip unannotated function" mode.
if (evaluatorOptions.analyzeUnannotatedFunctions) {
const codeFlowComplexity = AnalyzerNodeInfo.getCodeFlowComplexity(functionNode);
// For very complex functions that have no annotated parameter types,
// don't attempt to infer the return type because it can be extremely
// expensive.
const parametersAreAnnotated =
type.details.parameters.length <= 1 ||
type.details.parameters.some((param) => param.hasDeclaredType);
// For very complex functions that have no annotated parameter types,
// don't attempt to infer the return type because it can be extremely
// expensive.
const parametersAreAnnotated =
type.details.parameters.length <= 1 ||
type.details.parameters.some((param) => param.hasDeclaredType);
if (parametersAreAnnotated || codeFlowComplexity < maxReturnTypeInferenceCodeFlowComplexity) {
// Temporarily disable speculative mode while we
// lazily evaluate the return type.
disableSpeculativeMode(() => {
returnType = inferFunctionReturnType(functionNode, FunctionType.isAbstractMethod(type));
});
if (parametersAreAnnotated || codeFlowComplexity < maxReturnTypeInferenceCodeFlowComplexity) {
// Temporarily disable speculative mode while we
// lazily evaluate the return type.
disableSpeculativeMode(() => {
returnType = inferFunctionReturnType(functionNode, FunctionType.isAbstractMethod(type));
});
// Do we need to wrap this in an awaitable?
if (returnType && FunctionType.isWrapReturnTypeInAwait(type)) {
returnType = createAwaitableReturnType(
functionNode,
returnType,
!!type.details.declaration?.isGenerator
);
// Do we need to wrap this in an awaitable?
if (returnType && FunctionType.isWrapReturnTypeInAwait(type)) {
returnType = createAwaitableReturnType(
functionNode,
returnType,
!!type.details.declaration?.isGenerator
);
}
}
}
}
@ -17309,6 +17324,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
// params, try to analyze the function with the provided argument types and
// attempt to do a better job at inference.
if (
evaluatorOptions.analyzeUnannotatedFunctions &&
isPartlyUnknown(returnType) &&
FunctionType.hasUnannotatedParams(type) &&
!FunctionType.isStubDefinition(type) &&

View File

@ -118,12 +118,15 @@ export class CommandLineOptions {
// Use indexing.
indexing?: boolean | undefined;
// Use type evaluator call tracking
// Use type evaluator call tracking.
logTypeEvaluationTime = false;
// Minimum threshold for type eval logging
// Minimum threshold for type eval logging.
typeEvaluationTimeThreshold = 50;
// Run ambient analysis
// Run ambient analysis.
enableAmbientAnalysis = true;
// Analyze functions and methods that have no type annotations?
analyzeUnannotatedFunctions = true;
}

View File

@ -657,6 +657,10 @@ export class ConfigOptions {
// Was this config initialized from JSON (pyrightconfig/pyproject)?
initializedFromJson = false;
// Should we skip analysis of all functions and methods that have
// no parameter ore return type annotations?
analyzeUnannotatedFunctions = true;
//---------------------------------------------------------------
// Diagnostics Rule Set

View File

@ -131,6 +131,7 @@ async function processArgs(): Promise<ExitStatus> {
{ name: 'project', alias: 'p', type: String },
{ name: 'pythonplatform', type: String },
{ name: 'pythonversion', type: String },
{ name: 'skipunannotated', type: Boolean },
{ name: 'stats' },
{ name: 'typeshed-path', alias: 't', type: String },
{ name: 'venv-path', alias: 'v', type: String },
@ -177,7 +178,7 @@ async function processArgs(): Promise<ExitStatus> {
}
if (args['verifytypes'] !== undefined) {
const incompatibleArgs = ['watch', 'stats', 'createstub', 'dependencies'];
const incompatibleArgs = ['watch', 'stats', 'createstub', 'dependencies', 'skipunannotated'];
for (const arg of incompatibleArgs) {
if (args[arg] !== undefined) {
console.error(`'verifytypes' option cannot be used with '${arg}' option`);
@ -187,7 +188,7 @@ async function processArgs(): Promise<ExitStatus> {
}
if (args.createstub) {
const incompatibleArgs = ['watch', 'stats', 'verifytypes', 'dependencies'];
const incompatibleArgs = ['watch', 'stats', 'verifytypes', 'dependencies', 'skipunannotated'];
for (const arg of incompatibleArgs) {
if (args[arg] !== undefined) {
console.error(`'createstub' option cannot be used with '${arg}' option`);
@ -243,12 +244,16 @@ async function processArgs(): Promise<ExitStatus> {
options.typeStubTargetImportName = args.createstub;
}
options.analyzeUnannotatedFunctions = !args.skipAnalysisForUnannotatedFunctions;
if (args.verbose) {
options.verboseOutput = true;
}
if (args.lib) {
options.useLibraryCodeForTypes = true;
}
options.checkOnlyOpenFiles = false;
const treatWarningsAsErrors = !!args.warnings;
@ -603,6 +608,7 @@ function printUsage() {
' -p,--project <FILE OR DIRECTORY> Use the configuration file at this location\n' +
' --pythonplatform <PLATFORM> Analyze for a specific platform (Darwin, Linux, Windows)\n' +
' --pythonversion <VERSION> Analyze for a specific version (3.3, 3.4, etc.)\n' +
' --skipunannotated Do not analyze functions and methods with no type annotations\n' +
' --stats Print detailed performance stats\n' +
' -t,--typeshed-path <DIRECTORY> Use typeshed type stubs at this location\n' +
' -v,--venv-path <DIRECTORY> Directory that contains virtual environments\n' +