mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-36889 Type check assignments to class/instance attributes outside of class bodies
Previously, PyTypingTypeProvider.getReferenceType returned a type from a class attribute
type hint only for assignment to instance attribute located inside a class definition.
In other words, here we inferred the expected type from the annotation
and reported incompatible types in assignment:
```python
class C:
attr: int
def m(self):
self.attr = "foo"
```
but in the following we didn't:
```python
class C:
attr: int
inst = C()
inst.attr = "foo"
```
Now we try to resolve any qualified target expression to a class
or instance attribute and then infer the type from the corresponding
annotation.
(cherry picked from commit 086dbb678a8cd89cfe332bf801631568fb6c3a4d)
IJ-MR-147382
GitOrigin-RevId: 4e3f71baa598d4caf684d0aeab23d1a9a688b94d
This commit is contained in:
committed by
intellij-monorepo-bot
parent
24284fe20f
commit
99a6645e5d
+31
-5
@@ -511,11 +511,6 @@ public final class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<
|
||||
return Ref.create(typedDictType);
|
||||
}
|
||||
|
||||
final Ref<PyType> annotatedType = getTypeFromTargetExpressionAnnotation(target, context);
|
||||
if (annotatedType != null) {
|
||||
return annotatedType;
|
||||
}
|
||||
|
||||
final PyExpression assignedValue = PyTypingAliasStubType.getAssignedValueStubLike(target);
|
||||
if (assignedValue != null) {
|
||||
final PyType type = getTypeParameterTypeFromDeclaration(assignedValue, context);
|
||||
@@ -524,6 +519,37 @@ public final class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<
|
||||
}
|
||||
}
|
||||
|
||||
// Return a type from an immediate type hint, e.g. from a syntactic annotation for
|
||||
//
|
||||
// x: int = ...
|
||||
//
|
||||
// or find the "root" declaration and get a type from a type hint there, e.g.
|
||||
//
|
||||
// x: int
|
||||
// x = ...
|
||||
//
|
||||
// or
|
||||
//
|
||||
// class C:
|
||||
// attr: int
|
||||
// def __init__(self, x):
|
||||
// self.attr = x
|
||||
//
|
||||
// self.attr = ...
|
||||
|
||||
// assignments "inst.attr = ..." are not preserved in stubs anyway. See PyTargetExpressionElementType.shouldCreateStub.
|
||||
if (target.isQualified() && context.myContext.maySwitchToAST(target)) {
|
||||
PsiElement resolved = target.getReference(PyResolveContext.defaultContext(context.myContext)).resolve();
|
||||
if (resolved instanceof PyTargetExpression resolvedTarget && PyUtil.isAttribute(resolvedTarget)) {
|
||||
target = resolvedTarget;
|
||||
}
|
||||
}
|
||||
|
||||
final Ref<PyType> annotatedType = getTypeFromTargetExpressionAnnotation(target, context);
|
||||
if (annotatedType != null) {
|
||||
return annotatedType;
|
||||
}
|
||||
|
||||
final String name = target.getReferencedName();
|
||||
final ScopeOwner scopeOwner = ScopeUtil.getScopeOwner(target);
|
||||
if (name == null || scopeOwner == null) {
|
||||
|
||||
@@ -18,4 +18,4 @@ class C:
|
||||
self.class_var = 1
|
||||
self.class_var = <warning descr="Expected type 'int', got 'str' instead">'bar'</warning>
|
||||
C.class_var = 1
|
||||
C.class_var = 'bar'
|
||||
C.class_var = <warning descr="Expected type 'int', got 'str' instead">'bar'</warning>
|
||||
|
||||
@@ -358,6 +358,46 @@ public class Py3TypeCheckerInspectionTest extends PyInspectionTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-36889
|
||||
public void testInstanceAndClassAttributeAssignment() {
|
||||
doTestByText("""
|
||||
from typing import ClassVar
|
||||
|
||||
class ClassAnnotations:
|
||||
attr: int
|
||||
class_attr: ClassVar[int]
|
||||
|
||||
ClassAnnotations().attr = <warning descr="Expected type 'int', got 'str' instead">"foo"</warning>
|
||||
ClassAnnotations.class_attr = <warning descr="Expected type 'int', got 'str' instead">"foo"</warning>
|
||||
|
||||
class ClassAnnotationInstanceAssignment:
|
||||
attr: int
|
||||
def __init__(self, x):
|
||||
self.attr = x
|
||||
|
||||
ClassAnnotationInstanceAssignment(42).attr = <warning descr="Expected type 'int', got 'str' instead">"foo"</warning>
|
||||
|
||||
class InstanceAnnotationAndAssignment:
|
||||
def __init__(self):
|
||||
self.attr: int = 42
|
||||
|
||||
InstanceAnnotationAndAssignment().attr = <warning descr="Expected type 'int', got 'str' instead">"foo"</warning>
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-36889
|
||||
public void testDataclassInstanceAssignment() {
|
||||
doTestByText("""
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class C:
|
||||
attr: int
|
||||
|
||||
C().attr = <warning descr="Expected type 'int', got 'str' instead">"foo"</warning>
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-24832
|
||||
public void testNoTypeMismatchInAssignmentWithoutTypeAnnotation() {
|
||||
doTest();
|
||||
|
||||
Reference in New Issue
Block a user