Enhanced reportInvalidTypeVarUsage diagnostic message to include a recommended fix. This is related to discussion #3953.

This commit is contained in:
Eric Traut 2023-11-20 20:45:43 -08:00
parent 6758be4865
commit ca376c2109
3 changed files with 24 additions and 1 deletions

View File

@ -184,6 +184,7 @@ import {
} from './types';
interface TypeVarUsageInfo {
typeVar: TypeVarType;
isExempt: boolean;
returnTypeUsageCount: number;
paramTypeUsageCount: number;
@ -2360,6 +2361,7 @@ export class Checker extends ParseTreeWalker {
if (!existingEntry) {
localTypeVarUsage.set(nameType.details.name, {
nodes: [nameNode],
typeVar: nameType,
paramTypeUsageCount: curParamNode !== undefined ? 1 : 0,
paramTypeWithEllipsisUsageCount: isParamTypeWithEllipsisUsage ? 1 : 0,
returnTypeUsageCount: curParamNode === undefined ? 1 : 0,
@ -2392,6 +2394,7 @@ export class Checker extends ParseTreeWalker {
if (!existingEntry) {
classTypeVarUsage.set(nameType.details.name, {
typeVar: nameType,
nodes: [nameNode],
paramTypeUsageCount: curParamNode !== undefined ? 1 : 0,
paramTypeWithEllipsisUsageCount: isParamTypeWithEllipsisUsage ? 1 : 0,
@ -2436,12 +2439,29 @@ export class Checker extends ParseTreeWalker {
localTypeVarUsage.forEach((usage) => {
// Report error for local type variable that appears only once.
if (usage.nodes.length === 1 && !usage.isExempt) {
let altTypeText: string;
if (usage.typeVar.details.isVariadic) {
altTypeText = '"tuple[object, ...]" or "tuple[Any, ...]"';
} else if (usage.typeVar.details.boundType) {
altTypeText = `"${this._evaluator.printType(convertToInstance(usage.typeVar.details.boundType))}"`;
} else {
altTypeText = '"object" or "Any"';
}
const diag = new DiagnosticAddendum();
diag.addMessage(
Localizer.DiagnosticAddendum.typeVarUnnecessarySuggestion().format({
type: altTypeText,
})
);
this._evaluator.addDiagnostic(
this._fileInfo.diagnosticRuleSet.reportInvalidTypeVarUse,
DiagnosticRule.reportInvalidTypeVarUse,
Localizer.Diagnostic.typeVarUsedOnlyOnce().format({
name: usage.nodes[0].value,
}),
}) + diag.getString(),
usage.nodes[0]
);
}

View File

@ -1468,6 +1468,8 @@ export namespace Localizer {
export const typeVarNotAllowed = () => getRawString('DiagnosticAddendum.typeVarNotAllowed');
export const typeVarTupleRequiresKnownLength = () =>
getRawString('DiagnosticAddendum.typeVarTupleRequiresKnownLength');
export const typeVarUnnecessarySuggestion = () =>
new ParameterizedString<{ type: string }>(getRawString('DiagnosticAddendum.typeVarUnnecessarySuggestion'));
export const typeVarUnsolvableRemedy = () => getRawString('DiagnosticAddendum.typeVarUnsolvableRemedy');
export const unhashableType = () =>
new ParameterizedString<{ type: string }>(getRawString('DiagnosticAddendum.unhashableType'));

View File

@ -722,6 +722,7 @@
"typeVarIsCovariant": "Type parameter \"{name}\" is covariant, but \"{sourceType}\" is not a subtype of \"{destType}\"",
"typeVarIsInvariant": "Type parameter \"{name}\" is invariant, but \"{sourceType}\" is not the same as \"{destType}\"",
"typeVarNotAllowed": "TypeVar not allowed for instance or class checks",
"typeVarUnnecessarySuggestion": "Use {type} instead",
"typeVarUnsolvableRemedy": "Provide an overload that specifies the return type when the argument is not supplied",
"typeVarsMissing": "Missing type variables: {names}",
"typeVarTupleRequiresKnownLength": "TypeVarTuple cannot be bound to a tuple of unknown length",