LLDB REPL Code Completion: Possible improvement to implementation?

I've been looking at the LLDB REPL code completion (with the eventual goal of exposing it in the jupyter kernel that I'm working on), and I had a thought about how to improve its implementation, and I wanted to ask what others think about it.

Current implementation

Right now, the REPL accumulates all the code that you type in. When you ask for a completion, it sends all the accumulated code to REPLCodeCompletion.h, which calculates the completions. I see two disadvantages to this approach:

Efficiency: If you have a really long REPL session with a lot of code, the completer will have to process a lot of code to make a completion. (Though does it cache anything to make this faster?)

Correctness: LLDB lets you redeclare things that you woudn't normally be able to redeclare in Swift. For example, you can do:

  1> let x: Int = 1
x: Int = 1
  2> let x: Float = 1
x: Float = 1

This seems to confuse the completer. For example, when you do the above redeclaration and then ask it for completions for x., it gives you Int methods rather than Float methods.

Possible improvement

Instead of giving the completer the accumulated code, maybe we could give the completer: (1) the state of the declarations that LLDB is storing (SwiftPersistentExpressionState), and (2) just the current line of code.

Doing it this way should fix both problems.

Does this seem like a reasonable approach?

I might try to implement this soon. Would you be interested in accepting some PRs into master that do this?

I think it would be a reasonable thing to try. @Jim_Ingham knows the REPL more than I do so he might have an historical perspective on why this wasn't done before.

Remember that the REPL completions need to complete both against the new definitions in the REPL and against the names from all the external modules you've loaded into the REPL. You want:

> let x : Str<TAB>
Available completions:
        Strideable
        ...

So you can't JUST consult the SwiftPersistentExpressionState.

Because all the modules imported into the REPL are imported into the AST where we're building up the REPL history, by consulting that we get the imported module completion for free. I'm not sure how you would do that in the approach you are proposing.

That said, we really should be consulting the SwiftPersistentExpressionState for definitions made IN the REPL, since that is who knows which type is current when you've overwritten a definition. That the way we do it now doesn't capture overwritten definitions is known.

I got this approach working in completion with persistent expression state by marcrasi · Pull Request #1285 · apple/swift-lldb · GitHub !

I found some import information in SwiftPersistentExpressionState, so it works with imports too.

It involves a lot of different pieces that I don't know much about, so I don't have very high confidence that it is correct. But in my initial manual experiments with it through the Jupyter UI, everything that worked in the previous "concatenate everything" implementation also works with my new implementation.

2 Likes