PY-87890 pandas.DataFrame is considered Any with pandas >= 3 and pycharm 2026.1

(cherry picked from commit 7bab57dfde79fc86f672f7cb14193d7e1cbcdd6a)

IJ-MR-193139

GitOrigin-RevId: dc4a5bd0a1f4deb2865525b7db9841ab57ac42dd
This commit is contained in:
Marcus Mews
2026-03-02 21:39:01 +00:00
committed by intellij-monorepo-bot
parent 320aed5027
commit 859db02320
3 changed files with 115 additions and 35 deletions
@@ -175,17 +175,17 @@ object PyTypeInferenceCspFactory {
throw NotSupportedException()
}
for (paramType in generics.typeVars) {
if (builder.hasInferenceVariable(paramType)) continue
builder.addInferenceVariable(paramType)
for (typeParam in generics.typeVars) {
if (builder.hasInferenceVariable(typeParam)) continue
builder.addInferenceVariable(typeParam)
// bounds
if (paramType.getBound() != null) {
val typeVarBound_selfBounded = substituteSelfTypes(paramType.getBound(), receiverType, context)
if (typeParam.getBound() != null) {
val typeVarBound_selfBounded = substituteSelfTypes(typeParam.getBound(), receiverType, context)
// semantics: TV <: Bound
builder.addConstraint(paramType, typeVarBound_selfBounded, Variance.COVARIANT, ConstraintPriority.HIGH)
builder.addConstraint(typeParam, typeVarBound_selfBounded, Variance.COVARIANT, ConstraintPriority.HIGH)
}
else if (paramType.getConstraints().isNotEmpty()) {
else if (typeParam.getConstraints().isNotEmpty()) {
// Note: The Python type variable constraint(s) cannot be fully modeled without a specific CSP constraint that would model a strict logical OR.
// A logical OR does unfortunately come with a performance impact since it makes backtracking during the solving process inevitable.
// As a solution, Python type variable constraints will be modeled using an approximation that ensures that the type variable is both
@@ -194,13 +194,13 @@ object PyTypeInferenceCspFactory {
// Only at the very end, during instantiation, an actual set of remaining tv-constraints is chosen.
// Note that both of these bounds are necessary to ensure that the TV will be instantiated as exactly one of the given tv-constraints
// and not as a subtype of one of the given tv-constraints.
val paramTypeConstraints = paramType.getConstraints().map { substituteSelfTypes(it, receiverType, context) }
val paramTypeConstraints = typeParam.getConstraints().map { substituteSelfTypes(it, receiverType, context) }
val intersectionOfConstraints = PyIntersectionType.intersection(paramTypeConstraints)
val unionOfConstraints = PyUnionType.union(paramTypeConstraints)
// semantics: TV approximates CV_1 ⊕ CV_2 ⊕ ... ⊕ CV_n by
// CV_1 & CV_2 & ... & CV_n <: TV <: CV_1 | CV_2 | ... | CV_n
builder.addConstraint(paramType, intersectionOfConstraints, Variance.CONTRAVARIANT, ConstraintPriority.HIGH)
builder.addConstraint(paramType, unionOfConstraints, Variance.COVARIANT, ConstraintPriority.HIGH)
builder.addConstraint(typeParam, intersectionOfConstraints, Variance.CONTRAVARIANT, ConstraintPriority.HIGH)
builder.addConstraint(typeParam, unionOfConstraints, Variance.COVARIANT, ConstraintPriority.HIGH)
}
}
}
@@ -138,33 +138,42 @@ class CspBuilder(val context: TypeEvalContext) {
fun getSolution(keepUnconstrained: Boolean): Solution {
val instantiations = if (cp.failed) cp.instantiations else cp.solution
val typeVars2TypeRefs: MutableMap<PyTypeVarType, Ref<PyType?>> = LinkedHashMap()
for (entry in instantiations) {
val instantiatedType = entry.value
val instantiatedTypeOrTypeVar: PyType?
if (instantiatedType is PyUnconstrainedTypeVariable) {
val originalTypeVar = instantiatedType.typeVariable
instantiatedTypeOrTypeVar = if (keepUnconstrained) originalTypeVar else originalTypeVar.defaultType?.get()
}
else if (instantiatedType is PyTypeVarType) {
// if the solution is another PyTypeVarType, check the declared default types
if (instantiatedType.defaultType?.get() != null) {
instantiatedTypeOrTypeVar = instantiatedType.defaultType?.get()
}
else if (entry.key.typeVariable.defaultType?.get() != null) {
instantiatedTypeOrTypeVar = entry.key.typeVariable.defaultType?.get()
}
else {
instantiatedTypeOrTypeVar = instantiatedType
}
}
else {
instantiatedTypeOrTypeVar = instantiatedType
}
typeVars2TypeRefs[entry.key.typeVariable] = Ref.create(instantiatedTypeOrTypeVar)
for ((inferenceVariable, instantiatedType) in instantiations) {
val instantiatedTypeOrTypeVar = getPostComputedSolution(instantiatedType, inferenceVariable, keepUnconstrained)
typeVars2TypeRefs[inferenceVariable.typeVariable] = Ref.create(instantiatedTypeOrTypeVar)
}
val complete = instantiations.keys.containsAll(cp.inferenceVars.values())
return Solution(cp.failed, complete, typeVars2TypeRefs)
}
private fun getPostComputedSolution(
instantiatedType: PyType?,
inferenceVariable: InferenceVariable,
keepUnconstrained: Boolean,
): PyType? {
when (instantiatedType) {
is PyUnconstrainedTypeVariable -> {
val originalTypeVar = instantiatedType.typeVariable
return when {
keepUnconstrained -> originalTypeVar
originalTypeVar.defaultType != null -> originalTypeVar.defaultType?.get()
originalTypeVar.bound != null -> originalTypeVar.bound
else -> null
}
}
is PyTypeVarType -> {
// if the solution is another PyTypeVarType, check the declared default types
return when {
instantiatedType.defaultType?.get() != null -> instantiatedType.defaultType?.get()
inferenceVariable.typeVariable.defaultType?.get() != null -> inferenceVariable.typeVariable.defaultType?.get()
else -> instantiatedType
}
}
else -> {
return instantiatedType
}
}
}
}
enum class ConstraintPriority {
@@ -1371,6 +1380,12 @@ private object TypeBoundResolver {
return PyUnionType.union(lowerBoundsWidened)
}
else if (lowerBounds.isEmpty() && upperBounds.isNotEmpty()) {
if (upperBounds.size == 1 && upperBounds[0] == infVar.typeVariable.bound) {
// special case: the type variable is constrained only by its bound (i.e., `[T : int]`).
// Therefore, we treat this type variable as unconstrained and use its bound when necessary based on `#keepUnconstrained`.
return PyUnconstrainedTypeVariable(infVar.typeVariable)
}
// It is debatable whether we should just return PyIntersectionType.intersection(*upperBounds)
// Note however that intersection types are not part of Python (as of 2026).
// Hence, the following logic makes it mandatory that the user declares a common subtype at some point.
@@ -386,7 +386,7 @@ class PyTypeInferenceCspTest : PyInspectionTestCase() {
fun `test Handle inferred intersections 2`() {
doTestByText("""
from typing import Callable, TypeVar, assert_type, Never
from typing import Callable, TypeVar, assert_type, Never, Any
class A: ...
class B: ...
@@ -437,7 +437,6 @@ class PyTypeInferenceCspTest : PyInspectionTestCase() {
""")
}
@TestFor(issues = ["PY-86098"])
fun `test PY-86098`() {
doTestByText("""
@@ -448,4 +447,70 @@ class PyTypeInferenceCspTest : PyInspectionTestCase() {
assert_type(a1, A[int])
""")
}
@TestFor(issues = ["PY-87890"])
fun `test Nested csp with type parameter bound`() {
doTestByText("""
from typing import Any, Callable, assert_type
def f[F: Callable[..., Any]]() -> Callable[[F], F]:
return lambda x: x
assert_type(f()(lambda x: 1)(1), int)
""")
}
@TestFor(issues = ["PY-87890"])
fun `test Nested csp with type parameter default`() {
doTestByText("""
from typing import Callable, assert_type, Optional
def f[T = str]() -> Callable[[Optional[T]], T]: ...
assert_type(f()(2), int)
assert_type(f()(), str)
""")
}
fun `test Nested csp with type parameter default Any`() {
doTestByText("""
from typing import Callable, assert_type, Any, Optional
def f[T = Any]() -> Callable[[Optional[T]], T]: ...
assert_type(f()(2), int)
assert_type(f()(), Any)
""")
}
fun `test Nested csp with type parameter constraint`() {
fixme("Support for combined CSPs necessary", AssertionError::class.java) {
doTestByText("""
from typing import Callable, assert_type, Any
def f[T : (str, int)]() -> Callable[[T], T]: ...
assert_type(f()(2), int)
assert_type(f()("s"), str)
""")
}
}
fun `test Keep unconstrained type parameters for type return`() {
doTestByText("""
from typing import Generic, TypeVar
T = TypeVar("T", infer_variance=False)
class Box(Generic[T]):
...
def box_class() -> type[Box[T]]:
return Box
C = box_class()
box_int : Box[int] = C()
assert_type(box_int, Box[int])
""")
}
}