mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-05 01:50:56 +07:00
PY-78878 Missing error: class type parameter parameterizes an outer scope
GitOrigin-RevId: b819ff172605a949156e5bd96d920044b70e1679
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d424ea892b
commit
f89bcdcabc
@@ -1169,6 +1169,7 @@ INSP.type.hints.type.var.tuple.must.always.be.unpacked=TypeVarTuple must always
|
||||
INSP.type.hints.expected.a.type=Expected a type
|
||||
INSP.type.hints.metaclass.cannot.be.generic=Metaclass cannot be generic
|
||||
INSP.type.hints.unbound.type.variable=Unbound type variable
|
||||
INSP.type.hints.some.type.variables.are.used.by.an.outer.scope=Some type variables ({0}) are used by an outer scope
|
||||
QFIX.remove.function.annotations=Remove function annotations
|
||||
QFIX.replace.with.target.name=Replace with the target name
|
||||
QFIX.remove.generic.parameters=Remove generic parameters
|
||||
|
||||
@@ -1654,30 +1654,36 @@ public final class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<
|
||||
PyQualifiedNameOwner typeVarDeclaration = context.getTypeAliasStack().pop();
|
||||
assert typeVarDeclaration instanceof PyTargetExpression;
|
||||
try {
|
||||
if (owner instanceof PyClass) {
|
||||
return StreamEx.of(collectTypeParameters((PyClass)owner, context))
|
||||
.findFirst(type -> name.equals(type.getName()))
|
||||
.orElse(null);
|
||||
final Iterable<PyTypeParameterType> typeParameters;
|
||||
if (owner instanceof PyClass cls) {
|
||||
typeParameters = collectTypeParameters(cls, context);
|
||||
}
|
||||
else if (owner instanceof PyFunction function) {
|
||||
return StreamEx.of(function.getParameterList().getParameters())
|
||||
.select(PyNamedParameter.class)
|
||||
.map(parameter -> new PyTypingTypeProvider().getParameterType(parameter, function, context))
|
||||
.append(new PyTypingTypeProvider().getReturnType(function, context))
|
||||
.map(Ref::deref)
|
||||
.map(paramType -> PyTypeChecker.collectGenerics(paramType, context.getTypeContext()))
|
||||
.flatMap(generics -> StreamEx.<PyTypeParameterType>of(generics.getTypeVars())
|
||||
.append(generics.getParamSpecs())
|
||||
.append(generics.getTypeVarTuples())
|
||||
)
|
||||
.findFirst(type -> name.equals(type.getName()))
|
||||
.orElse(null);
|
||||
typeParameters = collectTypeParameters(function, context.getTypeContext());
|
||||
}
|
||||
else {
|
||||
typeParameters = List.of();
|
||||
}
|
||||
return ContainerUtil.find(typeParameters, type -> name.equals(type.getName()));
|
||||
}
|
||||
finally {
|
||||
context.getTypeAliasStack().push(typeVarDeclaration);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static @NotNull Iterable<PyTypeParameterType> collectTypeParameters(@NotNull PyFunction function,
|
||||
@NotNull TypeEvalContext context) {
|
||||
return StreamEx.of(function.getParameterList().getParameters())
|
||||
.select(PyNamedParameter.class)
|
||||
.map(parameter -> new PyTypingTypeProvider().getParameterType(parameter, function, context))
|
||||
.append(new PyTypingTypeProvider().getReturnType(function, context))
|
||||
.map(Ref::deref)
|
||||
.map(paramType -> PyTypeChecker.collectGenerics(paramType, context))
|
||||
.flatMap(generics -> StreamEx.<PyTypeParameterType>of(generics.getTypeVars())
|
||||
.append(generics.getParamSpecs())
|
||||
.append(generics.getTypeVarTuples())
|
||||
);
|
||||
}
|
||||
|
||||
private static @NotNull PsiElement getStubRetainedTypeHintContext(@NotNull PsiElement typeHintExpression) {
|
||||
|
||||
@@ -109,6 +109,7 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
checkPlainGenericInheritance(superClassExpressions)
|
||||
checkGenericDuplication(superClassExpressions)
|
||||
checkGenericCompleteness(node)
|
||||
checkGenericClassTypeParametersNotUsedByOuterScope(node)
|
||||
checkMetaClass(node.metaClassExpression)
|
||||
}
|
||||
|
||||
@@ -866,6 +867,40 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
return Pair(if (seenGeneric) genericTypeVars else null, nonGenericTypeVars)
|
||||
}
|
||||
|
||||
private fun checkGenericClassTypeParametersNotUsedByOuterScope(cls: PyClass) {
|
||||
fun getTypeParameters(clazz: PyClass): Iterable<PyTypeParameterType> {
|
||||
val clazzType = PyTypeChecker.findGenericDefinitionType(clazz, myTypeEvalContext) ?: return emptyList()
|
||||
return clazzType.elementTypes.filterIsInstance<PyTypeParameterType>()
|
||||
}
|
||||
|
||||
val names = getTypeParameters(cls).map { it.name }.toMutableSet()
|
||||
if (names.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val namesUsedByOuterScopes = mutableListOf<String>()
|
||||
var scopeOwner: ScopeOwner? = cls
|
||||
do {
|
||||
scopeOwner = PsiTreeUtil.getParentOfType(scopeOwner, PyClass::class.java, PyFunction::class.java)
|
||||
val typeParameters = when (scopeOwner) {
|
||||
is PyClass -> getTypeParameters(scopeOwner)
|
||||
is PyFunction -> PyTypingTypeProvider.collectTypeParameters(scopeOwner, myTypeEvalContext)
|
||||
else -> break
|
||||
}
|
||||
for (typeParameter in typeParameters) {
|
||||
val name = typeParameter.name
|
||||
if (names.remove(name)) {
|
||||
namesUsedByOuterScopes.add(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
while (true)
|
||||
|
||||
if (namesUsedByOuterScopes.isNotEmpty()) {
|
||||
registerProblem(cls.nameIdentifier, PyPsiBundle.message("INSP.type.hints.some.type.variables.are.used.by.an.outer.scope",
|
||||
namesUsedByOuterScopes.joinToString(", ")))
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkParameters(node: PySubscriptionExpression) {
|
||||
val operand = node.operand as? PyReferenceExpression ?: return
|
||||
val index = node.indexExpression ?: return
|
||||
|
||||
@@ -39,7 +39,6 @@ generics_paramspec_basic.py
|
||||
generics_paramspec_components.py
|
||||
generics_paramspec_semantics.py
|
||||
generics_paramspec_specialization.py
|
||||
generics_scoping.py
|
||||
generics_self_advanced.py
|
||||
generics_self_basic.py
|
||||
generics_self_usage.py
|
||||
|
||||
@@ -304,6 +304,30 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
|
||||
...""");
|
||||
}
|
||||
|
||||
public void testGenericClassCannotUseTypeVariablesFromOuterScope() {
|
||||
doTestByText("""
|
||||
from typing import TypeVar, Generic, Iterable
|
||||
|
||||
T = TypeVar('T')
|
||||
S = TypeVar('S')
|
||||
|
||||
def a_fun(x: T) -> None:
|
||||
a_list: list[T] = []
|
||||
|
||||
class <warning descr="Some type variables (T) are used by an outer scope">MyGeneric</warning>(Generic[T]):
|
||||
...
|
||||
|
||||
class Outer(Generic[T]):
|
||||
class <warning descr="Some type variables (T) are used by an outer scope">Bad</warning>(Iterable[T]):
|
||||
...
|
||||
class AlsoBad:
|
||||
x: list[<warning descr="Unbound type variable">T</warning>]
|
||||
|
||||
class Inner(Iterable[S]):
|
||||
...
|
||||
attr: Inner[T]""");
|
||||
}
|
||||
|
||||
// PY-28249
|
||||
public void testInstanceAndClassChecksOnAny() {
|
||||
doTestByText("""
|
||||
|
||||
Reference in New Issue
Block a user