Skip to content

Commit ba06f77

Browse files
authored
Fix completions for import and exports (#2640)
This pull request refactors the completion logic for imports and exports to be more robust and accurate. The completion provider in the language service (`global_items.rs`) has been rewritten to leverage the `GlobalScope` and `Locals` structs already available from the resolver. This allows completions to use the same "view" as the name resolution code, rather than trying to recreate it by manually analyzing imports, enumerating items, etc. All of that work has already been done by the resolver. The previous implementation for completions had difficulty handling more complex import and export scenarios, such as items re-exported from dependencies or items under aliased namespaces. This could lead to incomplete or incorrect suggestions. The new approach provides a more solid foundation, giving the language service a complete and accurate view of all available symbols. Fixes #2142
1 parent 12bbe9b commit ba06f77

File tree

12 files changed

+683
-745
lines changed

12 files changed

+683
-745
lines changed

source/compiler/qsc/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub use qsc_frontend::compile::{CompileUnit, PackageStore, SourceContents, Sourc
1515

1616
pub mod resolve {
1717
pub use qsc_frontend::resolve::{
18-
Local, LocalKind, Locals, Res, iter_valid_items, path_as_field_accessor,
18+
GlobalScope, Local, Locals, NameKind, Res, iter_valid_items, path_as_field_accessor,
1919
};
2020
}
2121

source/compiler/qsc_frontend/src/compile.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pub mod preprocess;
99
use crate::{
1010
error::WithSource,
1111
lower::{self, Lowerer},
12-
resolve::{self, Locals, Names, Resolver},
12+
resolve::{self, GlobalScope, Locals, Names, Resolver},
1313
typeck::{self, Checker, Table},
1414
};
1515

@@ -62,6 +62,7 @@ pub struct AstPackage {
6262
pub tys: Table,
6363
pub names: Names,
6464
pub locals: Locals,
65+
pub globals: GlobalScope,
6566
}
6667

6768
#[derive(Clone, Debug, Default)]
@@ -397,6 +398,7 @@ pub fn compile_ast(
397398
let ResolveResult {
398399
names,
399400
locals,
401+
globals,
400402
errors: name_errors,
401403
} = resolve_all(
402404
store,
@@ -429,6 +431,7 @@ pub fn compile_ast(
429431
tys,
430432
names,
431433
locals,
434+
globals,
432435
},
433436
assigner: hir_assigner,
434437
sources,
@@ -550,6 +553,7 @@ pub fn get_target_profile_from_entry_point(
550553
pub(crate) struct ResolveResult {
551554
pub names: Names,
552555
pub locals: Locals,
556+
pub globals: GlobalScope,
553557
pub errors: Vec<resolve::Error>,
554558
}
555559

@@ -581,13 +585,14 @@ fn resolve_all(
581585

582586
// resolve all symbols, binding imports/export names as they're resolved
583587
resolver.resolve(assigner, package);
584-
let (names, _, locals, mut resolver_errors) = resolver.into_result();
588+
let (names, globals, locals, mut resolver_errors) = resolver.into_result();
585589

586590
errors.append(&mut resolver_errors);
587591

588592
ResolveResult {
589593
names,
590594
locals,
595+
globals,
591596
errors,
592597
}
593598
}

source/compiler/qsc_frontend/src/incremental.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ impl Compiler {
190190
package: ast,
191191
names: self.resolver.names().clone(),
192192
locals: self.resolver.locals().clone(),
193+
globals: self.resolver.globals().clone(),
193194
tys: self.checker.table().clone(),
194195
},
195196
hir,
@@ -229,6 +230,7 @@ impl Compiler {
229230
package: ast,
230231
names: self.resolver.names().clone(),
231232
locals: self.resolver.locals().clone(),
233+
globals: self.resolver.globals().clone(),
232234
tys: self.checker.table().clone(),
233235
},
234236
hir,
@@ -245,6 +247,7 @@ impl Compiler {
245247
unit.ast.names = new.ast.names;
246248
unit.ast.tys = new.ast.tys;
247249
unit.ast.locals = new.ast.locals;
250+
unit.ast.globals = new.ast.globals;
248251

249252
// Update the HIR
250253
extend_hir(&mut unit.package, new.hir);

source/compiler/qsc_frontend/src/resolve.rs

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ impl Locals {
341341

342342
// deduping by name will effectively make locals in a child scope
343343
// shadow the locals in its parent scopes
344-
all_locals.dedup_by(|a, b| a.name == b.name);
344+
all_locals.dedup_by(|a, b| a.name().is_some() && a.name() == b.name());
345345

346346
all_locals
347347
}
@@ -361,22 +361,28 @@ impl Locals {
361361
}
362362

363363
#[derive(Debug)]
364-
pub struct Local {
365-
pub name: Rc<str>,
366-
pub kind: LocalKind,
367-
}
368-
369-
#[derive(Debug)]
370-
pub enum LocalKind {
364+
pub enum Local {
371365
/// A local callable or UDT.
372-
Item(ItemId),
366+
Item(ItemId, Rc<str>),
373367
/// A type parameter.
374-
TyParam(ParamId),
368+
TyParam(ParamId, Rc<str>),
375369
/// A local variable or parameter.
376-
Var(NodeId),
370+
Var(NodeId, Rc<str>),
371+
/// A namespace import (`import Foo as A` or `open Foo`)
372+
NamespaceImport(NamespaceId, Option<Rc<str>>),
377373
}
378374

379-
#[derive(Debug, Default)]
375+
impl Local {
376+
#[must_use]
377+
pub fn name(&self) -> Option<&Rc<str>> {
378+
match self {
379+
Local::Item(_, name) | Local::TyParam(_, name) | Local::Var(_, name) => Some(name),
380+
Local::NamespaceImport(_, alias) => alias.as_ref(),
381+
}
382+
}
383+
}
384+
385+
#[derive(Debug, Default, Clone)]
380386
pub struct GlobalScope {
381387
/// Global names that are valid in a type context (UDTs, builtin types...)
382388
tys: IndexMap<NamespaceId, FxHashMap<Rc<str>, Res>>,
@@ -394,12 +400,17 @@ pub struct GlobalScope {
394400

395401
impl GlobalScope {
396402
fn get(&self, kind: NameKind, namespace: NamespaceId, name: &str) -> Option<&Res> {
397-
let items = match kind {
403+
self.table(kind)
404+
.get(namespace)
405+
.and_then(|items| items.get(name))
406+
}
407+
408+
pub fn table(&self, kind: NameKind) -> &IndexMap<NamespaceId, FxHashMap<Rc<str>, Res>> {
409+
match kind {
398410
NameKind::Term => &self.terms,
399411
NameKind::Ty => &self.tys,
400412
NameKind::Importable => &self.importables,
401-
};
402-
items.get(namespace).and_then(|items| items.get(name))
413+
}
403414
}
404415

405416
/// Creates a namespace in the namespace mapping. Note that namespaces are tracked separately from their
@@ -419,7 +430,7 @@ impl GlobalScope {
419430
}
420431

421432
/// Finds a namespace by its path.
422-
fn find_namespace<'a>(
433+
pub fn find_namespace<'a>(
423434
&self,
424435
name: impl IntoIterator<Item = &'a str>,
425436
root: Option<NamespaceId>,
@@ -459,12 +470,23 @@ impl GlobalScope {
459470
}
460471

461472
/// Returns the full namespace path for a given namespace ID.
462-
fn format_namespace_name(&self, namespace_id: NamespaceId) -> String {
473+
pub fn format_namespace_name(&self, namespace_id: NamespaceId) -> String {
463474
self.namespaces
464475
.find_namespace_by_id(&namespace_id)
465476
.0
466477
.join(".")
467478
}
479+
480+
pub fn namespace_children(&self, namespace_id: NamespaceId) -> Vec<Rc<str>> {
481+
self.namespaces
482+
.find_namespace_by_id(&namespace_id)
483+
.1
484+
.borrow()
485+
.children
486+
.keys()
487+
.cloned()
488+
.collect()
489+
}
468490
}
469491

470492
#[derive(Debug, Clone, Eq, PartialEq)]
@@ -475,7 +497,7 @@ enum ScopeKind {
475497
}
476498

477499
#[derive(Clone, Copy, Debug)]
478-
enum NameKind {
500+
pub enum NameKind {
479501
/// A name that is valid as an expression context: a callable or UDT
480502
Term,
481503
/// A name that is valid in a type context: builtin or UDT
@@ -590,6 +612,10 @@ impl Resolver {
590612
&self.locals
591613
}
592614

615+
pub(super) fn globals(&self) -> &GlobalScope {
616+
&self.globals
617+
}
618+
593619
pub(super) fn drain_errors(&mut self) -> vec::Drain<Error> {
594620
self.errors.drain(..)
595621
}
@@ -1271,6 +1297,11 @@ impl GlobalTable {
12711297
}
12721298
}
12731299
(global::Kind::Namespace(item_id), hir::Visibility::Public) => {
1300+
if package_root == Some(namespace) && global.name.as_ref() == "Main" {
1301+
// If the namespace is `Main` and we have an alias, we treat it as the root of the package,
1302+
// so there's no namespace prefix between the dependency alias and the defined items.
1303+
continue;
1304+
}
12741305
let this_namespace = self
12751306
.scope
12761307
.insert_or_find_namespace(vec![global.name.clone()], Some(namespace));
@@ -2014,10 +2045,7 @@ fn get_scope_locals(scope: &Scope, offset: u32, vars: bool) -> Vec<Local> {
20142045
// when a variable is later shadowed in the same scope,
20152046
// it is missed in the list. https://github.com/microsoft/qsharp/issues/897
20162047
if offset >= *valid_at {
2017-
Some(Local {
2018-
name: name.clone(),
2019-
kind: LocalKind::Var(*id),
2020-
})
2048+
Some(Local::Var(*id, name.clone()))
20212049
} else {
20222050
None
20232051
}
@@ -2027,26 +2055,28 @@ fn get_scope_locals(scope: &Scope, offset: u32, vars: bool) -> Vec<Local> {
20272055
scope
20282056
.ty_vars
20292057
.iter()
2030-
.map(|(name, (id, _constraints))| Local {
2031-
name: name.clone(),
2032-
kind: LocalKind::TyParam(*id),
2033-
}),
2058+
.map(|(name, (id, _constraints))| Local::TyParam(*id, name.clone())),
20342059
);
20352060
}
20362061

20372062
// items
2038-
// skip adding newtypes since they're already in the terms map
2063+
// gather from the terms table, since it happens to contain all the
2064+
// declared and imported callables and newtypes
20392065
names.extend(scope.terms.iter().filter_map(|(name, res)| {
20402066
if let Res::Item(id, _) = res {
2041-
Some(Local {
2042-
name: name.clone(),
2043-
kind: LocalKind::Item(*id),
2044-
})
2067+
Some(Local::Item(*id, name.clone()))
20452068
} else {
20462069
None
20472070
}
20482071
}));
20492072

2073+
// opens and namespace imports
2074+
names.extend(scope.opens.iter().flat_map(|(alias, opens)| {
2075+
opens
2076+
.iter()
2077+
.map(|open| Local::NamespaceImport(open.namespace, alias.clone()))
2078+
}));
2079+
20502080
names
20512081
}
20522082

0 commit comments

Comments
 (0)