diff --git a/python/python-common-tests/com/jetbrains/python/PyCommonResolveTest.java b/python/python-common-tests/com/jetbrains/python/PyCommonResolveTest.java index 907acc487dba..c354d5f29f99 100644 --- a/python/python-common-tests/com/jetbrains/python/PyCommonResolveTest.java +++ b/python/python-common-tests/com/jetbrains/python/PyCommonResolveTest.java @@ -1860,6 +1860,11 @@ public abstract class PyCommonResolveTest extends PyCommonResolveTestCase { assertResolvesTo(PyClass.class, "Nested"); } + // PY-61877 + public void testNewStyleTypeParameterNotResolvedAsClassAttribute() { + assertNotResolved(); + } + // [TODO] daniil.kalinin enable when resolve for collisions in type parameter names and class attribute names is implemented // PY-61877 //public void testClassAttributeDeclarationWithSameAsTypeParameterNameNotResolvedToTypeParameter() { diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java b/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java index 65b2008cc4ec..1c9676d38f3f 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java @@ -267,20 +267,20 @@ public final class PyResolveUtil { final PyResolveContext resolveContext = PyResolveContext.defaultContext(context); final List unqualifiedResults; - if (scopeOwner instanceof PyiFile) { + if (scopeOwner instanceof PyiFile fileScope) { // pyi-stubs are special cased because // `resolveMember` delegates to `multiResolveName(..., true)` and // it skips elements that are imported without `as` - unqualifiedResults = ((PyiFile)scopeOwner).multiResolveName(firstName, false); + unqualifiedResults = fileScope.multiResolveName(firstName, false); } - else if (scopeOwner instanceof PyFunction) { + else if (scopeOwner instanceof PyFunction functionScope) { final Stream targets = StreamEx .of(PsiTreeUtil.getStubChildrenOfTypeAsList(scopeOwner, PyTargetExpression.class)) .filter(it -> !it.isQualified()) .select(PsiNamedElement.class); final Stream parameters = StreamEx - .of(((PyFunction)scopeOwner).getParameterList().getParameters()) + .of(functionScope.getParameterList().getParameters()) .select(PsiNamedElement.class); unqualifiedResults = StreamEx @@ -288,13 +288,22 @@ public final class PyResolveUtil { .append(parameters) .filter(it -> firstName.equals(it.getName())) .map(it -> new RatedResolveResult(RatedResolveResult.RATE_NORMAL, it)) + .append(resolveTypeParameters(functionScope, firstName)) .toList(); } + else if (scopeOwner instanceof PyTypeAliasStatement) { + unqualifiedResults = resolveTypeParameters((PyTypeParameterListOwner)scopeOwner, firstName); + } else { final PyType scopeType = context.getType((PyTypedElement)scopeOwner); if (scopeType == null) return Collections.emptyList(); - - unqualifiedResults = scopeType.resolveMember(firstName, null, AccessDirection.READ, resolveContext); + List typeMembers = scopeType.resolveMember(firstName, null, AccessDirection.READ, resolveContext); + if (scopeOwner instanceof PyClass pyClass) { + unqualifiedResults = ContainerUtil.concat(ContainerUtil.notNullize(typeMembers), resolveTypeParameters(pyClass, firstName)); + } + else { + unqualifiedResults = typeMembers; + } } final StreamEx initialResults; @@ -520,4 +529,18 @@ public final class PyResolveUtil { return reference.resolve(); } + + @NotNull + private static List resolveTypeParameters(@NotNull PyTypeParameterListOwner typeParameterListOwner, + @NotNull String name) { + if (typeParameterListOwner.getTypeParameterList() != null) { + return StreamEx.of(typeParameterListOwner.getTypeParameterList().getTypeParameters()) + .filter(it -> name.equals(it.getName())) + .map(it -> new RatedResolveResult(RatedResolveResult.RATE_NORMAL, it)) + .toList(); + } + else { + return Collections.emptyList(); + } + } } diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java b/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java index 8cbce669bd36..98c761281f68 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java @@ -777,6 +777,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { @Override protected boolean tryAddResult(@Nullable PsiElement element, @Nullable PyImportedNameDefiner definer) { PsiElement psiElement = definer != null ? definer : element; + if (element instanceof PyTypeParameter) return false; if (inSameScope(psiElement, myLocation)) { if (PsiTreeUtil.isAncestor(psiElement, myLocation, false) || PyDefUseUtil.isDefinedBefore(psiElement, myLocation) || diff --git a/python/testData/resolve/NewStyleTypeParameterNotResolvedAsClassAttribute.py b/python/testData/resolve/NewStyleTypeParameterNotResolvedAsClassAttribute.py new file mode 100644 index 000000000000..ea3831f7a0fc --- /dev/null +++ b/python/testData/resolve/NewStyleTypeParameterNotResolvedAsClassAttribute.py @@ -0,0 +1,5 @@ +class C[T]: + pass + +print(C.T) +