Make return type weak for functions decorated with unknown decorator (PY-28626)

Or decorator that could change return type.
This commit is contained in:
Semyon Proshev
2018-03-20 17:28:58 +03:00
parent b4b4b3c0f6
commit 68b17b57c0
13 changed files with 42 additions and 10 deletions

View File

@@ -67,10 +67,7 @@ public class PyNoneFunctionAssignmentInspection extends PyInspection {
}
if (callable instanceof PyFunction) {
final PyFunction function = (PyFunction)callable;
// Currently we don't infer types returned by decorators
if (hasInheritors(function) ||
PyKnownDecoratorUtil.hasUnknownOrChangingReturnTypeDecorator(function, myTypeEvalContext) ||
PyKnownDecoratorUtil.hasAbstractDecorator(function, myTypeEvalContext)) {
if (hasInheritors(function) || PyKnownDecoratorUtil.hasAbstractDecorator(function, myTypeEvalContext)) {
return true;
}
}

View File

@@ -216,7 +216,12 @@ public class PyKnownDecoratorUtil {
public static boolean hasUnknownOrChangingReturnTypeDecorator(@NotNull PyDecoratable decoratable, @NotNull TypeEvalContext context) {
final List<KnownDecorator> decorators = getKnownDecorators(decoratable, context);
return !allDecoratorsAreKnown(decoratable, decorators) || decorators.contains(UNITTEST_MOCK_PATCH);
if (!allDecoratorsAreKnown(decoratable, decorators)) {
return true;
}
return ContainerUtil.exists(decorators, d -> d == UNITTEST_MOCK_PATCH || d == CONTEXTLIB_CONTEXTMANAGER);
}
public static boolean hasUnknownOrUpdatingAttributesDecorator(@NotNull PyDecoratable decoratable, @NotNull TypeEvalContext context) {

View File

@@ -217,6 +217,10 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
}
}
if (getProperty() == null && PyKnownDecoratorUtil.hasUnknownOrChangingReturnTypeDecorator(this, context)) {
inferredType = PyUnionType.createWeakType(inferredType);
}
return PyTypingTypeProvider.toAsyncIfNeeded(this, inferredType);
}

View File

@@ -0,0 +1,13 @@
from contextlib import contextmanager
@contextmanager
def mycm():
pass
yield
pass
@mycm()
def decorated_func():
pass

View File

@@ -0,0 +1,2 @@
def contextmanager(func):
pass

View File

@@ -1,3 +1,6 @@
from abc import abstractmethod
class Abstract(object):
@abstractmethod

View File

@@ -1,3 +1,6 @@
from abc import abstractmethod
class Abstract(object):
@abstractmethod

View File

@@ -1,3 +1,3 @@
@decorator1, @decorator2
def foo(param)
Inferred type: (param:&nbsp;Any)&nbsp;-&gt;&nbsp;None
Inferred type: (param:&nbsp;Any)&nbsp;-&gt;&nbsp;Optional[Any]

View File

@@ -1,3 +1,3 @@
@decorator
def foo(param)
Inferred type: (param:&nbsp;Any)&nbsp;-&gt;&nbsp;None
Inferred type: (param:&nbsp;Any)&nbsp;-&gt;&nbsp;Optional[Any]

View File

@@ -1 +1 @@
<html><body><code>@<i>decorator1</i><br>@<i>decorator2</i><br>def&nbsp;<b>foo</b>(param)<br>Inferred&nbsp;type:&nbsp;(param:&nbsp;Any)&nbsp;-&gt;&nbsp;None</code></body></html>
<html><body><code>@<i>decorator1</i><br>@<i>decorator2</i><br>def&nbsp;<b>foo</b>(param)<br>Inferred&nbsp;type:&nbsp;(param:&nbsp;Any)&nbsp;-&gt;&nbsp;Optional[Any]</code></body></html>

View File

@@ -1 +1 @@
<html><body><code><small>class <a href="psi_element://#class#">Foo</a></small><br><br>@<i>deco</i><br>def&nbsp;<b>meth</b>(self)<br>Inferred&nbsp;type:&nbsp;(self:&nbsp;<a href="psi_element://#typename#Foo">Foo</a>)&nbsp;-&gt;&nbsp;None<br><br>Doc&nbsp;of&nbsp;meth.<br></code></body></html>
<html><body><code><small>class <a href="psi_element://#class#">Foo</a></small><br><br>@<i>deco</i><br>def&nbsp;<b>meth</b>(self)<br>Inferred&nbsp;type:&nbsp;(self:&nbsp;<a href="psi_element://#typename#Foo">Foo</a>)&nbsp;-&gt;&nbsp;Optional[Any]<br><br>Doc&nbsp;of&nbsp;meth.<br></code></body></html>

View File

@@ -1 +1 @@
<html><body><code>@<i>decorator</i><br>def&nbsp;<b>foo</b>(param)<br>Inferred&nbsp;type:&nbsp;(param:&nbsp;Any)&nbsp;-&gt;&nbsp;None</code></body></html>
<html><body><code>@<i>decorator</i><br>def&nbsp;<b>foo</b>(param)<br>Inferred&nbsp;type:&nbsp;(param:&nbsp;Any)&nbsp;-&gt;&nbsp;Optional[Any]</code></body></html>

View File

@@ -108,6 +108,11 @@ public class PyCallingNonCallableInspectionTest extends PyInspectionTestCase {
doTest();
}
// PY-28626
public void testFunctionDecoratedAsContextManager() {
doMultiFileTest();
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {