Skip to content

Add completion for expressions#585

Merged
vinistock merged 1 commit intomainfrom
01-23-add_completion_api_without_filtering
Feb 23, 2026
Merged

Add completion for expressions#585
vinistock merged 1 commit intomainfrom
01-23-add_completion_api_without_filtering

Conversation

@vinistock
Copy link
Member

@vinistock vinistock commented Feb 13, 2026

First step for #59

This PR adds a completion API with support for expression completion. That means, completion when there's nothing before the cursor.

The idea of the implementation is to have an enum to describe all of the possible contexts we may see completion being triggered since they all collect slightly different declarations. I tried to document all scenarios, despite having only implemented expression.

Notes

The expression completion is currently missing keywords. I'll propose something for those in a different PR.

@vinistock vinistock self-assigned this Feb 13, 2026
Copy link
Member Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@vinistock vinistock force-pushed the 01-23-add_completion_api_without_filtering branch from 0e7b5fa to 80c03f0 Compare February 13, 2026 22:40
@vinistock vinistock marked this pull request as ready for review February 13, 2026 22:40
@vinistock vinistock requested a review from a team as a code owner February 13, 2026 22:40
Copy link
Contributor

@soutaro soutaro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. 👍

I have a few thoughts related to this PR:

  • I’m assuming shortening candidate names will be implemented in another layer, right?
  • I’m not sure filtering out nested constants from completion makes sense. I often just type CONST and hope completion inserts Foo::CONST depending on the context. I think this is more useful because we usually focus on the last part of the qualified name and sometimes forget the prefix. (Steep filters out nested names in Ruby code completions but keeps them in RBS completions, and the latter feels better to me.)

///
/// Only panics if we attached a singleton class to something that isn't a namespace
#[must_use]
pub fn attached_object<'a>(&'a self, maybe_singleton: &'a Namespace) -> &'a Namespace {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a doc comment here to clarify the purpose. Also, should we validate that we eventually find a non-singleton class?

Suggested change
pub fn attached_object<'a>(&'a self, maybe_singleton: &'a Namespace) -> &'a Namespace {
/// Searches for the initial attached object for an arbitrarily nested singleton class.
/// Walks up the owner chain until finding a non-singleton namespace.
///
/// # Example
/// For `Foo::<Foo>::<<Foo>>`, returns `Foo`
///
/// # Panics
///
/// Panics if we attached a singleton class to something that isn't a namespace
/// or if the owner chain is broken.
#[must_use]
pub fn attached_object<'a>(&'a self, maybe_singleton: &'a Namespace) -> &'a Namespace {

My only concern is - what happens if we have a broken chain where the loop never exits? Should we add a safety check or assert that attached_object is actually different from the input?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the docs you suggested. Regarding the broken chain, I'm trying to think if we can add some debug asserts to verify that owners are always namespaces and ultimately connected to Object.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can have a sanity check about the resolution data, so that we can avoid paying the price of checking in the completion loop. I'll prototype this in a different PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// # Errors
///
/// Will error if the given `self_name_id` does not point to a namespace declaration
pub fn completion_candidates<'a>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we could make these errors more structured?

Right now we're using string-based errors which makes it harder to handle specific cases. Any thoughts on creating an enum for completion errors?

pub enum CompletionError {
    NameNotFound(NameId),
    UnresolvedName(NameId),
    InvalidDeclarationType,
}

This would make error handling more explicit and easier to test. We can do this later if needed 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added another task in #59. I agree, we should use specific errors.

@vinistock vinistock force-pushed the 01-23-add_completion_api_without_filtering branch from 80c03f0 to ac64f85 Compare February 18, 2026 22:04
@semgrep-pr-bot

This comment was marked as off-topic.

@vinistock
Copy link
Member Author

I’m assuming shortening candidate names will be implemented in another layer, right?

Yes, the name shortening will happen on the LSP side.

I’m not sure filtering out nested constants from completion makes sense.

Do you mean always showing all constants available regardless of lexical scope? This is an interesting question. The behaviour distinction would be:

  • Returning everything in one go gives you a gigantic list, but you can see everything immediately
  • Filtering based on lexical scope means you see constants in parts. For example:
module Foo
  CONST = 1
end

# completion here does not show Foo::CONST, only Foo
# Immediately after accepting `Foo` and adding `::`, another completion is triggered
# and you see all constants inside of `Foo`

What do people prefer in terms of experience?

@soutaro
Copy link
Contributor

soutaro commented Feb 19, 2026

Returning everything in one go gives you a gigantic list, but you can see everything immediately

True... I haven't had it my mind. Like in Shopify codebase, it must be unrealistic.

Filtering based on lexical scope means you see constants in parts

Yes. I often remember the base name of the constant, CONST, but forget the prefixes.

@vinistock vinistock merged commit 77d639f into main Feb 23, 2026
27 checks passed
@vinistock vinistock deleted the 01-23-add_completion_api_without_filtering branch February 23, 2026 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants