diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 76d383e7..54607b86 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -112,11 +112,12 @@ jobs:
args: nextest --all-features --workspace --lcov --output-path lcov.info --profile ci
- name: Upload coverage to Codecov
- uses: codecov/codecov-action@v3
+ uses: codecov/codecov-action@v4
with:
files: lcov.info
name: ${{ matrix.os }}
fail_ci_if_error: true
+ token: ${{ secrets.CODECOV_TOKEN }}
doctest:
name: Doctest Rust
diff --git a/crates/mun_compiler/src/diagnostics.rs b/crates/mun_compiler/src/diagnostics.rs
index 4867b819..3eb5213c 100644
--- a/crates/mun_compiler/src/diagnostics.rs
+++ b/crates/mun_compiler/src/diagnostics.rs
@@ -29,82 +29,82 @@ mod tests {
#[test]
fn test_syntax_error() {
- insta::assert_display_snapshot!(compilation_errors("\n\nfn main(\n struct Foo\n"));
+ insta::assert_snapshot!(compilation_errors("\n\nfn main(\n struct Foo\n"));
}
#[test]
fn test_unresolved_value_error() {
- insta::assert_display_snapshot!(compilation_errors(
+ insta::assert_snapshot!(compilation_errors(
"\n\nfn main() {\nlet b = a;\n\nlet d = c;\n}"
));
}
#[test]
fn test_unresolved_type_error() {
- insta::assert_display_snapshot!(compilation_errors(
+ insta::assert_snapshot!(compilation_errors(
"\n\nfn main() {\nlet a = Foo{};\n\nlet b = Bar{};\n}"
));
}
#[test]
fn test_leaked_private_type_error_function() {
- insta::assert_display_snapshot!(compilation_errors(
+ insta::assert_snapshot!(compilation_errors(
"\n\nstruct Foo;\n pub fn Bar() -> Foo { Foo } \n fn main() {}"
));
}
#[test]
fn test_expected_function_error() {
- insta::assert_display_snapshot!(compilation_errors(
+ insta::assert_snapshot!(compilation_errors(
"\n\nfn main() {\nlet a = Foo();\n\nlet b = Bar();\n}"
));
}
#[test]
fn test_mismatched_type_error() {
- insta::assert_display_snapshot!(compilation_errors(
+ insta::assert_snapshot!(compilation_errors(
"\n\nfn main() {\nlet a: f64 = false;\n\nlet b: bool = 22;\n}"
));
}
#[test]
fn test_duplicate_definition_error() {
- insta::assert_display_snapshot!(compilation_errors(
+ insta::assert_snapshot!(compilation_errors(
"\n\nfn foo(){}\n\nfn foo(){}\n\nstruct Bar;\n\nstruct Bar;\n\nfn BAZ(){}\n\nstruct BAZ;"
));
}
#[test]
fn test_possibly_uninitialized_variable_error() {
- insta::assert_display_snapshot!(compilation_errors(
+ insta::assert_snapshot!(compilation_errors(
"\n\nfn main() {\nlet a;\nif 5>6 {\na = 5\n}\nlet b = a;\n}"
));
}
#[test]
fn test_access_unknown_field_error() {
- insta::assert_display_snapshot!(compilation_errors(
+ insta::assert_snapshot!(compilation_errors(
"\n\nstruct Foo {\ni: bool\n}\n\nfn main() {\nlet a = Foo { i: false };\nlet b = a.t;\n}"
));
}
#[test]
fn test_free_type_alias_error() {
- insta::assert_display_snapshot!(compilation_errors("\n\ntype Foo;"));
+ insta::assert_snapshot!(compilation_errors("\n\ntype Foo;"));
}
#[test]
fn test_type_alias_target_undeclared_error() {
- insta::assert_display_snapshot!(compilation_errors("\n\ntype Foo = UnknownType;"));
+ insta::assert_snapshot!(compilation_errors("\n\ntype Foo = UnknownType;"));
}
#[test]
fn test_cyclic_type_alias_error() {
- insta::assert_display_snapshot!(compilation_errors("\n\ntype Foo = Foo;"));
+ insta::assert_snapshot!(compilation_errors("\n\ntype Foo = Foo;"));
}
#[test]
fn test_expected_function() {
- insta::assert_display_snapshot!(compilation_errors("\n\nfn foo() { let a = 3; a(); }"));
+ insta::assert_snapshot!(compilation_errors("\n\nfn foo() { let a = 3; a(); }"));
}
}
diff --git a/crates/mun_hir/src/code_model/function.rs b/crates/mun_hir/src/code_model/function.rs
index 6a1a0b8b..44e83091 100644
--- a/crates/mun_hir/src/code_model/function.rs
+++ b/crates/mun_hir/src/code_model/function.rs
@@ -1,6 +1,6 @@
use std::{iter::once, sync::Arc};
-use mun_syntax::ast::TypeAscriptionOwner;
+use mun_syntax::{ast, ast::TypeAscriptionOwner};
use super::Module;
use crate::{
@@ -11,8 +11,8 @@ use crate::{
resolve::HasResolver,
type_ref::{LocalTypeRefId, TypeRefMap, TypeRefSourceMap},
visibility::RawVisibility,
- Body, DefDatabase, DiagnosticSink, FileId, HasVisibility, HirDatabase, InferenceResult, Name,
- Ty, Visibility,
+ Body, DefDatabase, DiagnosticSink, FileId, HasSource, HasVisibility, HirDatabase, InFile,
+ InferenceResult, Name, Ty, Visibility,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
@@ -138,6 +138,20 @@ impl Function {
db.type_for_def(self.into(), Namespace::Values)
}
+ /// Returns the parameters of the function.
+ pub fn params(self, db: &dyn HirDatabase) -> Vec {
+ db.callable_sig(self.into())
+ .params()
+ .iter()
+ .enumerate()
+ .map(|(idx, ty)| Param {
+ func: self,
+ ty: ty.clone(),
+ idx,
+ })
+ .collect()
+ }
+
pub fn ret_type(self, db: &dyn HirDatabase) -> Ty {
let resolver = self.id.resolver(db.upcast());
let data = self.data(db.upcast());
@@ -166,6 +180,42 @@ impl Function {
}
}
+#[derive(Clone, PartialEq, Eq, Hash, Debug)]
+pub struct Param {
+ func: Function,
+ /// The index in parameter list, including self parameter.
+ idx: usize,
+ ty: Ty,
+}
+
+impl Param {
+ /// Returns the function to which this parameter belongs
+ pub fn parent_fn(&self) -> Function {
+ self.func
+ }
+
+ /// Returns the index of this parameter in the parameter list (including
+ /// self)
+ pub fn index(&self) -> usize {
+ self.idx
+ }
+
+ /// Returns the type of this parameter.
+ pub fn ty(&self) -> &Ty {
+ &self.ty
+ }
+
+ /// Returns the source of the parameter.
+ pub fn source(&self, db: &dyn HirDatabase) -> Option> {
+ let InFile { file_id, value } = self.func.source(db.upcast());
+ let params = value.param_list()?;
+ params
+ .params()
+ .nth(self.idx)
+ .map(|value| InFile { file_id, value })
+ }
+}
+
impl HasVisibility for Function {
fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
self.data(db.upcast())
diff --git a/crates/mun_hir/src/item_tree.rs b/crates/mun_hir/src/item_tree.rs
index 378ea690..58c5be3f 100644
--- a/crates/mun_hir/src/item_tree.rs
+++ b/crates/mun_hir/src/item_tree.rs
@@ -13,12 +13,12 @@ use std::{
sync::Arc,
};
-use mun_syntax::{ast, AstNode};
+use mun_syntax::ast;
use crate::{
arena::{Arena, Idx},
path::ImportAlias,
- source_id::FileAstId,
+ source_id::{AstIdNode, FileAstId},
type_ref::{LocalTypeRefId, TypeRefMap},
visibility::RawVisibility,
DefDatabase, FileId, InFile, Name, Path,
@@ -112,6 +112,7 @@ impl ItemVisibilities {
struct ItemTreeData {
imports: Arena,
functions: Arena,
+ params: Arena,
structs: Arena,
fields: Arena,
type_aliases: Arena,
@@ -122,7 +123,7 @@ struct ItemTreeData {
/// Trait implemented by all item nodes in the item tree.
pub trait ItemTreeNode: Clone {
- type Source: AstNode + Into;
+ type Source: AstIdNode + Into;
/// Returns the AST id for this instance
fn ast_id(&self) -> FileAstId;
@@ -244,7 +245,7 @@ macro_rules! impl_index {
};
}
-impl_index!(fields: Field);
+impl_index!(fields: Field, params: Param);
static VIS_PUB: RawVisibility = RawVisibility::Public;
static VIS_PRIV: RawVisibility = RawVisibility::This;
@@ -302,11 +303,22 @@ pub struct Function {
pub visibility: RawVisibilityId,
pub is_extern: bool,
pub types: TypeRefMap,
- pub params: Box<[LocalTypeRefId]>,
+ pub params: IdRange,
pub ret_type: LocalTypeRefId,
pub ast_id: FileAstId,
}
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Param {
+ pub type_ref: LocalTypeRefId,
+ pub ast_id: ParamAstId,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum ParamAstId {
+ Param(FileAstId),
+}
+
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Struct {
pub name: Name,
@@ -390,6 +402,11 @@ impl IdRange {
_p: PhantomData,
}
}
+
+ /// Returns true if the index range is empty
+ pub fn is_empty(&self) -> bool {
+ self.range.is_empty()
+ }
}
impl Iterator for IdRange {
diff --git a/crates/mun_hir/src/item_tree/lower.rs b/crates/mun_hir/src/item_tree/lower.rs
index 736a42d8..5ea75f6f 100644
--- a/crates/mun_hir/src/item_tree/lower.rs
+++ b/crates/mun_hir/src/item_tree/lower.rs
@@ -9,7 +9,8 @@ use smallvec::SmallVec;
use super::{
diagnostics, AssociatedItem, Field, Fields, Function, IdRange, Impl, ItemTree, ItemTreeData,
- ItemTreeNode, ItemVisibilities, LocalItemTreeId, ModItem, RawVisibilityId, Struct, TypeAlias,
+ ItemTreeNode, ItemVisibilities, LocalItemTreeId, ModItem, Param, ParamAstId, RawVisibilityId,
+ Struct, TypeAlias,
};
use crate::{
arena::{Idx, RawId},
@@ -156,13 +157,19 @@ impl Context {
let mut types = TypeRefMap::builder();
// Lower all the params
- let mut params = Vec::new();
+ let start_param_idx = self.next_param_idx();
if let Some(param_list) = func.param_list() {
for param in param_list.params() {
+ let ast_id = self.source_ast_id_map.ast_id(¶m);
let type_ref = types.alloc_from_node_opt(param.ascribed_type().as_ref());
- params.push(type_ref);
+ self.data.params.alloc(Param {
+ type_ref,
+ ast_id: ParamAstId::Param(ast_id),
+ });
}
}
+ let end_param_idx = self.next_param_idx();
+ let params = IdRange::new(start_param_idx..end_param_idx);
// Lowers the return type
let ret_type = match func.ret_type().and_then(|rt| rt.type_ref()) {
@@ -177,9 +184,9 @@ impl Context {
let res = Function {
name,
visibility,
- types,
is_extern,
- params: params.into_boxed_slice(),
+ types,
+ params,
ret_type,
ast_id,
};
@@ -313,6 +320,12 @@ impl Context {
let idx: u32 = self.data.fields.len().try_into().expect("too many fields");
Idx::from_raw(RawId::from(idx))
}
+
+ /// Returns the `Idx` of the next `Param`
+ fn next_param_idx(&self) -> Idx {
+ let idx: u32 = self.data.params.len().try_into().expect("too many params");
+ Idx::from_raw(RawId::from(idx))
+ }
}
/// Lowers a record field (e.g. `a:i32`)
diff --git a/crates/mun_hir/src/item_tree/pretty.rs b/crates/mun_hir/src/item_tree/pretty.rs
index 8b7988d8..08b44221 100644
--- a/crates/mun_hir/src/item_tree/pretty.rs
+++ b/crates/mun_hir/src/item_tree/pretty.rs
@@ -2,7 +2,7 @@ use std::{fmt, fmt::Write};
use crate::{
item_tree::{
- Fields, Function, Impl, Import, ItemTree, LocalItemTreeId, ModItem, RawVisibilityId,
+ Fields, Function, Impl, Import, ItemTree, LocalItemTreeId, ModItem, Param, RawVisibilityId,
Struct, TypeAlias,
},
path::ImportAlias,
@@ -181,8 +181,12 @@ impl Printer<'_> {
write!(self, "(")?;
if !params.is_empty() {
self.indented(|this| {
- for param in params.iter().copied() {
- this.print_type_ref(param, types)?;
+ for param in params.clone() {
+ let Param {
+ type_ref,
+ ast_id: _,
+ } = &this.tree[param];
+ this.print_type_ref(*type_ref, types)?;
writeln!(this, ",")?;
}
Ok(())
diff --git a/crates/mun_hir/src/source_id.rs b/crates/mun_hir/src/source_id.rs
index 6fab8ee1..8586b650 100644
--- a/crates/mun_hir/src/source_id.rs
+++ b/crates/mun_hir/src/source_id.rs
@@ -4,7 +4,7 @@ use std::{
sync::Arc,
};
-use mun_syntax::{ast, AstNode, AstPtr, SyntaxNode, SyntaxNodePtr};
+use mun_syntax::{ast, AstNode, AstPtr, SyntaxNode, SyntaxNodePtr, WalkEvent};
use crate::{
arena::{Arena, Idx},
@@ -13,12 +13,14 @@ use crate::{
FileId,
};
+type ErasedFileAstId = Idx;
+
/// `AstId` points to an AST node in any file.
///
/// It is stable across reparses, and can be used as salsa key/value.
pub(crate) type AstId = InFile>;
-impl AstId {
+impl AstId {
pub fn to_node(self, db: &dyn AstDatabase) -> N {
let root = db.parse(self.file_id);
db.ast_id_map(self.file_id)
@@ -28,29 +30,51 @@ impl AstId {
}
#[derive(Clone, Debug)]
-pub struct FileAstId {
+pub struct FileAstId {
raw: ErasedFileAstId,
_ty: PhantomData N>,
}
-impl Copy for FileAstId {}
+impl Copy for FileAstId {}
-impl PartialEq for FileAstId {
+impl PartialEq for FileAstId {
fn eq(&self, other: &Self) -> bool {
self.raw == other.raw
}
}
-impl Eq for FileAstId {}
-impl Hash for FileAstId {
+impl Eq for FileAstId {}
+impl Hash for FileAstId {
fn hash(&self, hasher: &mut H) {
self.raw.hash(hasher);
}
}
-impl FileAstId {
- pub(crate) fn with_file_id(self, file_id: FileId) -> AstId {
- AstId::new(file_id, self)
- }
+/// A trait that is implemented for all nodes that can be represented as a
+/// `FileAstId`.
+pub trait AstIdNode: AstNode {}
+
+macro_rules! register_ast_id_node {
+ (impl AstIdNode for $($ident:ident),+ ) => {
+ $(
+ impl AstIdNode for ast::$ident {}
+ )+
+ fn should_alloc_id(kind: mun_syntax::SyntaxKind) -> bool {
+ $(
+ ast::$ident::can_cast(kind)
+ )||+
+ }
+ };
+}
+
+register_ast_id_node! {
+ impl AstIdNode for
+ ModuleItem,
+ Use,
+ FunctionDef,
+ StructDef,
+ Impl,
+ TypeAliasDef,
+ Param
}
/// Maps items' `SyntaxNode`s to `ErasedFileAstId`s and back.
@@ -59,15 +83,13 @@ pub struct AstIdMap {
arena: Arena,
}
-type ErasedFileAstId = Idx;
-
impl AstIdMap {
pub(crate) fn ast_id_map_query(db: &dyn AstDatabase, file_id: FileId) -> Arc {
let map = AstIdMap::from_source(db.parse(file_id).tree().syntax());
Arc::new(map)
}
- pub(crate) fn ast_id(&self, item: &N) -> FileAstId {
+ pub(crate) fn ast_id(&self, item: &N) -> FileAstId {
let ptr = SyntaxNodePtr::new(item.syntax());
let raw = match self.arena.iter().find(|(_id, i)| **i == ptr) {
Some((it, _)) => it,
@@ -88,22 +110,31 @@ impl AstIdMap {
/// `node` must be the root of a syntax tree.
fn from_source(node: &SyntaxNode) -> AstIdMap {
assert!(node.parent().is_none());
-
let mut res = AstIdMap::default();
+
+ // Make sure the root node is allocated
+ if !should_alloc_id(node.kind()) {
+ res.alloc(node);
+ }
+
// By walking the tree in breadth-first order we make sure that parents
// get lower ids then children. That is, adding a new child does not
// change parent's id. This means that, say, adding a new function to a
// trait does not change ids of top-level items, which helps caching.
- bfs(node, |it| {
- if let Some(module_item) = ast::ModuleItem::cast(it) {
- res.alloc(module_item.syntax());
+ bdfs(node, |it| {
+ if should_alloc_id(it.kind()) {
+ res.alloc(&it);
+ TreeOrder::BreadthFirst
+ } else {
+ TreeOrder::DepthFirst
}
});
+
res
}
/// Returns the `AstPtr` of the given id.
- pub(crate) fn get(&self, id: FileAstId) -> AstPtr {
+ pub(crate) fn get(&self, id: FileAstId) -> AstPtr {
self.arena[id.raw].clone().try_cast::().unwrap()
}
@@ -113,14 +144,39 @@ impl AstIdMap {
}
}
-/// Walks the subtree in bfs order, calling `f` for each node.
-fn bfs(node: &SyntaxNode, mut f: impl FnMut(SyntaxNode)) {
+#[derive(Copy, Clone, PartialEq, Eq)]
+enum TreeOrder {
+ BreadthFirst,
+ DepthFirst,
+}
+
+/// Walks the subtree in bdfs order, calling `f` for each node.
+///
+/// ### What is bdfs order?
+///
+/// It is a mix of breadth-first and depth first orders. Nodes for which `f`
+/// returns [`TreeOrder::BreadthFirst`] are visited breadth-first, all the other
+/// nodes are explored [`TreeOrder::DepthFirst`].
+///
+/// In other words, the size of the bfs queue is bound by the number of "true"
+/// nodes.
+fn bdfs(node: &SyntaxNode, mut f: impl FnMut(SyntaxNode) -> TreeOrder) {
let mut curr_layer = vec![node.clone()];
let mut next_layer = vec![];
while !curr_layer.is_empty() {
curr_layer.drain(..).for_each(|node| {
- next_layer.extend(node.children());
- f(node);
+ let mut preorder = node.preorder();
+ while let Some(event) = preorder.next() {
+ match event {
+ WalkEvent::Enter(node) => {
+ if f(node.clone()) == TreeOrder::BreadthFirst {
+ next_layer.extend(node.children());
+ preorder.skip_subtree();
+ }
+ }
+ WalkEvent::Leave(_) => {}
+ }
+ }
});
std::mem::swap(&mut curr_layer, &mut next_layer);
}
diff --git a/crates/mun_language_server/Cargo.toml b/crates/mun_language_server/Cargo.toml
index d206d247..056914f5 100644
--- a/crates/mun_language_server/Cargo.toml
+++ b/crates/mun_language_server/Cargo.toml
@@ -23,7 +23,7 @@ mun_paths = { version = "0.6.0-dev", path="../mun_paths" }
anyhow = { version = "1.0", default-features = false, features=["std"] }
crossbeam-channel = { version = "0.5.9", default-features = false }
log = { version = "0.4", default-features = false }
-lsp-types = { version = "0.95.0", default-features = false }
+lsp-types = { version = "=0.95.0", default-features = false }
lsp-server = { version = "0.7.5", default-features = false }
parking_lot = { version = "0.12.1", default-features = false }
ra_ap_text_edit = { version = "0.0.190", default-features = false }
diff --git a/crates/tools/src/lib.rs b/crates/tools/src/lib.rs
index 05c7f7f0..3612d795 100644
--- a/crates/tools/src/lib.rs
+++ b/crates/tools/src/lib.rs
@@ -35,10 +35,8 @@ fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> {
}
fn reformat(text: impl std::fmt::Display) -> Result {
- let mut rustfmt = Command::new("rustfmt")
- .arg("+nightly")
- //.arg("--config-path")
- //.arg(project_root().join("rustfmt.toml"))
+ let mut rustfmt = Command::new("rustup")
+ .args(["run", "nightly", "--", "rustfmt"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;