mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-11 16:06:39 +03:00
Fixed a bug that results in incorrect type compatibility checks for a… (#7994)
* Fixed a bug that results in incorrect type compatibility checks for a callable that uses `*args: *tuple[T, *tuple[S, ...]]`. This addresses #7987. * Improved diagnostic messages for parameter mismatch.
This commit is contained in:
parent
af916c6644
commit
0a83d6459c
@ -25298,76 +25298,83 @@ export function createTypeEvaluator(
|
||||
canAssign = false;
|
||||
}
|
||||
|
||||
if (destPositionalCount < srcPositionalCount) {
|
||||
// If the dest type includes a ParamSpec, the additional parameters
|
||||
// can be assigned to it, so no need to report an error here.
|
||||
if (!targetIncludesParamSpec) {
|
||||
// All source parameters that don't have a default value must
|
||||
// have a matching parameter in the dest.
|
||||
if (destPositionalCount < srcPositionalCount && !targetIncludesParamSpec) {
|
||||
for (let i = destPositionalCount; i < srcPositionalCount; i++) {
|
||||
// If the dest has an *args parameter, make sure it can accept the remaining
|
||||
// positional arguments in the source.
|
||||
if (destParamDetails.argsIndex !== undefined) {
|
||||
const destArgsType = destParamDetails.params[destParamDetails.argsIndex].type;
|
||||
const srcParamType = srcParamDetails.params[i].type;
|
||||
if (
|
||||
!assignFunctionParameter(
|
||||
destArgsType,
|
||||
srcParamType,
|
||||
i,
|
||||
diag?.createAddendum(),
|
||||
destTypeVarContext,
|
||||
srcTypeVarContext,
|
||||
flags,
|
||||
recursionCount
|
||||
)
|
||||
) {
|
||||
canAssign = false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// If The source parameter has a default value, it is OK for the
|
||||
// corresponding dest parameter to be missing.
|
||||
const srcParam = srcParamDetails.params[i];
|
||||
|
||||
if (srcParam.param.hasDefault) {
|
||||
// Assign default arg value in case it is needed for
|
||||
// populating TypeVar constraints.
|
||||
const paramInfo = srcParamDetails.params[i];
|
||||
const defaultArgType = paramInfo.defaultArgType ?? paramInfo.param.defaultType;
|
||||
|
||||
if (
|
||||
defaultArgType &&
|
||||
!assignType(
|
||||
paramInfo.type,
|
||||
defaultArgType,
|
||||
diag?.createAddendum(),
|
||||
srcTypeVarContext,
|
||||
/* destTypeVarContext */ undefined,
|
||||
flags,
|
||||
recursionCount
|
||||
)
|
||||
) {
|
||||
canAssign = false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the source parameter is also addressible by keyword, it is OK
|
||||
// that there is no matching positional parameter in the dest.
|
||||
if (srcParam.kind === ParameterKind.Standard) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the source parameter is a variadic, it is OK that there is no
|
||||
// matching positional parameter in the dest.
|
||||
if (srcParam.param.category === ParameterCategory.ArgsList) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const nonDefaultSrcParamCount = srcParamDetails.params.filter(
|
||||
(p) => !!p.param.name && !p.param.hasDefault && p.param.category === ParameterCategory.Simple
|
||||
).length;
|
||||
|
||||
if (destParamDetails.argsIndex === undefined) {
|
||||
if (destPositionalCount < nonDefaultSrcParamCount) {
|
||||
const destPositionOnlyCount = destParamDetails.firstPositionOrKeywordIndex;
|
||||
|
||||
if (destPositionOnlyCount > 0 && destPositionOnlyCount < srcPositionalCount) {
|
||||
diag?.createAddendum().addMessage(
|
||||
LocAddendum.functionTooFewParams().format({
|
||||
expected: nonDefaultSrcParamCount,
|
||||
received: destPositionalCount,
|
||||
})
|
||||
);
|
||||
canAssign = false;
|
||||
}
|
||||
} else {
|
||||
// Assign default arg values in case they are needed for
|
||||
// populating TypeVar constraints.
|
||||
for (let i = destParamDetails.firstPositionOrKeywordIndex; i < srcPositionalCount; i++) {
|
||||
const paramInfo = srcParamDetails.params[i];
|
||||
const defaultArgType = paramInfo.defaultArgType ?? paramInfo.param.defaultType;
|
||||
|
||||
if (
|
||||
defaultArgType &&
|
||||
!assignType(
|
||||
paramInfo.type,
|
||||
defaultArgType,
|
||||
diag?.createAddendum(),
|
||||
srcTypeVarContext,
|
||||
/* destTypeVarContext */ undefined,
|
||||
flags,
|
||||
recursionCount
|
||||
)
|
||||
) {
|
||||
canAssign = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Make sure the remaining positional arguments are of the
|
||||
// correct type for the *args parameter.
|
||||
const destArgsType = destParamDetails.params[destParamDetails.argsIndex].type;
|
||||
if (!isAnyOrUnknown(destArgsType)) {
|
||||
for (let paramIndex = destPositionalCount; paramIndex < srcPositionalCount; paramIndex++) {
|
||||
const srcParamType = srcParamDetails.params[paramIndex].type;
|
||||
if (
|
||||
!assignFunctionParameter(
|
||||
destArgsType,
|
||||
srcParamType,
|
||||
paramIndex,
|
||||
diag?.createAddendum(),
|
||||
destTypeVarContext,
|
||||
srcTypeVarContext,
|
||||
flags,
|
||||
recursionCount
|
||||
)
|
||||
) {
|
||||
canAssign = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
diag?.createAddendum().addMessage(
|
||||
LocAddendum.functionTooFewParams().format({
|
||||
expected: nonDefaultSrcParamCount,
|
||||
received: destPositionalCount,
|
||||
})
|
||||
);
|
||||
canAssign = false;
|
||||
break;
|
||||
}
|
||||
} else if (srcPositionalCount < destPositionalCount) {
|
||||
if (srcParamDetails.argsIndex !== undefined) {
|
||||
@ -25522,7 +25529,7 @@ export function createTypeEvaluator(
|
||||
|
||||
if (!destParamInfo) {
|
||||
if (destParamDetails.kwargsIndex === undefined && !srcParamInfo.param.hasDefault) {
|
||||
if (paramDiag && srcParamDetails.firstKeywordOnlyIndex !== undefined) {
|
||||
if (paramDiag) {
|
||||
paramDiag.addMessage(
|
||||
LocAddendum.namedParamMissingInDest().format({
|
||||
name: srcParamInfo.param.name,
|
||||
|
@ -679,8 +679,8 @@
|
||||
"missingGetter": "Property getter method is missing",
|
||||
"missingSetter": "Property setter method is missing",
|
||||
"missingDeleter": "Property deleter method is missing",
|
||||
"namedParamMissingInDest": "Keyword parameter \"{name}\" is missing in destination",
|
||||
"namedParamMissingInSource": "Keyword parameter \"{name}\" is missing in source",
|
||||
"namedParamMissingInDest": "Extra parameter \"{name}\"",
|
||||
"namedParamMissingInSource": "Missing keyword parameter \"{name}\"",
|
||||
"namedParamTypeMismatch": "Keyword parameter \"{name}\" of type \"{sourceType}\" is incompatible with type \"{destType}\"",
|
||||
"namedTupleNotAllowed": "NamedTuple cannot be used for instance or class checks",
|
||||
"newMethodLocation": "The __new__ method is defined in class \"{type}\"",
|
||||
|
@ -122,3 +122,27 @@ def test_func5(a: int, b: str) -> int:
|
||||
|
||||
|
||||
f5: TestClass5 = test_func5
|
||||
|
||||
|
||||
class TestClass6(Protocol):
|
||||
def __call__(self, a: int, /, *, b: str) -> int: ...
|
||||
|
||||
|
||||
def test_func6(a: int, b: str) -> int:
|
||||
return 123
|
||||
|
||||
|
||||
f6: TestClass6 = test_func6
|
||||
|
||||
|
||||
class TestClass7:
|
||||
def __call__(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def test_func7(*args: * tuple[int, *tuple[int, ...]]) -> int:
|
||||
return 123
|
||||
|
||||
|
||||
# This should generate an error.
|
||||
f7: TestClass7 = test_func7
|
||||
|
@ -16,7 +16,7 @@ import * as TestUtils from './testUtils';
|
||||
test('CallbackProtocol1', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['callbackProtocol1.py']);
|
||||
|
||||
TestUtils.validateResults(analysisResults, 9);
|
||||
TestUtils.validateResults(analysisResults, 10);
|
||||
});
|
||||
|
||||
test('CallbackProtocol2', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user