Skip to content

Commit ad600e4

Browse files
committed
Take into account position of library() calls
1 parent ced4252 commit ad600e4

File tree

1 file changed

+104
-17
lines changed

1 file changed

+104
-17
lines changed

crates/ark/src/lsp/diagnostics.rs

Lines changed: 104 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//
66
//
77

8+
use std::collections::BTreeMap;
89
use std::collections::HashMap;
910
use std::collections::HashSet;
1011

@@ -17,6 +18,7 @@ use stdext::*;
1718
use tower_lsp::lsp_types::Diagnostic;
1819
use tower_lsp::lsp_types::DiagnosticSeverity;
1920
use tree_sitter::Node;
21+
use tree_sitter::Point;
2022
use tree_sitter::Range;
2123

2224
use crate::lsp;
@@ -64,7 +66,7 @@ pub struct DiagnosticContext<'a> {
6466
/// The symbols exported by packages loaded via `library()` calls in this
6567
/// document. Currently global. TODO: Store individual exports in a BTreeMap
6668
/// sorted by position in the source?
67-
pub library_symbols: HashSet<String>,
69+
pub library_symbols: BTreeMap<Point, HashSet<String>>,
6870

6971
// Whether or not we're inside of a formula.
7072
pub in_formula: bool,
@@ -88,7 +90,7 @@ impl<'a> DiagnosticContext<'a> {
8890
workspace_symbols: HashSet::new(),
8991
installed_packages: HashSet::new(),
9092
library,
91-
library_symbols: HashSet::new(),
93+
library_symbols: BTreeMap::new(),
9294
in_formula: false,
9395
in_call_like_arguments: false,
9496
}
@@ -99,8 +101,8 @@ impl<'a> DiagnosticContext<'a> {
99101
symbols.insert(name.to_string(), location);
100102
}
101103

