Fixed a bug that results in a false negative when a generic function has a parameter with a generic type and a default argument value. This could lead to a typing hole when considering subtyping rules for callables. This addresses #7288. (#7954)

This commit is contained in:
Eric Traut 2024-05-19 21:34:08 -07:00 committed by GitHub
parent 0b7860b15e
commit dbac83d45c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 98 additions and 1 deletions

View File

@ -25073,6 +25073,28 @@ export function createTypeEvaluator(
);
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
@ -25276,6 +25298,26 @@ export function createTypeEvaluator(
) {
canAssign = false;
}
} else if (srcParamInfo.param.hasDefault) {
// Assign default arg values in case they are needed for
// populating TypeVar constraints.
const defaultArgType =
srcParamInfo.defaultArgType ?? srcParamInfo.param.defaultType;
if (
defaultArgType &&
!assignType(
srcParamInfo.type,
defaultArgType,
diag?.createAddendum(),
srcTypeVarContext,
/* destTypeVarContext */ undefined,
flags,
recursionCount
)
) {
canAssign = false;
}
}
} else {
const destParamType = destParamInfo.type;

View File

@ -0,0 +1,47 @@
# This sample tests the case where a generic function has a default argument
# value for a parameter with a generic type.
from collections.abc import Callable
from typing import Iterable, Mapping, TypeVar
T = TypeVar("T")
default_value: dict[str, int] = {}
def func1(x: T, y: Mapping[str, T] = default_value, /) -> T: ...
def func2(x: T, y: Mapping[str, T] = default_value) -> T: ...
def func3(x: T, *, y: Mapping[str, T] = default_value) -> T: ...
def test1(func: Callable[[T], T], value: T) -> T:
return func(value)
# This should generate an error.
test1(func1, "")
# This should generate an error.
test1(func2, "")
# This should generate an error.
test1(func3, "")
reveal_type(test1(func1, 1), expected_text="int")
reveal_type(test1(func2, 1), expected_text="int")
reveal_type(test1(func3, 1), expected_text="int")
def func4(x: T, y: Iterable[T] = default_value, z: T = "", /) -> T: ...
def func5(x: T, y: Iterable[T] = default_value, z: T = "") -> T: ...
def func6(x: T, *, y: Iterable[T] = default_value, z: T = "") -> T: ...
# This should generate an error.
test1(func4, 1)
# This should generate an error.
test1(func5, 1)
# This should generate an error.
test1(func6, 1)

View File

@ -209,7 +209,9 @@ _T9 = TypeVar("_T9")
@overload
def func12(func: Callable[[_T7], _T8], iterable: Iterable[_T7], /) -> Iterable[_T8]: ...
def func12(
func: Callable[[_T7], _T8], iterable: Iterable[_T7], default_value: None = None, /
) -> Iterable[_T8 | None]: ...
@overload

View File

@ -858,6 +858,12 @@ test('Call14', () => {
TestUtils.validateResults(analysisResults, 5);
});
test('Call15', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['call15.py']);
TestUtils.validateResults(analysisResults, 6);
});
test('Function1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['function1.py']);