Fixed bug that results in false positive reportInconsistentOverload and reportNoOverloadImplementation errors when an overloaded decorator is applied to a non-overloaded function or method. This addresses #8246. (#8249)

This commit is contained in:
Eric Traut 2024-06-27 05:35:50 -07:00 committed by GitHub
parent 1e1e912380
commit f104e7bb30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 124 additions and 89 deletions

View File

@ -3092,10 +3092,16 @@ export class Checker extends ParseTreeWalker {
private _reportInvalidOverload(name: string, symbol: Symbol) {
const typedDecls = symbol.getTypedDeclarations();
if (typedDecls.length >= 1) {
if (typedDecls.length === 0) {
return;
}
const primaryDecl = typedDecls[0];
if (primaryDecl.type === DeclarationType.Function) {
if (primaryDecl.type !== DeclarationType.Function) {
return;
}
const type = this._evaluator.getEffectiveTypeOfSymbol(symbol);
const overloadedFunctions = isOverloadedFunction(type)
? OverloadedFunctionType.getOverloads(type)
@ -3103,6 +3109,20 @@ export class Checker extends ParseTreeWalker {
? [type]
: [];
// If the implementation has no name, it was synthesized probably by a
// decorator that used a callable with a ParamSpec that captured the
// overloaded signature. We'll exempt it from this check.
if (isOverloadedFunction(type)) {
const overloads = OverloadedFunctionType.getOverloads(type);
if (overloads.length > 0 && overloads[0].details.name === '') {
return;
}
} else if (isFunction(type)) {
if (type.details.name === '') {
return;
}
}
if (overloadedFunctions.length === 1) {
// There should never be a single overload.
this._evaluator.addDiagnostic(
@ -3114,20 +3134,14 @@ export class Checker extends ParseTreeWalker {
// If the file is not a stub and this is the first overload,
// verify that there is an implementation.
if (!this._fileInfo.isStubFile && overloadedFunctions.length > 0) {
if (this._fileInfo.isStubFile || overloadedFunctions.length === 0) {
return;
}
let implementationFunction: FunctionType | undefined;
let exemptMissingImplementation = false;
if (isOverloadedFunction(type)) {
implementationFunction = OverloadedFunctionType.getImplementation(type);
// If the implementation has no name, it was synthesized probably by a
// decorator that used a callable with a ParamSpec that captured the
// overloaded signature. We'll exempt it from this check.
const overloads = OverloadedFunctionType.getOverloads(type);
if (overloads.length > 0 && overloads[0].details.name === '') {
exemptMissingImplementation = true;
}
} else if (isFunction(type) && !FunctionType.isOverloaded(type)) {
implementationFunction = type;
}
@ -3138,15 +3152,17 @@ export class Checker extends ParseTreeWalker {
const classType = this._evaluator.getTypeOfClass(containingClassNode);
if (classType) {
if (ClassType.isProtocolClass(classType.classType)) {
exemptMissingImplementation = true;
} else if (ClassType.supportsAbstractMethods(classType.classType)) {
return;
}
if (ClassType.supportsAbstractMethods(classType.classType)) {
if (
isOverloadedFunction(type) &&
OverloadedFunctionType.getOverloads(type).every((overload) =>
FunctionType.isAbstractMethod(overload)
)
) {
exemptMissingImplementation = true;
return;
}
}
}
@ -3154,7 +3170,6 @@ export class Checker extends ParseTreeWalker {
// If this is a method within a protocol class, don't require that
// there is an implementation.
if (!exemptMissingImplementation) {
this._evaluator.addDiagnostic(
DiagnosticRule.reportNoOverloadImplementation,
LocMessage.overloadWithoutImplementation().format({
@ -3162,8 +3177,14 @@ export class Checker extends ParseTreeWalker {
}),
primaryDecl.node.name
);
return;
}
} else if (isOverloadedFunction(type)) {
if (!isOverloadedFunction(type)) {
return;
}
// Verify that all overload signatures are assignable to implementation signature.
OverloadedFunctionType.getOverloads(type).forEach((overload, index) => {
const diag = new DiagnosticAddendum();
@ -3189,10 +3210,6 @@ export class Checker extends ParseTreeWalker {
}
});
}
}
}
}
}
private _reportMultipleFinalDeclarations(name: string, symbol: Symbol, scopeType: ScopeType) {
if (!this._evaluator.isFinalVariable(symbol)) {

View File

@ -43,7 +43,7 @@ class ClassB(Protocol):
def deco1(
_origin: Callable[P, T]
_origin: Callable[P, T],
) -> Callable[[Callable[..., Any]], Callable[P, T]]: ...
@ -60,3 +60,21 @@ def func5(v: int | str) -> int | str: ...
@deco1(func5)
def func6(*args: Any, **kwargs: Any) -> Any: ...
@overload
def deco2() -> Callable[[Callable[P, T]], Callable[P, T | None]]: ...
@overload
def deco2(
x: Callable[[], T],
) -> Callable[[Callable[P, T]], Callable[P, T]]: ...
def deco2(
x: Callable[[], T | None] = lambda: None,
) -> Callable[[Callable[P, T]], Callable[P, T | None]]: ...
@deco2(x=dict)
def func7() -> dict[str, str]:
return {}