55 karma · joined April 8, 2011
https://blogs.msdn.microsoft.com/visualstudio/2017/10/26/run...
One thing I'll point out though, it's a Field on StrongBox<T> for correctness not performance - the field needs to be capable of being passed by reference to get consistently correct semantics. That's simply not possible on .NET native using the interpreter so it will end up with copy in / copy out semantics (which could break people but it's pretty unlikely). Also StrongBox<T> pre-existed the DLR expression compiler and was originally added w/ LINQ's ETs in 3.5 so we were also just re-using what they had already done. IronPython actually had Reference<T> early on which grew into the DLR's version and then finally converged back on StrongBox<T>.
To accomplish that we require that we have symbols for the Python interpreter (which are typically available on python.org). The debug engine uses a combination of the native equivalent of sys.settrace, strategic breakpoints, and using the symbols to walk core Python data structures. There's a bunch of fun tricks for evaluating code when stopped at a Python frame and creating strings, ints, etc... when users type them into the watch window.
If you'd like to dig in more the source is at http://pytools.codeplex.com/SourceControl/latest under Python\Product\Debugger\DkmDebugger.
The priority of this has gone up for us after PEP 405 but we weren't able to get to it for 1.5. You can go to Tools->Options->Python Tools->Interpreter Options and setup a custom interpreter which points to your virtualenv, but we need to make it easier than doing that.
Basically how it works is that we do an abstract interpretation of the code. To take this specific example first we analyze the a = {2:42} line, and the dictionary literal produces a new unique value in the system, and obviously we know it's key/value types. From there we'll analyze the "a.values" which will produce a new value in the system for the bound "values" method. Then we do an abstract call on that, and it says that it returns a list (and goes off to the original dictionary value, and produces a list which contains the int objects). And then we do the indexing, and finally we get back the original 42 value that the dictionary was created with. So basically at each step along the way we are just propagating sets of abstract values. A naive implementation of a system like this would iterate until it hits a fixed point - we're a little more sophisticated in that we do a bunch of dependency tracking to know what needs to be analyzed based upon changes in the system. And then we also need to deal with a user editing the program in real time which makes things even more complicated as we need to discard old stale information which has been propagated from edited files. The actual implementation of the basic Python semantics is actually the easy part, it's getting this to perform on large programs and working correctly with live editing which is the difficult part.
This can go wrong in a couple of ways. First off we don't model control flow, so you could have something like:
if False: a = 42 else: a = 'bar'
And we'd think that a was an int or a bar. In the future we might model control flow, but it's a big feature. Mainly I just need an excuse to implement it :). Another way it could go wrong is if the user is using exec/eval which we don't have any insight into. And finally there can be things that we simply don't understand. So while we know about the primitive types and the methods on them, we don't model all of Python's built-in types. So for example if you're using deque in collections you won't get the same results.
All of this is open source (under the Apache license) and the parser and analysis engine are entirely stand alone components, so they could even be re-used by other IDEs. You can check out the source code over here: http://pytools.codeplex.com/SourceControl/changeset/view/3b8...
The second part you mention is all the AD7 classes. I would actually suggest that you try and ignore these for the most part – at least as you get started. Instead you should look at the PythonProcess class which exposes a simple C# API and a number of events which all of the AD7 classes are simply wrapping. If you completely replace this class with your own you’ll pretty much have a working debug engine. There may be a few things you’ll need to update (for example launch options over in AD7Engine.LaunchSuspended, or parsing expressions to see if they’re valid) but there shouldn’t be much need to look at those classes. And by the time you do need to look at them hopefully you can do it within the context of calls to and from the PythonProcess API and it’ll make more sense.
I hope that helps!