102-
pub fn has_definition(&self, name: &str) -> bool {
103-
// First, check document symbols.
104+
// First, check document symbols.
105+
pub fn has_definition(&self, name: &str, start_position: Point) -> bool {
104106
for symbols in &self.document_symbols {
105107
if symbols.contains_key(name) {
106108
return true;
@@ -112,10 +114,15 @@ impl<'a> DiagnosticContext<'a> {
112114
return true;
113115
}
114116

115-
// Finally, check library symbols from library() calls.
116-
// TODO: Take `Node` and check by position.
117-
if self.library_symbols.contains(name) {
118-
return true;
117+
// Finally, check package symbols from `library()` calls.
118+
// Check all symbols exported by `library()` before the given position.
119+
for (library_position, exports) in self.library_symbols.iter() {
120+
if *library_position > start_position {
121+
break;
122+
}
123+
if exports.contains(name) {
124+
return true;
125+
}
119126
}
120127

121128
// Finally, check session symbols.
@@ -823,7 +830,12 @@ fn handle_library_call(node: Node, context: &mut DiagnosticContext) -> anyhow::R
823830
// Insert exports globablly for now
824831
if let Some(package) = context.library.get(&package_name) {
825832
for symbol in &package.namespace.exports {
826-
context.library_symbols.insert(symbol.clone());
833+
let pos = node.end_position();
834+
context
835+
.library_symbols
836+
.entry(pos)
837+
.or_insert_with(HashSet::new)
838+
.insert(symbol.clone());
827839
}
828840
} else {
829841
lsp::log_warn!("Can't get exports from package {package_name} because it is not installed.")
@@ -990,7 +1002,7 @@ fn check_symbol_in_scope(
9901002

9911003
// Skip if a symbol with this name is in scope.
9921004
let name = context.contents.node_slice(&node)?.to_string();
993-
if context.has_definition(name.as_str()) {
1005+
if context.has_definition(name.as_str(), node.start_position()) {
9941006
return false.ok();
9951007
}
9961008

@@ -1008,13 +1020,19 @@ fn check_symbol_in_scope(
10081020

10091021
#[cfg(test)]
10101022
mod tests {
1023+
use std::path::PathBuf;
1024+
10111025
use harp::eval::RParseEvalOptions;
10121026
use once_cell::sync::Lazy;
10131027
use tower_lsp::lsp_types::Position;
10141028

10151029
use crate::interface::console_inputs;
10161030
use crate::lsp::diagnostics::generate_diagnostics;
10171031
use crate::lsp::documents::Document;
1032+
use crate::lsp::inputs::library::Library;
1033+
use crate::lsp::inputs::package::Description;
1034+
use crate::lsp::inputs::package::Namespace;
1035+
use crate::lsp::inputs::package::Package;
10181036
use crate::lsp::state::WorldState;
10191037
use crate::r_task;
10201038

@@ -1504,13 +1522,6 @@ foo
15041522
#[test]
15051523
fn test_library_static_exports() {
15061524
r_task(|| {
1507-
use std::path::PathBuf;
1508-
1509-
use crate::lsp::inputs::library::Library;
1510-
use crate::lsp::inputs::package::Description;
1511-
use crate::lsp::inputs::package::Namespace;
1512-
use crate::lsp::inputs::package::Package;
1513-
15141525
// `mockpkg` exports `foo` and `bar`
15151526
let namespace = Namespace {
15161527
exports: vec!["foo".to_string(), "bar".to_string()],
@@ -1588,4 +1599,80 @@ foo
15881599
assert_eq!(diagnostics.len(), 0);
15891600
});
15901601
}
1602+
1603+
#[test]
1604+
fn test_library_static_exports_multiple_packages() {
1605+
r_task(|| {
1606+
// pkg1 exports `foo` and `bar`
1607+
let namespace1 = Namespace {
1608+
exports: vec!["foo".to_string(), "bar".to_string()],
1609+
imports: vec![],
1610+
bulk_imports: vec![],
1611+
};
1612+
let description1 = Description {
1613+
name: "pkg1".to_string(),
1614+
version: "1.0.0".to_string(),
1615+
depends: vec![],
1616+
};
1617+
let package1 = Package {
1618+
path: PathBuf::from("/mock/path1"),
1619+
description: description1,
1620+
namespace: namespace1,
1621+
};
1622+
1623+
// pkg2 exports `bar` and `baz`
1624+
let namespace2 = Namespace {
1625+
exports: vec!["bar".to_string(), "baz".to_string()],
1626+
imports: vec![],
1627+
bulk_imports: vec![],
1628+
};
1629+
let description2 = Description {
1630+
name: "pkg2".to_string(),
1631+
version: "1.0.0".to_string(),
1632+
depends: vec![],
1633+
};
1634+
let package2 = Package {
1635+
path: PathBuf::from("/mock/path2"),
1636+
description: description2,
1637+
namespace: namespace2,
1638+
};
1639+
1640+
let library = Library::new(vec![])
1641+
.insert("pkg1", package1)
1642+
.insert("pkg2", package2);
1643+
1644+
let console_scopes = vec![vec!["library".to_string()]];
1645+
let state = WorldState {
1646+
library,
1647+
console_scopes,
1648+
..Default::default()
1649+
};
1650+
1651+
// Code with two library calls at different points
1652+
let code = "
1653+
foo # not in scope
1654+
bar # not in scope
1655+
baz # not in scope
1656+
1657+
library(pkg1)
1658+
foo # in scope
1659+
bar # in scope
1660+
baz # not in scope
1661+
1662+
library(pkg2)
1663+
foo # in scope
1664+
bar # in scope
1665+
baz # in scope
1666+
";
1667+
let document = Document::new(code, None);
1668+
let diagnostics = generate_diagnostics(document, state.clone());
1669+
1670+
let messages: Vec<_> = diagnostics.iter().map(|d| d.message.clone()).collect();
1671+
assert!(messages.iter().any(|m| m.contains("No symbol named 'foo'")));
1672+
assert!(messages.iter().any(|m| m.contains("No symbol named 'bar'")));
1673+
assert!(messages.iter().any(|m| m.contains("No symbol named 'baz'")));
1674+
assert!(messages.iter().any(|m| m.contains("No symbol named 'baz'")));
1675+
assert_eq!(messages.len(), 4);
1676+
});
1677+
}
15911678
}

0 commit comments

Comments
 (0)