PY-61878 Resolve type parameters and type aliases on stubs

Do not resolve type parameters as class members

Tests for the changes above

Co-authored-by: Mikhail Golubev <mikhail.golubev@jetbrains.com>

GitOrigin-RevId: 96309ebedf26d04e375bfa3a5f8ae0bc9257d48f
This commit is contained in:
Daniil Kalinin
2023-11-06 19:59:18 +00:00
committed by intellij-monorepo-bot
co-authored by Mikhail Golubev
parent eb58a3805e
commit b83be81bb7
4 changed files with 40 additions and 6 deletions
@@ -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() {
@@ -267,20 +267,20 @@ public final class PyResolveUtil {
final PyResolveContext resolveContext = PyResolveContext.defaultContext(context);
final List<? extends RatedResolveResult> 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<PsiNamedElement> targets = StreamEx
.of(PsiTreeUtil.getStubChildrenOfTypeAsList(scopeOwner, PyTargetExpression.class))
.filter(it -> !it.isQualified())
.select(PsiNamedElement.class);
final Stream<PsiNamedElement> 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<? extends RatedResolveResult> 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<RatedResolveResult> initialResults;
@@ -520,4 +529,18 @@ public final class PyResolveUtil {
return reference.resolve();
}
@NotNull
private static List<RatedResolveResult> 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();
}
}
}
@@ -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) ||
@@ -0,0 +1,5 @@
class C[T]:
pass
print(C.T)
<ref>