PY-86310 Code Insight: Fix false-positive "Type alias is not generic or already specialized"

Correctly resolve type aliases to their declarations

GitOrigin-RevId: 1bf29bf68e8c79dc3e403b7addf9e62ead8cd7d3
This commit is contained in:
Daniil Kalinin
2025-12-22 18:27:31 +00:00
committed by intellij-monorepo-bot
parent 761b7a99d7
commit 34eafbc5cf
3 changed files with 32 additions and 14 deletions
@@ -2291,18 +2291,23 @@ public final class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<
* It can be done either with a variable annotation or a type comment.
*/
public static boolean isExplicitTypeAlias(@NotNull PyAssignmentStatement assignment, @NotNull TypeEvalContext context) {
PyExpression annotationValue = getAnnotationValue(assignment, context);
PyTargetExpression target = as(ArrayUtil.getFirstElement(assignment.getTargets()), PyTargetExpression.class);
if (target == null) {
return false;
}
return isExplicitTypeAlias(target, context);
}
public static boolean isExplicitTypeAlias(@NotNull PyTargetExpression targetExpression, @NotNull TypeEvalContext context) {
PyExpression annotationValue = getAnnotationValue(targetExpression, context);
if (annotationValue instanceof PyReferenceExpression) {
return resolvesToQualifiedNames(annotationValue, context, TYPE_ALIAS, TYPE_ALIAS_EXT);
}
PyTargetExpression target = as(ArrayUtil.getFirstElement(assignment.getTargets()), PyTargetExpression.class);
if (target != null) {
String typeCommentAnnotation = target.getTypeCommentAnnotation();
if (typeCommentAnnotation != null) {
PyExpression commentValue = toExpression(typeCommentAnnotation, assignment);
if (commentValue instanceof PyReferenceExpression) {
return resolvesToQualifiedNames(commentValue, context, TYPE_ALIAS, TYPE_ALIAS_EXT);
}
String typeCommentAnnotation = targetExpression.getTypeCommentAnnotation();
if (typeCommentAnnotation != null) {
PyExpression commentValue = toExpression(typeCommentAnnotation, targetExpression);
if (commentValue instanceof PyReferenceExpression) {
return resolvesToQualifiedNames(commentValue, context, TYPE_ALIAS, TYPE_ALIAS_EXT);
}
}
return false;
@@ -1096,8 +1096,14 @@ class PyTypeHintsInspection : PyInspection() {
}
private fun checkGenericTypeParameterization(node: PySubscriptionExpression) {
val declaration = node.operand.reference
?.let { PyResolveUtil.resolveDeclaration(it, resolveContext) }
val operandRefExpression = node.operand as? PyReferenceExpression ?: return
val declaration = multiFollowAssignmentsChain(operandRefExpression) {
return@multiFollowAssignmentsChain when {
PyTypingTypeProvider.isExplicitTypeAlias(it, myTypeEvalContext) -> false
PyTypingAliasStubType.getAssignedValueStubLike(it) is PyReferenceExpression -> followNotTypingOpaque(it)
else -> false
}
}.firstOrNull()
when (declaration) {
is PyTargetExpression -> checkTypeAliasParameterization(node, declaration)
@@ -1134,11 +1140,9 @@ class PyTypeHintsInspection : PyInspection() {
private fun checkTypeAliasParameterization(node: PySubscriptionExpression, declaration: PyTargetExpression) {
val assignedValue = PyTypingAliasStubType.getAssignedValueStubLike(declaration) ?: return
if (PyTypingTypeProvider.resolveToQualifiedNames(assignedValue, myTypeEvalContext)
.any { PyTypingTypeProvider.OPAQUE_NAMES.contains(it) }) return
val assignedValueType = Ref.deref(PyTypingTypeProvider.getType(assignedValue, myTypeEvalContext)) ?: return
val isExplicitTypeAlias = declaration.annotationValue != null
val isExplicitTypeAlias = PyTypingTypeProvider.isExplicitTypeAlias(declaration, myTypeEvalContext)
val generics = collectTypeParametersFromTypeAlias(assignedValue, assignedValueType, isExplicitTypeAlias)
if (generics.isEmpty) {
registerProblem(node.indexExpression, PyPsiBundle.message("INSP.type.hints.generic.type.alias.is.not.generic.or.already.parameterized"), ProblemHighlightType.WARNING)
@@ -3243,6 +3243,15 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
""");
}
// PY-86310
public void testTargetExpressionWithReassignmentNotProcessedAsImplicitTypeAlias() {
doTestByText("""
b = []
a = b
_ = a[0] # No error expected
""");
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {