Autocomplete in Dynamically Typed Languages

Mark Lewis
5 min readJan 5, 2022

In a discussion thread on one of my recent posts on dynamically typed languages, someone questioned my assertion that Python IDEs can’t do autocomplete and claimed that I’m using the wrong IDE because their favorite one can. These types of comments frustrate me enough that I decided I would write a post on the topic instead of just a response.

First, let me be clear about what I mean when I talk about autocomplete. I don’t just mean that your IDE throws up random strings that occur in other parts of your code. What I’m referring to is what I’m used to having for languages like Java and Scala. When I say “auto-complete” I’m expecting a list of all the valid options that could go at the current location in the code. Note that this implies that there are no wrong suggestions and there are no missing correct suggestions. This is what I’m used to in statically typed languages. It is what I expect from my tools. It is also what I want for my students. I don’t want my students to be confused by getting incorrect suggestions that fail when they run the code or by having the option they were wanting to use not appear in the list.

The thing is, what I’m asking for is provably impossible to do in dynamically typed languages because that information isn’t known until runtime. This means it isn’t available when you write the code. In many situations, your IDE might be able to make a good guess at things, but it is only a guess. The IDE will be wrong/incomplete at times. To help illustrate this, let’s look at some examples.

First, go ahead and enter the following code:

def aFunction(o):
o.

What does autocomplete show here in your IDE? Given that we have no idea what the type of o is, anything it might list is potentially wrong, and listing nothing is almost certainly incomplete. This should be proof enough, but let’s just keep this handy as we will come back to it.

If you put a call to aFunction somewhere in your code and pass it a particular value, like a String or a List, then go back and invoke autocomplete you might get something more correct. However, that is only a transient state. Try adding the following to your file.

import randomclass Foo1:
def bar(this):
return 42
class Foo2:
def baz(this):
return "hi there"
def pickFoos():
if random.random() < 0.5:
return Foo1()
else:
return Foo2()
myFoo = pickFoos()
aFunction(myFoo)

Now go back to aFunction and see what your autocomplete puts in. Again, whatever it is, it is wrong because the information to fill out that location isn’t known until runtime. For fun, I put in the line print(o.bar()) and ran the code. It ran successfully several times before failing.

Of course, I expect people to point out that modern Python has type hints. My first response to that is that if you are going to enter all your types you probably should have just started with a statically typed language, but we won’t go there now. If you put in the type hint of def aFunction(o: Foo1): then the autocomplete does list bar, but even with the type hints, this might not be correct. (It is also worth noting that this code with the type annotation passes through mypy quite happily.)

Let’s put in a bit more code to see why even when the type is “known” through a type hint the autocomplete list can be incomplete.

def augmentFoo(aFoo1):
if random.random() < 0.5:
aFoo1.baz = 99
theFoo1 = Foo1()
augmentFoo(theFoo1)
theFoo1.

What should the IDE list as options in that last line? In my testing, it lists bar because it knows that theFoo1 is an instance of Foo1 that was created two lines above. However, even if you comment out the if in augmentFoo it still won’t list baz and clearly with the if there it can’t know if baz is safe or not. Note that if you use baz in this case, mypy does say there is an error, though it does so even when the if is commented and the code always runs properly. Thanks to del and delattr we can also create situations where autocomplete thinks something should be there that isn’t and where mypy says it is happy when it shouldn’t be.

Bottom line, IDEs for dynamic languages can’t list all valid options for autocomplete because that information doesn’t exist until runtime.

Does this Matter?

I can see people objecting that my examples are very made up and represent poor coding choices. I think that they would be half right, but even if they were completely right, it doesn’t change the fact that the language allows these things so no IDE can do accurate autocomplete and the code will often run happily with these bugs in place. If the 0.5 values were changed, these could be bugs that exist, but almost never manifest and would be extremely difficult to duplicate and debug. The fact that autocomplete can’t make completely correct suggestions is tied to the fundamental issue that the knowledge of what is valid isn’t known until runtime and this has broader implications for using these languages.

The reason I say that the argument that my examples are bad style is only half right though is because I can easily see these types of things happening in a large codebase as it evolves. Take the example of Foo1 and Foo2. Maybe you start with two very similar classes that both have methods with the same name. Maybe they started life close together, perhaps in the same file. There is a function that, like my example, returns either a Foo1 or a Foo2 based on some criteria. This is fine and everything works because they have the same names for their members.

As the code base grows things get refactored. Foo1 and Foo2 move further apart in the code. They might even move away from the function that utilizes them. Indeed, the branch that instantiates Foo2 might be a very low probability event. Then at some point in the future, someone looks at the code and decides that one of the methods in Foo2 has a name that doesn’t quite fit what it does, so they rename it. This is good code maintenance. It is the type of thing that you should do regularly. In this case, it introduces a subtle bug that might not be caught by unit tests and could be extremely hard to reproduce and find. The key is that neither your IDE nor any other tools are likely to provide much help in tracking this error down either.

This actually points to a different issue I have with dynamically typed languages, they make refactoring harder, but that’s a different blog post.

--

--

Mark Lewis

Computer Science Professor, Planetary Rings Simulator, Scala Zealot