Skip to content

language server features upgrade#1175

Merged
sbillig merged 36 commits intoargotorg:masterfrom
micahscopes:lsp-features-galore
Dec 12, 2025
Merged

language server features upgrade#1175
sbillig merged 36 commits intoargotorg:masterfrom
micahscopes:lsp-features-galore

Conversation

@micahscopes
Copy link
Collaborator

@micahscopes micahscopes commented Dec 5, 2025

Builds on #1165 (reference view API) to add all kinds of new language server features including code completion, signature help, inlay hints, and more.

Changes

HIR layer additions:

  • items_in_scope query for collecting all visible items in a scope (imports,
    child items, parent scopes)
  • ScopeId::items_in_scope() convenience method for scope-aware name
    resolution
  • Fixed ModuleTree::tree_node() to return Result instead of panicking on
    cross-ingot queries
  • Test for cross-ingot module tree access (was causing LSP panics during
    diagnostics)

LSP handlers (language-server):

  • completion - scope-aware item collection, member access detection,
    auto-import for symbols from current ingot
  • signature help - shows parameter info when typing function arguments
  • inlay hints - type annotations for let bindings without explicit types
  • document symbols - file outline with functions, structs, enums, traits,
    etc.
  • workspace symbols - project-wide symbol search
  • code actions - quick fix to add return type annotations
  • go-to implementations - for struct and trait implementations and their methods
  • rename improvements - renaming trait method definitions now works; rename at def site
  • standalone file warning - files that don't belong to any ingot come with a warning

Misc fixes:

  • Argument label mismatch only checked when caller explicitly provides a label
    (was applying label mismatch errors at call sites)
  • Completion text replacement properly handles existing identifiers
  • Member access detection (. and ::) for path completions
  • Better error handling

What's left for followup

  • manual QA! try it out!
  • Auto-import for cross-ingot symbols (std lib, dependencies)
  • Better completion filtering
    • e.g. by expected type context
    • needs refining for path completions ("foo::" --> "foo::bar"
  • Signature help for method calls on trait objects
  • More code actions (add missing imports, implement trait, etc.)
  • Treesitter fixes

@micahscopes micahscopes force-pushed the lsp-features-galore branch 4 times, most recently from b159e14 to fd2c56c Compare December 8, 2025 01:03
@micahscopes micahscopes marked this pull request as ready for review December 8, 2025 01:14
}
}

// Collect glob imports
Copy link
Collaborator

Choose a reason for hiding this comment

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

Name resolution rules dictate that explicitly named imports and local items trump glob imports.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Here's codex's version; prompted to encourage reuse of the name resolution machinery to get the precedence rules right. It looks right, but please verify. Also not sure if we want primitives included for this use-case.

#[salsa::tracked(return_ref)]
fn items_in_scope_impl<'db>(
    db: &'db dyn HirAnalysisDb,
    i_scope: ItemScope<'db>,
) -> IndexMap<String, NameRes<'db>> {
    let scope = i_scope.scope_kind(db).to_scope();
    let domain = i_scope.domain(db);
    let mut items: IndexMap<String, NameResBucket<'db>> = IndexMap::default();

    let mut resolver = NameResolver::new(db, &DefaultImporter);
    let local_resolutions =
        resolver.collect_all_resolutions_for_glob(scope, scope, FxHashSet::default());

    // Local definitions + imports (named then glob) with resolver precedence.
    for (ident, resolutions) in local_resolutions {
        let name = ident.data(db).to_string();
        let bucket = items.entry(name).or_default();
        for name_res in resolutions {
            if name_res.domain & domain != NameDomain::Invalid {
                bucket.push(&name_res);
            }
        }
    }

    // Collect unnamed/prelude imports (treated like named imports).
    let imports = &resolve_imports(db, scope.ingot(db)).1;
    if let Some(unnamed) = imports.unnamed_resolved.get(&scope) {
        for bucket in unnamed {
            for name_res in bucket.iter_ok() {
                if name_res.domain & domain != NameDomain::Invalid
                    && let Some(res_scope) = name_res.scope()
                    && let Some(name) = res_scope.name(db)
                    && name_res.is_visible(db, scope)
                {
                    items
                        .entry(name.data(db).to_string())
                        .or_default()
                        .push(name_res);
                }
            }
        }
    }

    // Recursively collect from parent scope (lexical shadowing handled via derivation).
    if let Some(parent) = scope.parent(db) {
        let parent_items = items_in_scope(db, parent, domain);
        for (name, name_res) in parent_items {
            let mut inherited = name_res.clone();
            inherited.derivation.lexed();
            items.entry(name.clone()).or_default().push(&inherited);
        }
    }

    // External ingots (lowest before primitives).
    for (ext_name, ingot) in scope.top_mod(db).ingot(db).resolved_external_ingots(db) {
        let res = NameRes::new_from_scope(
            ScopeId::from_item((ingot.root_mod(db)).into()),
            NameDomain::TYPE,
            NameDerivation::External,
        );

        if res.domain & domain != NameDomain::Invalid {
            items
                .entry(ext_name.data(db).to_string())
                .or_default()
                .push(&res);
        }
    }

    // Builtin primitive types (lowest precedence).
    if domain & NameDomain::TYPE != NameDomain::Invalid {
        for &prim in PrimTy::all_types() {
            let res = NameRes {
                kind: NameResKind::Prim(prim),
                domain: NameDomain::TYPE,
                derivation: NameDerivation::Prim,
            };

            items
                .entry(prim.name(db).data(db).to_string())
                .or_default()
                .push(&res);
        }
    }

    // Pick the best resolution per requested domain using resolver ordering.
    let mut flattened = IndexMap::default();
    for (name, bucket) in items {
        match bucket.pick(domain) {
            Ok(res) => {
                flattened.insert(name, res.clone());
            }
            Err(NameResolutionError::Ambiguous(cands)) => {
                if let Some(first) = cands.first() {
                    flattened.insert(name, first.clone());
                }
            }
            Err(_) => {}
        }
    }

    flattened
}

@micahscopes micahscopes mentioned this pull request Dec 10, 2025
Add a generic items_in_scope API to HIR that collects all visible items
in a given scope across the specified name domains. This follows the same
pattern as available_traits_in_scope and properly handles:
- Named imports
- Glob imports
- Unnamed/prelude imports
- Direct child items
- Parent scope items (recursively)

Add ScopeId::items_in_scope() as a convenient traversal-like method.

Rewrite completion handler to:
- Find the most specific scope at cursor position
- Use items_in_scope to get properly scoped completions
- Convert NameRes items to appropriate CompletionItem kinds

This fixes completion showing random unrelated symbols and provides
context-aware suggestions that respect lexical scope.
- Set insert_text, insert_text_format, and insert_text_mode to prevent
  unwanted text replacement and indentation issues
- Detect when completion is triggered by '.' for future member access
  implementation
- Add TODO for member completion (needs proper public API for type members)

This fixes the issue where completion would replace existing text and
add extra indentation.
Use top_mod.target_at() instead of reference_at() + target_at() so that
rename works when cursor is on a definition name, not just references.
- Add textDocument/implementation handler for traits and trait methods
  - On traits: navigates to all impl Trait blocks
  - On trait methods: navigates to all implementations of that method

- Fix trait method rename to also rename implementations
  - When renaming a trait method, all corresponding method definitions
    in impl blocks are also renamed
Copy link
Collaborator

@sbillig sbillig left a comment

Choose a reason for hiding this comment

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

🚀

@sbillig sbillig merged commit c8e8624 into argotorg:master Dec 12, 2025
5 checks passed
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.

2 participants