diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py b/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py
index 59e78f140254..60ba0ac8d049 100644
--- a/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py
+++ b/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py
@@ -58,10 +58,9 @@ each command has a format:
* PYDB - pydevd, the python end
'''
-from _pydev_imps import _pydev_threading as threading
+from _pydev_bundle.pydev_imports import _queue
from _pydev_imps import _pydev_time as time, _pydev_thread
from _pydev_imps._pydev_socket import socket, AF_INET, SOCK_STREAM, SHUT_RD, SHUT_WR
-from _pydev_bundle.pydev_imports import _queue
from _pydevd_bundle.pydevd_constants import * #@UnusedWildImport
try:
@@ -115,10 +114,9 @@ CMD_REMOVE_DJANGO_EXCEPTION_BREAK = 126
CMD_SET_NEXT_STATEMENT = 127
CMD_SMART_STEP_INTO = 128
CMD_EXIT = 129
+
CMD_SIGNATURE_CALL_TRACE = 130
-
-
CMD_SET_PY_EXCEPTION = 131
CMD_GET_FILE_CONTENTS = 132
CMD_SET_PROPERTY_TRACE = 133
@@ -142,35 +140,36 @@ CMD_RETURN = 502
CMD_ERROR = 901
ID_TO_MEANING = {
- '101':'CMD_RUN',
- '102':'CMD_LIST_THREADS',
- '103':'CMD_THREAD_CREATE',
- '104':'CMD_THREAD_KILL',
- '105':'CMD_THREAD_SUSPEND',
- '106':'CMD_THREAD_RUN',
- '107':'CMD_STEP_INTO',
- '108':'CMD_STEP_OVER',
- '109':'CMD_STEP_RETURN',
- '110':'CMD_GET_VARIABLE',
- '111':'CMD_SET_BREAK',
- '112':'CMD_REMOVE_BREAK',
- '113':'CMD_EVALUATE_EXPRESSION',
- '114':'CMD_GET_FRAME',
- '115':'CMD_EXEC_EXPRESSION',
- '116':'CMD_WRITE_TO_CONSOLE',
- '117':'CMD_CHANGE_VARIABLE',
- '118':'CMD_RUN_TO_LINE',
- '119':'CMD_RELOAD_CODE',
- '120':'CMD_GET_COMPLETIONS',
- '121':'CMD_CONSOLE_EXEC',
- '122':'CMD_ADD_EXCEPTION_BREAK',
- '123':'CMD_REMOVE_EXCEPTION_BREAK',
- '124':'CMD_LOAD_SOURCE',
- '125':'CMD_ADD_DJANGO_EXCEPTION_BREAK',
- '126':'CMD_REMOVE_DJANGO_EXCEPTION_BREAK',
- '127':'CMD_SET_NEXT_STATEMENT',
- '128':'CMD_SMART_STEP_INTO',
+ '101': 'CMD_RUN',
+ '102': 'CMD_LIST_THREADS',
+ '103': 'CMD_THREAD_CREATE',
+ '104': 'CMD_THREAD_KILL',
+ '105': 'CMD_THREAD_SUSPEND',
+ '106': 'CMD_THREAD_RUN',
+ '107': 'CMD_STEP_INTO',
+ '108': 'CMD_STEP_OVER',
+ '109': 'CMD_STEP_RETURN',
+ '110': 'CMD_GET_VARIABLE',
+ '111': 'CMD_SET_BREAK',
+ '112': 'CMD_REMOVE_BREAK',
+ '113': 'CMD_EVALUATE_EXPRESSION',
+ '114': 'CMD_GET_FRAME',
+ '115': 'CMD_EXEC_EXPRESSION',
+ '116': 'CMD_WRITE_TO_CONSOLE',
+ '117': 'CMD_CHANGE_VARIABLE',
+ '118': 'CMD_RUN_TO_LINE',
+ '119': 'CMD_RELOAD_CODE',
+ '120': 'CMD_GET_COMPLETIONS',
+ '121': 'CMD_CONSOLE_EXEC',
+ '122': 'CMD_ADD_EXCEPTION_BREAK',
+ '123': 'CMD_REMOVE_EXCEPTION_BREAK',
+ '124': 'CMD_LOAD_SOURCE',
+ '125': 'CMD_ADD_DJANGO_EXCEPTION_BREAK',
+ '126': 'CMD_REMOVE_DJANGO_EXCEPTION_BREAK',
+ '127': 'CMD_SET_NEXT_STATEMENT',
+ '128': 'CMD_SMART_STEP_INTO',
'129': 'CMD_EXIT',
+
'130': 'CMD_SIGNATURE_CALL_TRACE',
'131': 'CMD_SET_PY_EXCEPTION',
@@ -184,14 +183,13 @@ ID_TO_MEANING = {
'139': 'CMD_SEND_CURR_EXCEPTION_TRACE_PROCEEDED',
'140': 'CMD_IGNORE_THROWN_EXCEPTION_AT',
'141': 'CMD_ENABLE_DONT_TRACE',
+ '143': 'CMD_GET_ARRAY',
+ '144': 'CMD_STEP_INTO_MY_CODE',
+ '145': 'CMD_GET_CONCURRENCY_EVENT',
- '501':'CMD_VERSION',
- '502':'CMD_RETURN',
- '901':'CMD_ERROR',
-
- '143':'CMD_GET_ARRAY',
- '144':'CMD_STEP_INTO_MY_CODE',
- '145':'CMD_GET_CONCURRENCY_EVENT',
+ '501': 'CMD_VERSION',
+ '502': 'CMD_RETURN',
+ '901': 'CMD_ERROR',
}
MAX_IO_MSG_SIZE = 1000 #if the io is too big, we'll not send all (could make the debugger too non-responsive)
diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py b/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py
index b449dee9c89d..f03c41b44173 100644
--- a/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py
+++ b/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py
@@ -13,13 +13,14 @@ from _pydevd_bundle.pydevd_comm import CMD_STEP_CAUGHT_EXCEPTION, CMD_STEP_RETUR
from _pydevd_bundle.pydevd_constants import STATE_SUSPEND, dict_contains, get_thread_id, STATE_RUN, dict_iter_values
from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, just_raised
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame
+
try:
from inspect import CO_GENERATOR
except:
CO_GENERATOR = 0
try:
- from _pydevd_bundle.pydevd_signature import send_signature_call_trace
+ from _pydevd_bundle.pydevd_signature import send_signature_call_trace, send_signature_return_trace
except ImportError:
def send_signature_call_trace(*args, **kwargs):
pass
@@ -80,6 +81,12 @@ class PyDBFrame: # No longer cdef because object was dying when only a reference
return self.trace_exception
+ def trace_return(self, frame, event, arg):
+ if event == 'return':
+ main_debugger, filename, info, thread = self._args
+ send_signature_return_trace(main_debugger, frame, filename, arg)
+ return self.trace_return
+
# IFDEF CYTHON
# def should_stop_on_exception(self, frame, str event, arg):
# cdef PyDBAdditionalThreadInfo info;
@@ -274,12 +281,15 @@ class PyDBFrame: # No longer cdef because object was dying when only a reference
try:
# print 'frame trace_dispatch', frame.f_lineno, frame.f_code.co_name, event
info.is_tracing = True
+ need_trace_return = False
if main_debugger._finish_debugging_session:
return None
if event == 'call' and main_debugger.signature_factory:
- send_signature_call_trace(main_debugger, frame, filename)
+ need_trace_return = send_signature_call_trace(main_debugger, frame, filename)
+ if event == 'return' and main_debugger.signature_factory:
+ send_signature_return_trace(main_debugger, frame, filename, arg)
plugin_manager = main_debugger.plugin
@@ -341,7 +351,10 @@ class PyDBFrame: # No longer cdef because object was dying when only a reference
if has_exception_breakpoints:
return self.trace_exception
else:
- return None
+ if need_trace_return:
+ return self.trace_return
+ else:
+ return None
else:
#checks the breakpoint to see if there is a context match in some function
@@ -361,7 +374,10 @@ class PyDBFrame: # No longer cdef because object was dying when only a reference
if has_exception_breakpoints:
return self.trace_exception
else:
- return None
+ if need_trace_return:
+ return self.trace_return
+ else:
+ return None
#We may have hit a breakpoint or we are already in step mode. Either way, let's check what we should do in this frame
diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_signature.py b/python/helpers/pydev/_pydevd_bundle/pydevd_signature.py
index e529994048d0..5af54c8098a3 100644
--- a/python/helpers/pydev/_pydevd_bundle/pydevd_signature.py
+++ b/python/helpers/pydev/_pydevd_bundle/pydevd_signature.py
@@ -19,6 +19,7 @@ class Signature(object):
self.name = name
self.args = []
self.args_str = []
+ self.return_type = None
def add_arg(self, name, type):
self.args.append((name, type))
@@ -28,32 +29,39 @@ class Signature(object):
return "%s %s(%s)"%(self.file, self.name, ", ".join(self.args_str))
+def get_type_of_value(value, ignore_module_name=('__main__', '__builtin__', 'builtins')):
+ tp = type(value)
+ class_name = tp.__name__
+ if class_name == 'instance': # old-style classes
+ tp = value.__class__
+ class_name = tp.__name__
+
+ if hasattr(tp, '__module__') and tp.__module__ and tp.__module__ not in ignore_module_name:
+ class_name = "%s.%s"%(tp.__module__, class_name)
+
+ return class_name
+
+
class SignatureFactory(object):
def __init__(self):
self._caller_cache = {}
- self._ignore_module_name = ('__main__', '__builtin__', 'builtins')
+ self.cache = CallSignatureCache()
def is_in_scope(self, filename):
return not pydevd_utils.not_in_project_roots(filename)
- def create_signature(self, frame):
+ def create_signature(self, frame, with_args=True):
try:
code = frame.f_code
locals = frame.f_locals
filename, modulename, funcname = self.file_module_function_of(frame)
res = Signature(filename, funcname)
- for i in xrange(0, code.co_argcount):
- name = code.co_varnames[i]
- tp = type(locals[name])
- class_name = tp.__name__
- if class_name == 'instance': # old-style classes
- tp = locals[name].__class__
- class_name = tp.__name__
-
- if hasattr(tp, '__module__') and tp.__module__ and tp.__module__ not in self._ignore_module_name:
- class_name = "%s.%s"%(tp.__module__, class_name)
-
- res.add_arg(name, class_name)
+ if with_args:
+ for i in xrange(0, code.co_argcount):
+ name = code.co_varnames[i]
+ class_name = get_type_of_value(locals[name])
+
+ res.add_arg(name, class_name)
return res
except:
import traceback
@@ -109,6 +117,41 @@ class SignatureFactory(object):
return filename, modulename, funcname
+
+def get_signature_info(signature):
+ return signature.file, signature.name, ' '.join([arg[1]for arg in signature.args])
+
+
+def get_frame_info(frame):
+ co = frame.f_code
+ return co.co_name, frame.f_lineno, co.co_filename
+
+
+class CallSignatureCache(object):
+ def __init__(self):
+ self.cache = {}
+
+ def add(self, signature):
+ filename, name, args_type = get_signature_info(signature)
+
+ if not filename in self.cache:
+ self.cache[filename] = {}
+
+ calls_from_file = self.cache[filename]
+
+ if not name in calls_from_file:
+ calls_from_file[name] = {}
+
+ name_calls = calls_from_file[name]
+ name_calls[args_type] = None
+
+ def is_in_cache(self, signature):
+ filename, name, args_type = get_signature_info(signature)
+ if filename in self.cache and name in self.cache[filename] and args_type in self.cache[filename][name]:
+ return True
+ return False
+
+
def create_signature_message(signature):
cmdTextList = [""]
@@ -116,14 +159,40 @@ def create_signature_message(signature):
for arg in signature.args:
cmdTextList.append('' % (pydevd_vars.make_valid_xml_value(arg[0]), pydevd_vars.make_valid_xml_value(arg[1])))
+
+ if signature.return_type is not None:
+ cmdTextList.append('' % (pydevd_vars.make_valid_xml_value(signature.return_type)))
cmdTextList.append("")
cmdText = ''.join(cmdTextList)
return NetCommand(CMD_SIGNATURE_CALL_TRACE, 0, cmdText)
+
def send_signature_call_trace(dbg, frame, filename):
- if dbg.signature_factory.is_in_scope(filename):
- dbg.writer.add_command(create_signature_message(dbg.signature_factory.create_signature(frame)))
+ if dbg.signature_factory and dbg.signature_factory.is_in_scope(filename):
+ signature = dbg.signature_factory.create_signature(frame)
+ if dbg.signature_factory.cache is not None:
+ if not dbg.signature_factory.cache.is_in_cache(signature):
+ dbg.signature_factory.cache.add(signature)
+ dbg.writer.add_command(create_signature_message(signature))
+ return True
+ else:
+ # we don't send signature if it is cached
+ return False
+ else:
+ dbg.writer.add_command(create_signature_message(signature))
+ return True
+ return False
+
+
+def send_signature_return_trace(dbg, frame, filename, return_value):
+ if dbg.signature_factory and dbg.signature_factory.is_in_scope(filename):
+ signature = dbg.signature_factory.create_signature(frame, with_args=False)
+ signature.return_type = get_type_of_value(return_value)
+ dbg.writer.add_command(create_signature_message(signature))
+ return True
+
+ return False
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PySignature.java b/python/pydevSrc/com/jetbrains/python/debugger/PySignature.java
index a75887aba711..9d13e6421554 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/PySignature.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/PySignature.java
@@ -13,6 +13,8 @@ import java.util.List;
public class PySignature {
private final String myFile;
private final String myFunctionName;
+
+ private NamedParameter myReturnType = null;
private final List myArgs = Lists.newArrayList();
@@ -46,6 +48,22 @@ public class PySignature {
return myArgs;
}
+ public NamedParameter getReturnType() {
+ return myReturnType;
+ }
+
+ public PySignature addReturnType(@Nullable String returnType) {
+ if (StringUtil.isNotEmpty(returnType)) {
+ if (myReturnType != null) {
+ myReturnType.addType(returnType);
+ }
+ else {
+ myReturnType = new NamedParameter("", returnType);
+ }
+ }
+ return this;
+ }
+
@NotNull
public PySignature addAllArgs(@NotNull PySignature signature) {
for (NamedParameter param : signature.getArgs()) {
@@ -71,6 +89,11 @@ public class PySignature {
return null;
}
+ @Nullable
+ public String getReturnTypeQualifiedName() {
+ return myReturnType != null ? myReturnType.getTypeQualifiedName() : null;
+ }
+
public static class NamedParameter {
private final String myName;
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java
index 586d150caf79..94e4adb12a41 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java
@@ -30,6 +30,7 @@ public abstract class AbstractCommand {
public static final int LOAD_SOURCE = 124;
public static final int SMART_STEP_INTO = 128;
public static final int EXIT = 129;
+
public static final int CALL_SIGNATURE_TRACE = 130;
public static final int CMD_SET_PY_EXCEPTION = 131;
@@ -45,6 +46,9 @@ public abstract class AbstractCommand {
public static final int CMD_ENABLE_DONT_TRACE = 141;
public static final int SHOW_CONSOLE = 142;
+ public static final int GET_ARRAY = 143;
+ public static final int STEP_INTO_MY_CODE = 144;
+ public static final int LOG_CONCURRENCY_EVENT = 145;
public static final int ERROR = 901;
@@ -52,9 +56,7 @@ public abstract class AbstractCommand {
public static final String NEW_LINE_CHAR = "@_@NEW_LINE_CHAR@_@";
public static final String TAB_CHAR = "@_@TAB_CHAR@_@";
- public static final int GET_ARRAY = 143;
- public static final int STEP_INTO_MY_CODE = 144;
- public static final int LOG_CONCURRENCY_EVENT = 145;
+
@NotNull private final RemoteDebugger myDebugger;
private final int myCommandCode;
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java
index cbfe858b2dad..ea4d5173b31b 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java
@@ -31,10 +31,15 @@ public class ProtocolParser {
while (reader.hasMoreChildren()) {
reader.moveDown();
- if (!"arg".equals(reader.getNodeName())) {
- throw new PyDebuggerException("Expected , found " + reader.getNodeName());
+ if ("arg".equals(reader.getNodeName())) {
+ signature.addArgument(readString(reader, "name", ""), readString(reader, "type", ""));
+ } else
+ if ("return".equals(reader.getNodeName())) {
+ signature.addReturnType(readString(reader, "type", ""));
+ } else {
+ throw new PyDebuggerException("Expected or , found " + reader.getNodeName());
}
- signature.addArgument(readString(reader, "name", ""), readString(reader, "type", ""));
+
reader.moveUp();
}
diff --git a/python/src/com/jetbrains/python/debugger/PyCallSignatureTypeProvider.java b/python/src/com/jetbrains/python/debugger/PyCallSignatureTypeProvider.java
index 57d37479c444..984f9017f341 100644
--- a/python/src/com/jetbrains/python/debugger/PyCallSignatureTypeProvider.java
+++ b/python/src/com/jetbrains/python/debugger/PyCallSignatureTypeProvider.java
@@ -16,6 +16,7 @@
package com.jetbrains.python.debugger;
import com.intellij.openapi.util.Ref;
+import com.jetbrains.python.psi.PyCallable;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyNamedParameter;
import com.jetbrains.python.psi.types.*;
@@ -25,6 +26,7 @@ import org.jetbrains.annotations.NotNull;
* @author traff
*/
public class PyCallSignatureTypeProvider extends PyTypeProviderBase {
+ @Override
public Ref getParameterType(@NotNull final PyNamedParameter param,
@NotNull final PyFunction func,
@NotNull TypeEvalContext context) {
@@ -34,8 +36,25 @@ public class PyCallSignatureTypeProvider extends PyTypeProviderBase {
if (typeName != null) {
final PyType type = PyTypeParser.getTypeByName(param, typeName);
if (type != null) {
- final PyType evaluatedType = PyDynamicallyEvaluatedType.create(type);
- return Ref.create(evaluatedType);
+ return Ref.create(PyDynamicallyEvaluatedType.create(type));
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Ref getReturnType(@NotNull final PyCallable callable, @NotNull TypeEvalContext context) {
+ if (callable instanceof PyFunction) {
+ PyFunction function = (PyFunction)callable;
+ PySignature signature = PySignatureCacheManager.getInstance(function.getProject()).findSignature(function);
+ if (signature != null) {
+ final String typeName = signature.getReturnType().getTypeQualifiedName();
+ if (typeName != null) {
+ final PyType type = PyTypeParser.getTypeByName(function, typeName);
+ if (type != null) {
+ return Ref.create(PyDynamicallyEvaluatedType.create(type));
+ }
}
}
}
diff --git a/python/src/com/jetbrains/python/debugger/PySignatureCacheManagerImpl.java b/python/src/com/jetbrains/python/debugger/PySignatureCacheManagerImpl.java
index 49ab3ad28202..cbb5bef6f6d0 100644
--- a/python/src/com/jetbrains/python/debugger/PySignatureCacheManagerImpl.java
+++ b/python/src/com/jetbrains/python/debugger/PySignatureCacheManagerImpl.java
@@ -104,7 +104,7 @@ public class PySignatureCacheManagerImpl extends PySignatureCacheManager {
else {
//noinspection ConstantConditions
lines[i] = signatureToString(stringToSignature(file.
- getCanonicalPath(), lines[i]).addAllArgs(signature));
+ getCanonicalPath(), lines[i]).addAllArgs(signature).addReturnType(signature.getReturnTypeQualifiedName()));
}
}
i++;
@@ -140,7 +140,9 @@ public class PySignatureCacheManagerImpl extends PySignatureCacheManager {
}
private static String signatureToString(PySignature signature) {
- return signature.getFunctionName() + "\t" + StringUtil.join(arguments(signature), "\t");
+ return signature.getFunctionName() + "\t" + StringUtil.join(arguments(signature), "\t") +
+ (signature.getReturnType() != null
+ ? "\t" + StringUtil.join(signature.getReturnType().getTypesList(), "\t") : "");
}
private static List arguments(PySignature signature) {
@@ -248,8 +250,11 @@ public class PySignatureCacheManagerImpl extends PySignatureCacheManager {
if (var.length == 2) {
signature = signature.addArgument(var[0], var[1]);
}
+ else if (var.length == 1) {
+ signature = signature.addReturnType(var[0]);
+ }
else {
- throw new IllegalStateException("Should be : format. " + parts[i] + " instead.");
+ throw new IllegalStateException("Should be : format for arg or for return type; '" + parts[i] + "' instead.");
}
}
return signature;
@@ -259,7 +264,7 @@ public class PySignatureCacheManagerImpl extends PySignatureCacheManager {
@Nullable
private static VirtualFile getFile(@NotNull PySignature signature) {
- return LocalFileSystem.getInstance().findFileByPath(signature.getFile());
+ return LocalFileSystem.getInstance().refreshAndFindFileByPath(signature.getFile());
}
@Nullable
@@ -291,7 +296,7 @@ public class PySignatureCacheManagerImpl extends PySignatureCacheManager {
}
});
}
- }, "Cleaning the cache of dynamically collected types", true, myProject);
+ }, "Cleaning the Cache of Dynamically Collected Types", true, myProject);
String message;
diff --git a/python/src/com/jetbrains/python/documentation/docstrings/PyDocstringGenerator.java b/python/src/com/jetbrains/python/documentation/docstrings/PyDocstringGenerator.java
index bd40187d1ee0..9e70d29a26eb 100644
--- a/python/src/com/jetbrains/python/documentation/docstrings/PyDocstringGenerator.java
+++ b/python/src/com/jetbrains/python/documentation/docstrings/PyDocstringGenerator.java
@@ -275,7 +275,8 @@ public class PyDocstringGenerator {
if (param.getType() == null) {
String type;
if (signature != null) {
- type = StringUtil.notNullize(signature.getArgTypeQualifiedName(param.getName()));
+ type = StringUtil.notNullize(param.isReturnValue() ? signature.getReturnTypeQualifiedName() :
+ signature.getArgTypeQualifiedName(param.getName()));
}
else {
type = paramTypes.get(paramCoordinates);
diff --git a/python/src/com/jetbrains/python/psi/types/PyDynamicallyEvaluatedType.java b/python/src/com/jetbrains/python/psi/types/PyDynamicallyEvaluatedType.java
index 4dff5a00d886..5e961060c1a1 100644
--- a/python/src/com/jetbrains/python/psi/types/PyDynamicallyEvaluatedType.java
+++ b/python/src/com/jetbrains/python/psi/types/PyDynamicallyEvaluatedType.java
@@ -15,7 +15,6 @@
*/
package com.jetbrains.python.psi.types;
-import com.intellij.openapi.project.Project;
import com.jetbrains.python.PyNames;
import org.jetbrains.annotations.NotNull;
diff --git a/python/testData/dynamicTypes/1.py b/python/testData/dynamicTypes/1.py
new file mode 100644
index 000000000000..e7c846b2c640
--- /dev/null
+++ b/python/testData/dynamicTypes/1.py
@@ -0,0 +1,4 @@
+def foo(x):
+ return x
+
+foo(1)
\ No newline at end of file
diff --git a/python/testData/dynamicTypes/1_after.py b/python/testData/dynamicTypes/1_after.py
new file mode 100644
index 000000000000..e647aea92013
--- /dev/null
+++ b/python/testData/dynamicTypes/1_after.py
@@ -0,0 +1,11 @@
+def foo(x):
+ """
+
+ :param x:
+ :type x: int
+ :return:
+ :rtype: int
+ """
+ return x
+
+foo(1)
\ No newline at end of file
diff --git a/python/testSrc/com/jetbrains/env/python/PyDynamicTypesTest.java b/python/testSrc/com/jetbrains/env/python/PyDynamicTypesTest.java
new file mode 100644
index 000000000000..4d0922cbd5d4
--- /dev/null
+++ b/python/testSrc/com/jetbrains/env/python/PyDynamicTypesTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.env.python;
+
+import com.google.common.collect.Lists;
+import com.intellij.codeInsight.intention.IntentionAction;
+import com.intellij.openapi.editor.LogicalPosition;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.testFramework.EditorTestUtil;
+import com.jetbrains.env.PyEnvTestCase;
+import com.jetbrains.env.python.debug.PyDebuggerTask;
+import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.debugger.PyDebuggerOptionsProvider;
+import com.jetbrains.python.debugger.PySignatureCacheManagerImpl;
+
+import java.io.IOException;
+
+/**
+ * @author traff
+ */
+
+public class PyDynamicTypesTest extends PyEnvTestCase {
+ public void test1() throws Exception {
+ doTest(getTestName(true) + ".py");
+ }
+
+ private void doTest(final String scriptName) {
+ runPythonTest(new PyDebuggerTask("/" + "dynamicTypes", scriptName) {
+ @Override
+ public void setUp(String testName) throws Exception {
+ super.setUp(testName);
+ }
+
+ @Override
+ public void before() throws Exception {
+ PyDebuggerOptionsProvider.getInstance(myFixture.getProject()).setSaveCallSignatures(true);
+ }
+
+ @Override
+ public void after() throws Exception {
+ PyDebuggerOptionsProvider.getInstance(myFixture.getProject()).setSaveCallSignatures(false);
+ }
+
+ @Override
+ public void testing() throws Exception {
+ waitForTerminate();
+
+ edt(() -> {
+ myFixture.configureByFile("dynamicTypes/" + scriptName);
+
+ try {
+ //copy signature attributes from real file to temporary test file
+ PySignatureCacheManagerImpl.CALL_SIGNATURES_ATTRIBUTE.writeAttributeBytes(myFixture.getFile().getVirtualFile(),
+ PySignatureCacheManagerImpl.CALL_SIGNATURES_ATTRIBUTE
+ .readAttributeBytes(LocalFileSystem.getInstance()
+ .refreshAndFindFileByPath(
+ getTestDataPath() +
+ "/dynamicTypes/" +
+ scriptName)));
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ EditorTestUtil.setCaretsAndSelection(myFixture.getEditor(), new EditorTestUtil.CaretAndSelectionState(
+ Lists.newArrayList(new EditorTestUtil.CaretInfo(new LogicalPosition(0, 6), null)), null));
+ final IntentionAction action = myFixture.findSingleIntention(PyBundle.message("INTN.doc.string.stub"));
+ myFixture.launchAction(action);
+ myFixture.checkResultByFile("dynamicTypes/" + getTestName(true) + "_after.py");
+ });
+ }
+ });
+ }
+}
+