PY-79816 Introduce PyType#getMemberTypes and use it to infer __hash__ type of dataclasses

`getMemberTypes` should be used for members which have no PSI which can be used to resolve to. For example, `__init__` method in dataclasses are sometimes not present in the source code. Yet its parameters are always useful for the code analysis. In this case, `getMemberTypes` should be used.

GitOrigin-RevId: 2455ed05099842fc50e1fa2a196c4952b6444795
This commit is contained in:
evgeny.bovykin
2025-07-02 08:33:56 +00:00
committed by intellij-monorepo-bot
parent 9094c7acdc
commit abe6184e8a
12 changed files with 267 additions and 52 deletions

View File

@@ -5,12 +5,16 @@ import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.types.PyCallableType;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.PyTypedResolveResult;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
@@ -72,4 +76,11 @@ public interface PyTypeProvider {
@Nullable Ref<@Nullable PyCallableType> prepareCalleeTypeForCall(@Nullable PyType type,
@NotNull PyCallExpression call,
@NotNull TypeEvalContext context);
@ApiStatus.Experimental
@Nullable List<@NotNull PyTypedResolveResult> getMemberTypes(@NotNull PyType type,
@NotNull String name,
@Nullable PyExpression location,
@NotNull AccessDirection direction,
@NotNull PyResolveContext context);
}

View File

@@ -5,9 +5,12 @@ import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.psi.PsiElement;
import com.intellij.util.ProcessingContext;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.psi.AccessDirection;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyQualifiedNameOwner;
import com.jetbrains.python.psi.PyTypedElement;
import com.jetbrains.python.psi.impl.PyTypeProvider;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import org.jetbrains.annotations.ApiStatus;
@@ -36,22 +39,53 @@ public interface PyType {
/**
* Resolves an attribute of type.
*
* @param name attribute name
* @param location the expression of type qualifierType on which the member is being resolved (optional)
* @param name attribute name
* @param location the expression of type qualifierType on which the member is being resolved (optional)
* @return null if name definitely cannot be found (e.g. in a qualified reference),
* or an empty list if name is not found but other contexts are worth looking at,
* or a list of elements that define the name, a la multiResolve().
* or an empty list if name is not found but other contexts are worth looking at,
* or a list of elements that define the name, a la multiResolve().
*/
@Nullable
List<? extends RatedResolveResult> resolveMember(@NotNull String name, final @Nullable PyExpression location,
final @NotNull AccessDirection direction, final @NotNull PyResolveContext resolveContext);
List<? extends RatedResolveResult> resolveMember(@NotNull String name,
final @Nullable PyExpression location,
final @NotNull AccessDirection direction,
final @NotNull PyResolveContext resolveContext);
@ApiStatus.Experimental
@Nullable
default List<@NotNull PyTypedResolveResult> getMemberTypes(@NotNull String name,
final @Nullable PyExpression location,
final @NotNull AccessDirection direction,
final @NotNull PyResolveContext context) {
for (PyTypeProvider typeProvider : PyTypeProvider.EP_NAME.getExtensionList()) {
List<PyTypedResolveResult> types = typeProvider.getMemberTypes(this, name, location, direction, context);
if (types != null) {
return types;
}
}
List<? extends RatedResolveResult> results = resolveMember(name, location, direction, context);
if (results == null) {
return null;
}
return ContainerUtil.map(results, result -> {
PsiElement element = result.getElement();
if (element instanceof PyTypedElement typedElement) {
return new PyTypedResolveResult(typedElement,
context.getTypeEvalContext().getType(typedElement));
}
else {
return new PyTypedResolveResult(element, null);
}
});
}
/**
* Proposes completion variants from type's attributes.
*
*
* @param location the reference on which the completion was invoked
* @param context to share state between nested invocations
* @param location the reference on which the completion was invoked
* @param context to share state between nested invocations
* @return completion variants good for {@link com.intellij.psi.PsiReference#getVariants} return value.
*/
Object[] getCompletionVariants(String completionPrefix, PsiElement location, ProcessingContext context);
@@ -63,9 +97,12 @@ public interface PyType {
/**
* TODO rename it to something like getPresentableName(), because it's not clear that these names are actually visible to end-user
*
* @return name of the type
*/
@Nullable @NlsSafe String getName();
@Nullable
@NlsSafe
String getName();
/**
* @return true if the type is a known built-in type.

View File

@@ -7,6 +7,7 @@ import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.FactoryMap;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyTypeProvider;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -82,6 +83,15 @@ public class PyTypeProviderBase implements PyTypeProvider {
return null;
}
@Override
public @Nullable List<@NotNull PyTypedResolveResult> getMemberTypes(@NotNull PyType type,
@NotNull String name,
@Nullable PyExpression location,
@NotNull AccessDirection direction,
@NotNull PyResolveContext context) {
return null;
}
protected void registerSelfReturnType(@NotNull String classQualifiedName, @NotNull Collection<String> methods) {
registerReturnType(classQualifiedName, methods, mySelfTypeCallback);
}

View File

@@ -0,0 +1,14 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.psi.types
import com.intellij.psi.PsiElement
import com.intellij.psi.ResolveResult
class PyTypedResolveResult(private val el: PsiElement?, val type: PyType?) : ResolveResult {
override fun getElement(): PsiElement? {
return el
}
override fun isValidResult(): Boolean {
return element != null
}
}