mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-20 13:31:28 +07:00
PY-9342 Distinguish between function and method types
This commit is contained in:
@@ -1009,6 +1009,8 @@ class ModuleRedeclarator(object):
|
||||
self.classes_buf.out(0, txt)
|
||||
txt = create_function()
|
||||
self.classes_buf.out(0, txt)
|
||||
txt = create_method()
|
||||
self.classes_buf.out(0, txt)
|
||||
|
||||
# Fake <type 'namedtuple'>
|
||||
if version[0] >= 3 or (version[0] == 2 and version[1] >= 6):
|
||||
|
||||
@@ -115,6 +115,25 @@ class __function(object):
|
||||
"""
|
||||
return txt
|
||||
|
||||
def create_method():
|
||||
txt = """
|
||||
class __method(object):
|
||||
'''A mock class representing method type.'''
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
if version[0] == 2:
|
||||
txt += """
|
||||
self.im_class = None
|
||||
self.im_self = None
|
||||
self.im_func = None
|
||||
"""
|
||||
if version[0] >= 3 or (version[0] == 2 and version[1] >= 6):
|
||||
txt += """
|
||||
self.__func__ = None
|
||||
self.__self__ = None
|
||||
"""
|
||||
return txt
|
||||
|
||||
def _searchbases(cls, accum):
|
||||
# logic copied from inspect.py
|
||||
|
||||
@@ -60,6 +60,7 @@ public class PyNames {
|
||||
public static final String FAKE_OLD_BASE = "___Classobj";
|
||||
public static final String FAKE_GENERATOR = "__generator";
|
||||
public static final String FAKE_FUNCTION = "__function";
|
||||
public static final String FAKE_METHOD = "__method";
|
||||
public static final String FAKE_NAMEDTUPLE = "__namedtuple";
|
||||
|
||||
public static final String FUTURE_MODULE = "__future__";
|
||||
@@ -471,4 +472,33 @@ public class PyNames {
|
||||
public static boolean isRightOperatorName(@Nullable String name) {
|
||||
return name != null && name.matches("__r[a-z]+__");
|
||||
}
|
||||
|
||||
/**
|
||||
* Available in Python 3 and Python 2 starting from 2.6.
|
||||
* <p/>
|
||||
* Attributes {@code __doc__}, {@code __dict__} and {@code __module__} should be inherited from object.
|
||||
*/
|
||||
public static final ImmutableSet<String> FUNCTION_SPECIAL_ATTRIBUTES = ImmutableSet.of(
|
||||
"__defaults__",
|
||||
"__globals__",
|
||||
"__closure__",
|
||||
"__code__",
|
||||
"__name__"
|
||||
);
|
||||
|
||||
public static final ImmutableSet<String> LEGACY_FUNCTION_SPECIAL_ATTRIBUTES = ImmutableSet.of(
|
||||
"func_defaults",
|
||||
"func_globals",
|
||||
"func_closure",
|
||||
"func_code",
|
||||
"func_name",
|
||||
"func_doc",
|
||||
"func_dict"
|
||||
);
|
||||
|
||||
public static final ImmutableSet<String> PY3_ONLY_FUNCTION_SPECIAL_ATTRIBUTES = ImmutableSet.of("__annotations__", "__kwdefaults__");
|
||||
|
||||
public static final ImmutableSet<String> METHOD_SPECIAL_ATTRIBUTES = ImmutableSet.of("__func__", "__self__");
|
||||
|
||||
public static final ImmutableSet<String> LEGACY_METHOD_SPECIAL_ATTRIBUTES = ImmutableSet.of("im_func", "im_self", "im_class");
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static com.jetbrains.python.psi.PyFunction.Modifier.STATICMETHOD;
|
||||
import static com.jetbrains.python.psi.PyUtil.as;
|
||||
|
||||
/**
|
||||
* Type of a particular function that is represented as a {@link Callable} in the PSI tree.
|
||||
*
|
||||
@@ -74,20 +77,68 @@ public class PyFunctionType implements PyCallableType {
|
||||
@Nullable PyExpression location,
|
||||
@NotNull AccessDirection direction,
|
||||
@NotNull PyResolveContext resolveContext) {
|
||||
final PyClassTypeImpl functionType = PyBuiltinCache.getInstance(getCallable()).getObjectType(PyNames.FAKE_FUNCTION);
|
||||
if (functionType == null) {
|
||||
final PyClassType delegate = selectFakeType(location, resolveContext.getTypeEvalContext());
|
||||
if (delegate == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return functionType.resolveMember(name, location, direction, resolveContext);
|
||||
return delegate.resolveMember(name, location, direction, resolveContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getCompletionVariants(String completionPrefix, PsiElement location, ProcessingContext context) {
|
||||
final PyClassTypeImpl functionType = PyBuiltinCache.getInstance(getCallable()).getObjectType(PyNames.FAKE_FUNCTION);
|
||||
if (functionType == null) {
|
||||
final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(location.getContainingFile());
|
||||
final PyClassType delegate;
|
||||
if (location instanceof PyReferenceExpression) {
|
||||
delegate = selectFakeType(((PyReferenceExpression)location).getQualifier(), typeEvalContext);
|
||||
}
|
||||
else {
|
||||
delegate = PyBuiltinCache.getInstance(getCallable()).getObjectType(PyNames.FAKE_FUNCTION);
|
||||
}
|
||||
if (delegate == null) {
|
||||
return ArrayUtil.EMPTY_OBJECT_ARRAY;
|
||||
}
|
||||
return functionType.getCompletionVariants(completionPrefix, location, context);
|
||||
return delegate.getCompletionVariants(completionPrefix, location, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select either {@link PyNames#FAKE_FUNCTION} or {@link PyNames#FAKE_METHOD} fake class depending on concrete reference used and
|
||||
* language level. Will fallback to fake function type.
|
||||
*/
|
||||
@Nullable
|
||||
private PyClassTypeImpl selectFakeType(@Nullable PyExpression location, @NotNull TypeEvalContext context) {
|
||||
if (location instanceof PyReferenceExpression && isBoundMethodReference(((PyReferenceExpression)location), context)) {
|
||||
return PyBuiltinCache.getInstance(getCallable()).getObjectType(PyNames.FAKE_METHOD);
|
||||
}
|
||||
return PyBuiltinCache.getInstance(getCallable()).getObjectType(PyNames.FAKE_FUNCTION);
|
||||
}
|
||||
|
||||
private boolean isBoundMethodReference(@NotNull PyReferenceExpression location, @NotNull TypeEvalContext context) {
|
||||
final PyFunction function = as(getCallable(), PyFunction.class);
|
||||
final boolean isNonStaticMethod = function != null && function.getContainingClass() != null && function.getModifier() != STATICMETHOD;
|
||||
if (isNonStaticMethod) {
|
||||
if (LanguageLevel.forElement(location).isOlderThan(LanguageLevel.PYTHON30)) {
|
||||
return true;
|
||||
}
|
||||
if (location.isQualified()) {
|
||||
//noinspection ConstantConditions
|
||||
final PyType qualifierType = PyTypeChecker.toNonWeakType(context.getType(location.getQualifier()), context);
|
||||
if (isInstanceType(qualifierType)) {
|
||||
return true;
|
||||
}
|
||||
else if (qualifierType instanceof PyUnionType) {
|
||||
for (PyType type : ((PyUnionType)qualifierType).getMembers()) {
|
||||
if (isInstanceType(type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isInstanceType(@Nullable PyType type) {
|
||||
return type instanceof PyClassType && !((PyClassType)type).isDefinition();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -652,6 +652,19 @@ class __function(object):
|
||||
self.__name__ = ''
|
||||
|
||||
|
||||
class __method(object):
|
||||
'''A mock class representing method type.'''
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.im_class = None
|
||||
self.im_self = None
|
||||
self.im_func = None
|
||||
|
||||
self.__func__ = None
|
||||
self.__self__ = None
|
||||
|
||||
|
||||
class __namedtuple(tuple):
|
||||
'''A mock base class for named tuples.'''
|
||||
|
||||
|
||||
@@ -613,14 +613,24 @@ class __function(object):
|
||||
self.__dict__ = ''
|
||||
self.__module__ = ''
|
||||
|
||||
self.__annotations__ = {}
|
||||
self.__defaults__ = {}
|
||||
self.__globals__ = {}
|
||||
self.__kwdefaults__ = {}
|
||||
self.__closure__ = None
|
||||
self.__code__ = None
|
||||
self.__name__ = ''
|
||||
|
||||
self.__annotations__ = {}
|
||||
self.__kwdefaults__ = {}
|
||||
|
||||
|
||||
class __method(object):
|
||||
'''A mock class representing method type.'''
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.__func__ = None
|
||||
self.__self__ = None
|
||||
|
||||
|
||||
class __namedtuple(tuple):
|
||||
'''A mock base class for named tuples.'''
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class MyClass(object):
|
||||
def method(self):
|
||||
pass
|
||||
|
||||
MyClass().method.__<caret>
|
||||
1
python/testData/completion/lambdaSpecialAttributes.py
Normal file
1
python/testData/completion/lambdaSpecialAttributes.py
Normal file
@@ -0,0 +1 @@
|
||||
(lambda: 42).__<caret>
|
||||
@@ -0,0 +1,6 @@
|
||||
class MyClass(object):
|
||||
@staticmethod
|
||||
def method(self):
|
||||
pass
|
||||
|
||||
MyClass().method.__<caret>
|
||||
@@ -0,0 +1,5 @@
|
||||
class MyClass(object):
|
||||
def method(self):
|
||||
pass
|
||||
|
||||
MyClass.method.__<caret>
|
||||
@@ -0,0 +1,10 @@
|
||||
class MyClass(object):
|
||||
def method(self):
|
||||
pass
|
||||
|
||||
if True:
|
||||
inst = MyClass()
|
||||
else:
|
||||
inst = unresolved
|
||||
|
||||
inst.method.__<caret>
|
||||
@@ -626,23 +626,80 @@ public class PythonCompletionTest extends PyTestCase {
|
||||
}
|
||||
|
||||
// PY-4073
|
||||
public void testSpecialFunctionAttributes() throws Exception {
|
||||
setLanguageLevel(LanguageLevel.PYTHON27);
|
||||
try {
|
||||
List<String> suggested = doTestByText("def func(): pass; func.func_<caret>");
|
||||
assertNotNull(suggested);
|
||||
assertContainsElements(suggested,
|
||||
"func_defaults", "func_globals", "func_closure",
|
||||
"func_code", "func_name", "func_doc", "func_dict");
|
||||
public void testFunctionSpecialAttributes() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON27, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
List<String> suggested = doTestByText("def func(): pass; func.func_<caret>");
|
||||
assertNotNull(suggested);
|
||||
assertContainsElements(suggested, PyNames.LEGACY_FUNCTION_SPECIAL_ATTRIBUTES);
|
||||
|
||||
suggested = doTestByText("def func(): pass; func.__<caret>");
|
||||
assertNotNull(suggested);
|
||||
assertContainsElements(suggested, "__defaults__", "__globals__", "__closure__",
|
||||
"__code__", "__name__", "__doc__", "__dict__", "__module__");
|
||||
assertDoesntContain(suggested, "__annotations__", "__kwdefaults__");
|
||||
}
|
||||
finally {
|
||||
setLanguageLevel(null);
|
||||
}
|
||||
suggested = doTestByText("def func(): pass; func.__<caret>");
|
||||
assertNotNull(suggested);
|
||||
assertContainsElements(suggested, PyNames.FUNCTION_SPECIAL_ATTRIBUTES);
|
||||
assertDoesntContain(suggested, PyNames.PY3_ONLY_FUNCTION_SPECIAL_ATTRIBUTES);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// PY-9342
|
||||
public void testBoundMethodSpecialAttributes() {
|
||||
List<String> suggested = doTestByText("{}.update.im_<caret>");
|
||||
assertNotNull(suggested);
|
||||
assertContainsElements(suggested, PyNames.LEGACY_METHOD_SPECIAL_ATTRIBUTES);
|
||||
|
||||
suggested = doTestByText("{}.update.__<caret>");
|
||||
assertNotNull(suggested);
|
||||
assertContainsElements(suggested, PyNames.METHOD_SPECIAL_ATTRIBUTES);
|
||||
assertDoesntContain(suggested, PyNames.FUNCTION_SPECIAL_ATTRIBUTES);
|
||||
}
|
||||
|
||||
// PY-9342
|
||||
public void testWeakQualifierBoundMethodAttributes() {
|
||||
assertUnderscoredMethodSpecialAttributesSuggested();
|
||||
}
|
||||
|
||||
private void assertUnderscoredMethodSpecialAttributesSuggested() {
|
||||
myFixture.configureByFile("completion/" + getTestName(true) + ".py");
|
||||
myFixture.completeBasic();
|
||||
final List<String> suggested = myFixture.getLookupElementStrings();
|
||||
assertNotNull(suggested);
|
||||
assertContainsElements(suggested, PyNames.METHOD_SPECIAL_ATTRIBUTES);
|
||||
assertDoesntContain(suggested, PyNames.FUNCTION_SPECIAL_ATTRIBUTES);
|
||||
}
|
||||
|
||||
// PY-9342
|
||||
public void testUnboundMethodSpecialAttributes() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON27, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
assertUnderscoredMethodSpecialAttributesSuggested();
|
||||
}
|
||||
});
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON32, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
assertUnderscoredFunctionAttributesSuggested();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// PY-9342
|
||||
public void testStaticMethodSpecialAttributes() {
|
||||
assertUnderscoredFunctionAttributesSuggested();
|
||||
}
|
||||
|
||||
// PY-9342
|
||||
public void testLambdaSpecialAttributes() {
|
||||
assertUnderscoredFunctionAttributesSuggested();
|
||||
}
|
||||
|
||||
private void assertUnderscoredFunctionAttributesSuggested() {
|
||||
myFixture.configureByFile("completion/" + getTestName(true) + ".py");
|
||||
myFixture.completeBasic();
|
||||
final List<String> suggested = myFixture.getLookupElementStrings();
|
||||
assertNotNull(suggested);
|
||||
assertContainsElements(suggested, PyNames.FUNCTION_SPECIAL_ATTRIBUTES);
|
||||
assertDoesntContain(suggested, PyNames.METHOD_SPECIAL_ATTRIBUTES);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user