Exposed new configuration setting analyzeUnannotatedFunctions which corresponds to the --skipunannotated command-line option. Added an information diagnostic for skipped functions. This addresses https://github.com/microsoft/pyright/issues/4303.

This commit is contained in:
Eric Traut 2022-12-08 20:05:32 -08:00
parent 1d6a5f385e
commit 5f48e39671
12 changed files with 71 additions and 23 deletions

View File

@ -50,6 +50,8 @@ The following settings control pyrights diagnostic output (warnings or errors
<a name="strictSetInference"></a> **strictSetInference** [boolean]: When inferring the type of a set, use strict type assumptions. For example, the expression `{1, 'a', 3.4}` could be inferred to be of type `set[Any]` or `set[int | str | float]`. If this setting is true, it will use the latter (stricter) type. The default value for this setting is 'false'.
<a name="analyzeUnannotatedFunctions"></a> **analyzeUnannotatedFunctions** [boolean]: Analyze and report errors for functions and methods that have no type annotations for input parameters or return types. The default value for this setting is 'true'.
<a name="strictParameterNoneValue"></a> **strictParameterNoneValue** [boolean]: PEP 484 indicates that when a function parameter is assigned a default value of None, its type should implicitly be Optional even if the explicit type is not. When enabled, this rule requires that parameter type annotations use Optional explicitly in this case. The default value for this setting is 'true'.
<a name="enableTypeIgnoreComments"></a> **enableTypeIgnoreComments** [boolean]: PEP 484 defines support for "# type: ignore" comments. This switch enables or disables support for these comments. The default value for this setting is 'true'. This does not affect "# pyright: ignore" comments.
@ -293,6 +295,7 @@ The following table lists the default severity levels for each diagnostic rule w
| strictListInference | false | false | true |
| strictDictionaryInference | false | false | true |
| strictSetInference | false | false | true |
| analyzeUnannotatedFunctions | true | true | true |
| strictParameterNoneValue | true | true | true |
| enableTypeIgnoreComments | true | true | true |
| reportMissingModuleSource | "warning" | "warning" | "warning" |

View File

@ -414,6 +414,15 @@ export class Checker extends ParseTreeWalker {
this.walk(node.typeParameters);
}
if (!this._fileInfo.diagnosticRuleSet.analyzeUnannotatedFunctions && !this._fileInfo.isStubFile) {
if (ParseTreeUtils.isUnannotatedFunction(node)) {
this._evaluator.addInformation(
Localizer.Diagnostic.unannotatedFunctionSkipped().format({ name: node.name.value }),
node.name
);
}
}
const functionTypeResult = this._evaluator.getTypeOfFunction(node);
const containingClassNode = ParseTreeUtils.getEnclosingClass(node, /* stopAtFunction */ true);

View File

@ -886,7 +886,6 @@ export class Program {
printTypeFlags: Program._getPrintTypeFlags(this._configOptions),
logCalls: this._configOptions.logTypeEvaluationTime,
minimumLoggingThreshold: this._configOptions.typeEvaluationTimeThreshold,
analyzeUnannotatedFunctions: this._configOptions.analyzeUnannotatedFunctions,
evaluateUnknownImportsAsAny: !!this._configOptions.evaluateUnknownImportsAsAny,
verifyTypeCacheEvaluatorFlags: !!this._configOptions.internalTestMode,
},

View File

@ -761,7 +761,11 @@ export class AnalyzerService {
configOptions.applyDiagnosticOverrides(commandLineOptions.diagnosticSeverityOverrides);
}
configOptions.analyzeUnannotatedFunctions = commandLineOptions.analyzeUnannotatedFunctions ?? true;
// Override the analyzeUnannotatedFunctions setting based on the command-line setting.
if (commandLineOptions.analyzeUnannotatedFunctions !== undefined) {
configOptions.diagnosticRuleSet.analyzeUnannotatedFunctions =
commandLineOptions.analyzeUnannotatedFunctions;
}
const reportDuplicateSetting = (settingName: string, configValue: number | string | boolean) => {
const settingSource = commandLineOptions.fromVsCodeExtension

View File

@ -567,7 +567,6 @@ export interface EvaluatorOptions {
printTypeFlags: TypePrinter.PrintTypeFlags;
logCalls: boolean;
minimumLoggingThreshold: number;
analyzeUnannotatedFunctions: boolean;
evaluateUnknownImportsAsAny: boolean;
verifyTypeCacheEvaluatorFlags: boolean;
}
@ -2935,6 +2934,21 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
return undefined;
}
// Should we suppress this diagnostic because it's within an unannotated function?
const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
if (!fileInfo.diagnosticRuleSet.analyzeUnannotatedFunctions) {
const containingFunction = ParseTreeUtils.getEnclosingFunction(node);
// Is the target node within the body of the function? If so, suppress the diagnostic.
if (
containingFunction &&
ParseTreeUtils.isUnannotatedFunction(containingFunction) &&
ParseTreeUtils.isNodeContainedWithin(node, containingFunction.suite)
) {
return undefined;
}
}
const diagnostic = addDiagnosticWithSuppressionCheck(diagLevel, message, node, range);
if (diagnostic) {
diagnostic.setRule(rule);
@ -4059,16 +4073,6 @@ 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 {
type: AnyType.create(),
isIncomplete: false,
};
}
}
// Does this name refer to a PEP 695-style type parameter?
const typeParamSymbol = AnalyzerNodeInfo.getTypeParameterSymbol(node);
if (typeParamSymbol) {
@ -4109,6 +4113,19 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
}
symbol = symbolWithScope.symbol;
setSymbolAccessed(fileInfo, symbol, node);
// If we're not supposed to be analyzing this function, skip the remaining work
// to determine the name's type. Simply evaluate its type as Any.
if (!fileInfo.diagnosticRuleSet.analyzeUnannotatedFunctions) {
const containingFunction = ParseTreeUtils.getEnclosingFunction(node);
if (containingFunction && ParseTreeUtils.isUnannotatedFunction(containingFunction)) {
return {
type: AnyType.create(),
isIncomplete: false,
};
}
}
// Get the effective type (either the declared type or the inferred type).
// If we're using code flow analysis, pass the usage node so we consider
@ -4196,8 +4213,6 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
// Detect, report, and fill in missing type arguments if appropriate.
type = reportMissingTypeArguments(node, type, flags);
setSymbolAccessed(fileInfo, symbol, node);
if ((flags & EvaluatorFlags.ExpectingTypeAnnotation) !== 0) {
// Verify that the name does not refer to a (non type alias) variable.
if (effectiveTypeInfo.includesVariableDecl && !type.typeAliasInfo) {
@ -20260,6 +20275,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
function getFunctionInferredReturnType(type: FunctionType, args?: ValidateArgTypeParams[]) {
let returnType: Type | undefined;
let isIncomplete = false;
let analyzeUnannotatedFunctions = true;
// Don't attempt to infer the return type for a stub file.
if (FunctionType.isStubDefinition(type)) {
@ -20277,9 +20293,11 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
returnType = NoneType.createInstance();
} else if (type.details.declaration) {
const functionNode = type.details.declaration.node;
analyzeUnannotatedFunctions =
AnalyzerNodeInfo.getFileInfo(functionNode).diagnosticRuleSet.analyzeUnannotatedFunctions;
// Skip return type inference if we are in "skip unannotated function" mode.
if (evaluatorOptions.analyzeUnannotatedFunctions && !checkCodeFlowTooComplex(functionNode.suite)) {
if (analyzeUnannotatedFunctions && !checkCodeFlowTooComplex(functionNode.suite)) {
const codeFlowComplexity = AnalyzerNodeInfo.getCodeFlowComplexity(functionNode);
// For very complex functions that have no annotated parameter types,
@ -20332,7 +20350,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
// attempt to do a better job at inference.
if (
!isIncomplete &&
evaluatorOptions.analyzeUnannotatedFunctions &&
analyzeUnannotatedFunctions &&
isPartlyUnknown(returnType) &&
FunctionType.hasUnannotatedParams(type) &&
!FunctionType.isStubDefinition(type) &&

View File

@ -137,5 +137,5 @@ export class CommandLineOptions {
enableAmbientAnalysis = true;
// Analyze functions and methods that have no type annotations?
analyzeUnannotatedFunctions = true;
analyzeUnannotatedFunctions?: boolean;
}

View File

@ -92,6 +92,9 @@ export interface DiagnosticRuleSet {
// Use strict inference rules for dictionary expressions?
strictDictionaryInference: boolean;
// Analyze functions and methods that have no annotations?
analyzeUnannotatedFunctions: boolean;
// Use strict type rules for parameters assigned default of None?
strictParameterNoneValue: boolean;
@ -312,6 +315,7 @@ export function getBooleanDiagnosticRules(includeNonOverridable = false) {
DiagnosticRule.strictListInference,
DiagnosticRule.strictSetInference,
DiagnosticRule.strictDictionaryInference,
DiagnosticRule.analyzeUnannotatedFunctions,
DiagnosticRule.strictParameterNoneValue,
];
@ -410,6 +414,7 @@ export function getOffDiagnosticRuleSet(): DiagnosticRuleSet {
strictListInference: false,
strictSetInference: false,
strictDictionaryInference: false,
analyzeUnannotatedFunctions: true,
strictParameterNoneValue: true,
enableTypeIgnoreComments: true,
reportGeneralTypeIssues: 'none',
@ -489,6 +494,7 @@ export function getBasicDiagnosticRuleSet(): DiagnosticRuleSet {
strictListInference: false,
strictSetInference: false,
strictDictionaryInference: false,
analyzeUnannotatedFunctions: true,
strictParameterNoneValue: true,
enableTypeIgnoreComments: true,
reportGeneralTypeIssues: 'error',
@ -568,6 +574,7 @@ export function getStrictDiagnosticRuleSet(): DiagnosticRuleSet {
strictListInference: true,
strictSetInference: true,
strictDictionaryInference: true,
analyzeUnannotatedFunctions: true,
strictParameterNoneValue: true,
enableTypeIgnoreComments: true, // Not overridden by strict mode
reportGeneralTypeIssues: 'error',
@ -716,10 +723,6 @@ 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

@ -14,6 +14,7 @@ export enum DiagnosticRule {
strictListInference = 'strictListInference',
strictSetInference = 'strictSetInference',
strictDictionaryInference = 'strictDictionaryInference',
analyzeUnannotatedFunctions = 'analyzeUnannotatedFunctions',
strictParameterNoneValue = 'strictParameterNoneValue',
enableTypeIgnoreComments = 'enableTypeIgnoreComments',

View File

@ -930,6 +930,8 @@ export namespace Localizer {
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.unaccessedSymbol'));
export const unaccessedVariable = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.unaccessedVariable'));
export const unannotatedFunctionSkipped = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.unannotatedFunctionSkipped'));
export const unexpectedAsyncToken = () => getRawString('Diagnostic.unexpectedAsyncToken');
export const unexpectedExprToken = () => getRawString('Diagnostic.unexpectedExprToken');
export const unexpectedIndent = () => getRawString('Diagnostic.unexpectedIndent');

View File

@ -470,6 +470,7 @@
"unaccessedImport": "Import \"{name}\" is not accessed",
"unaccessedSymbol": "\"{name}\" is not accessed",
"unaccessedVariable": "Variable \"{name}\" is not accessed",
"unannotatedFunctionSkipped": "Analysis of function \"{name}\" is skipped because it unannotated",
"unexpectedAsyncToken": "Expected \"def\", \"with\" or \"for\" to follow \"async\"",
"unexpectedExprToken": "Unexpected token at end of expression",
"unexpectedIndent": "Unexpected indentation",

View File

@ -255,7 +255,9 @@ async function processArgs(): Promise<ExitStatus> {
options.typeStubTargetImportName = args.createstub;
}
options.analyzeUnannotatedFunctions = !args.skipunannotated;
if (args.skipunannotated) {
options.analyzeUnannotatedFunctions = false;
}
if (args.verbose) {
options.verboseOutput = true;

View File

@ -130,6 +130,12 @@
"title": "Infer strict types for dictionary expressions",
"default": false
},
"analyzeUnannotatedFunctions": {
"$id": "#/properties/analyzeUnannotatedFunctions",
"type": "boolean",
"title": "Analyze and report diagnostics for functions that have no annotations",
"default": true
},
"strictParameterNoneValue": {
"$id": "#/properties/strictParameterNoneValue",
"type": "boolean",