Even if you get fault tolerant parsing right, there’s no a lot you can do to offer responsive autocompletion. For that you need to type-check everything and that’s slow.
An alternative is to only offer suggestions that may be possible, but not all of them, which might be good enough and it might also be responsive.
For example, in this code (where | is where the cursor is):
def foo(x)
x.|
end
we know nothing about x. But we still know it’s an Object so we could suggest every possible method in the system, or maybe in this particular case just methods defined in Object to shorten that list.
But then if you have this:
def foo(x)
y = x.abs
y.|
end
We don’t know what’s the type of x but we know it has an abs method. We could search all the types that have abs in them and know y is the return type of those… if the return type is explicit (though in this particular case we could type-check abs because it has no arguments). Then we offer methods of those types.
You kind of need to replicate the logic of the compiler here, where you “kind of” type-check a method with whatever information you have and proceed to the next expression, type-check it, etc., but in a different way than what the compiler does.
If we have a type annotation on an argument:
def foo(x: Foo)
x.|
end
then we can use that to offer methods from Foo or any of its subclasses.
Given the type-less nature of Crystal, this is in my mind the correct LSP server to build for Crystal, not one that offers precise completion (because that’s impossible, and also very, very slow).