marcus.mews/PY-55691

PY-55691: `Expected type 'Type[AttrsInstance]'` warning passing `attrs` class to `attrs.fields()`

- add member __attrs_attrs__ to classes decorated with attrs.define or similar

Merge-request: IJ-MR-165647
Merged-by: Marcus Mews <marcus.mews@jetbrains.com>

GitOrigin-RevId: 979f894e595d903c55f33c8bd4c5c3d06cb2fe6d
This commit is contained in:
Marcus Mews
2025-06-18 21:08:01 +00:00
committed by intellij-monorepo-bot
parent 84dc30fe02
commit cb63214bc9
5 changed files with 116 additions and 0 deletions
@@ -563,6 +563,7 @@
<inspectionExtension implementation="com.jetbrains.python.codeInsight.typing.PyTypingInspectionExtension"/>
<customPackageIdentifier implementation="com.jetbrains.python.pyi.PyiCustomPackageIdentifier"/>
<knownDecoratorProvider implementation="com.jetbrains.python.psi.PyStdKnownDecoratorProvider"/>
<pyClassMembersProvider implementation="com.jetbrains.python.codeInsight.PyAttrsClassMembersProvider"/>
<!-- IPython -->
<pyReferenceResolveProvider implementation="com.jetbrains.python.psi.resolve.PyIPythonBuiltinReferenceResolveProvider"/>
@@ -0,0 +1,37 @@
// Copyright 2000-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.codeInsight;
import com.intellij.psi.PsiElement;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.types.PyClassMembersProviderBase;
import com.jetbrains.python.psi.types.PyClassType;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static com.jetbrains.python.codeInsight.PyDataclassesKt.parseDataclassParameters;
/**
* Adds member __attrs_attrs__ caused by decorator @attrs.define
*/
public final class PyAttrsClassMembersProvider extends PyClassMembersProviderBase {
@Override
public @NotNull Collection<PyCustomMember> getMembers(PyClassType clazz, PsiElement location, @NotNull TypeEvalContext context) {
PyClass pyClass = clazz.getPyClass();
PyDataclassParameters dataclassParameters = parseDataclassParameters(pyClass, context);
boolean hasAttrs = dataclassParameters != null && dataclassParameters.getType() == PyDataclassParameters.PredefinedType.ATTRS;
if (hasAttrs) {
PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(pyClass);
PyClass objectClass = builtinCache.getClass(PyNames.OBJECT);
return List.of(new PyCustomMember("__attrs_attrs__", objectClass));
}
return Collections.emptyList();
}
}
@@ -389,6 +389,36 @@ public class Py3CompletionTest extends PyTestCase {
);
}
// PY-55691
public void testAttrsClassMembersProvider() {
runWithAdditionalClassEntryInSdkRoots(
"../packages",
() -> {
doTestByText(
"""
import attrs
@attrs.define
class User:
password: str
User().__at<caret>"""
);
myFixture.checkResult(
"""
import attrs
@attrs.define
class User:
password: str
User().__attrs_attrs__"""
);
}
);
}
//PY-28332
public void testImportNamespacePackageInMultipleRoots() {
doMultiFileTest(Arrays.asList("root1/src", "root2/src"));
@@ -2894,4 +2894,35 @@ def foo(param: str | int) -> TypeGuard[str]:
f(<warning descr="Expected type 'Literal[1]', got 'Literal[2]' instead">a=2</warning>)
""");
}
// PY-55691
public void testAttrsDataclassProtocolMatchingDefine() {
runWithAdditionalClassEntryInSdkRoots("packages", () ->
doTestByText("""
import attrs
@attrs.define
class User:
password: str
attrs.fields(User)
""")
);
}
// PY-55691
public void testAttrsDataclassProtocolMatchingFrozen() {
runWithAdditionalClassEntryInSdkRoots("packages", () ->
doTestByText("""
import attrs
@attrs.frozen
class User:
password: str
attrs.fields(User)
""")
);
}
}
@@ -447,4 +447,21 @@ public class Py3UnresolvedReferencesInspectionTest extends PyInspectionTestCase
x.<warning descr="Cannot find reference 'is_integer' in 'T'">is_integer</warning>() # E
""");
}
// PY-55691
public void testAttrsClassMembersProviderAttrsProperty() {
runWithAdditionalClassEntryInSdkRoots("packages", () ->
doTestByText(
"""
import attrs
@attrs.define
class User:
password: str
User().__attrs_attrs__ # OK
""")
);
}
}