From aaca7d003bd969785be53d8f312b67bfa26f6272 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 27 Aug 2018 20:58:38 +0300 Subject: [PATCH] move scopes to file --- code/package.json | 4 + code/src/extension.ts | 12 +- crates/libanalysis/src/lib.rs | 36 +++- crates/libanalysis/src/module_map.rs | 5 + crates/libanalysis/tests/tests.rs | 13 ++ crates/libeditor/scope.rs | 0 crates/libeditor/src/completion.rs | 187 +----------------- crates/libeditor/src/lib.rs | 1 + crates/libeditor/src/scope.rs | 183 +++++++++++++++++ .../src/grammar/expressions/atom.rs | 4 +- crates/server/src/main_loop/handlers.rs | 31 ++- 11 files changed, 280 insertions(+), 196 deletions(-) create mode 100644 crates/libeditor/scope.rs create mode 100644 crates/libeditor/src/scope.rs diff --git a/code/package.json b/code/package.json index 00604efcc8..282e533af0 100644 --- a/code/package.json +++ b/code/package.json @@ -51,6 +51,10 @@ } ], "commands": [ + { + "command": "libsyntax-rust.createFile", + "title": "Show Rust syntax tree" + }, { "command": "libsyntax-rust.syntaxTree", "title": "Show Rust syntax tree" diff --git a/code/src/extension.ts b/code/src/extension.ts index c25e8cb611..b9d0097763 100644 --- a/code/src/extension.ts +++ b/code/src/extension.ts @@ -81,11 +81,21 @@ export function activate(context: vscode.ExtensionContext) { let e = await vscode.window.showTextDocument(doc) e.revealRange(range, vscode.TextEditorRevealType.InCenter) }) - console.log("ping") + registerCommand('libsyntax-rust.run', async (cmd: ProcessSpec) => { let task = createTask(cmd) await vscode.tasks.executeTask(task) }) + registerCommand('libsyntax-rust.createFile', async (uri_: string) => { + console.log(`uri: ${uri_}`) + let uri = vscode.Uri.parse(uri_) + let edit = new vscode.WorkspaceEdit() + edit.createFile(uri) + await vscode.workspace.applyEdit(edit) + let doc = await vscode.workspace.openTextDocument(uri) + await vscode.window.showTextDocument(doc) + console.log("Done") + }) dispose(vscode.workspace.registerTextDocumentContentProvider( 'libsyntax-rust', diff --git a/crates/libanalysis/src/lib.rs b/crates/libanalysis/src/lib.rs index a3f721cc86..49c39e1f36 100644 --- a/crates/libanalysis/src/lib.rs +++ b/crates/libanalysis/src/lib.rs @@ -12,12 +12,9 @@ extern crate rayon; mod symbol_index; mod module_map; -use once_cell::sync::OnceCell; -use rayon::prelude::*; - use std::{ fmt, - path::{Path}, + path::{Path, PathBuf}, sync::{ Arc, atomic::{AtomicBool, Ordering::SeqCst}, @@ -26,13 +23,16 @@ use std::{ time::Instant, }; +use once_cell::sync::OnceCell; +use rayon::prelude::*; + use libsyntax2::{ File, TextUnit, TextRange, SmolStr, ast::{self, AstNode, NameOwner}, SyntaxKind::*, }; -use libeditor::{LineIndex, FileSymbol, find_node_at_offset}; +use libeditor::{Diagnostic, LineIndex, FileSymbol, find_node_at_offset}; use self::{ symbol_index::FileSymbols, @@ -130,6 +130,9 @@ impl WorldState { } } +pub enum QuickFix { + CreateFile(PathBuf), +} impl World { pub fn file_syntax(&self, file_id: FileId) -> Result { @@ -210,6 +213,29 @@ impl World { Ok(vec![]) } + pub fn diagnostics(&self, file_id: FileId) -> Result)>> { + let syntax = self.file_syntax(file_id)?; + let mut res = libeditor::diagnostics(&syntax) + .into_iter() + .map(|d| (d, None)) + .collect::>(); + for module in syntax.ast().modules() { + if module.has_semi() && self.resolve_module(file_id, module).is_empty() { + if let Some(name) = module.name() { + let d = Diagnostic { + range: name.syntax().range(), + msg: "unresolved module".to_string(), + }; + let quick_fix = self.data.module_map.suggested_child_mod_path(module) + .map(QuickFix::CreateFile); + + res.push((d, quick_fix)) + } + } + } + Ok(res) + } + fn index_resolve(&self, name_ref: ast::NameRef) -> Vec<(FileId, FileSymbol)> { let name = name_ref.text(); let mut query = Query::new(name.to_string()); diff --git a/crates/libanalysis/src/module_map.rs b/crates/libanalysis/src/module_map.rs index 6a9104d84e..4f480591ed 100644 --- a/crates/libanalysis/src/module_map.rs +++ b/crates/libanalysis/src/module_map.rs @@ -93,6 +93,11 @@ impl ModuleMap { res } + pub fn suggested_child_mod_path(&self, m: ast::Module) -> Option { + let name = m.name()?; + Some(PathBuf::from(format!("../{}.rs", name.text()))) + } + fn links( &self, file_resolver: &FileResolver, diff --git a/crates/libanalysis/tests/tests.rs b/crates/libanalysis/tests/tests.rs index 32abf91528..b3c357b027 100644 --- a/crates/libanalysis/tests/tests.rs +++ b/crates/libanalysis/tests/tests.rs @@ -44,6 +44,19 @@ fn test_resolve_module() { ); } +#[test] +fn test_unresolved_module_diagnostic() { + let mut world = WorldState::new(); + world.change_file(FileId(1), Some("mod foo;".to_string())); + + let snap = world.snapshot(|_id, _path| None); + let diagnostics = snap.diagnostics(FileId(1)).unwrap(); + assert_eq_dbg( + r#"[Diagnostic { range: [4; 7), msg: "unresolved module" }]"#, + &diagnostics, + ); +} + #[test] fn test_resolve_parent_module() { let mut world = WorldState::new(); diff --git a/crates/libeditor/scope.rs b/crates/libeditor/scope.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/libeditor/src/completion.rs b/crates/libeditor/src/completion.rs index 69c039e832..242a3a4347 100644 --- a/crates/libeditor/src/completion.rs +++ b/crates/libeditor/src/completion.rs @@ -1,20 +1,14 @@ -use std::{ - fmt, - collections::HashMap, -}; - use libsyntax2::{ - File, TextUnit, AstNode, SyntaxNodeRef, SyntaxNode, SmolStr, - ast::{self, NameOwner}, + File, TextUnit, AstNode, + ast::self, algo::{ ancestors, - walk::preorder, - generate, }, }; use { AtomEdit, find_node_at_offset, + scope::{FnScopes, compute_scopes}, }; #[derive(Debug)] @@ -43,178 +37,3 @@ fn complete(name_ref: ast::NameRef, scopes: &FnScopes) -> Vec { }) .collect() } - -fn compute_scopes(fn_def: ast::FnDef) -> FnScopes { - let mut scopes = FnScopes::new(); - let root = scopes.root_scope(); - fn_def.param_list().into_iter() - .flat_map(|it| it.params()) - .filter_map(|it| it.pat()) - .for_each(|it| scopes.add_bindings(root, it)); - - if let Some(body) = fn_def.body() { - compute_block_scopes(body, &mut scopes, root) - } - scopes -} - -fn compute_block_scopes(block: ast::Block, scopes: &mut FnScopes, mut scope: ScopeId) { - for stmt in block.statements() { - match stmt { - ast::Stmt::LetStmt(stmt) => { - scope = scopes.new_scope(scope); - if let Some(pat) = stmt.pat() { - scopes.add_bindings(scope, pat); - } - if let Some(expr) = stmt.initializer() { - scopes.set_scope(expr.syntax(), scope) - } - } - ast::Stmt::ExprStmt(expr_stmt) => { - if let Some(expr) = expr_stmt.expr() { - scopes.set_scope(expr.syntax(), scope); - compute_expr_scopes(expr, scopes, scope); - } - } - } - } - if let Some(expr) = block.expr() { - scopes.set_scope(expr.syntax(), scope); - compute_expr_scopes(expr, scopes, scope); - } -} - -fn compute_expr_scopes(expr: ast::Expr, scopes: &mut FnScopes, scope: ScopeId) { - match expr { - ast::Expr::IfExpr(e) => { - let cond_scope = e.condition().and_then(|cond| { - compute_cond_scopes(cond, scopes, scope) - }); - if let Some(block) = e.then_branch() { - compute_block_scopes(block, scopes, cond_scope.unwrap_or(scope)); - } - if let Some(block) = e.else_branch() { - compute_block_scopes(block, scopes, scope); - } - }, - ast::Expr::WhileExpr(e) => { - let cond_scope = e.condition().and_then(|cond| { - compute_cond_scopes(cond, scopes, scope) - }); - if let Some(block) = e.body() { - compute_block_scopes(block, scopes, cond_scope.unwrap_or(scope)); - } - }, - ast::Expr::BlockExpr(e) => { - if let Some(block) = e.block() { - compute_block_scopes(block, scopes, scope); - } - } - // ForExpr(e) => TODO, - _ => { - expr.syntax().children() - .filter_map(ast::Expr::cast) - .for_each(|expr| compute_expr_scopes(expr, scopes, scope)) - } - }; - - fn compute_cond_scopes(cond: ast::Condition, scopes: &mut FnScopes, scope: ScopeId) -> Option { - if let Some(expr) = cond.expr() { - compute_expr_scopes(expr, scopes, scope); - } - if let Some(pat) = cond.pat() { - let s = scopes.new_scope(scope); - scopes.add_bindings(s, pat); - Some(s) - } else { - None - } - } -} - -type ScopeId = usize; - -#[derive(Debug)] -struct FnScopes { - scopes: Vec, - scope_for: HashMap, -} - -impl FnScopes { - fn new() -> FnScopes { - FnScopes { - scopes: vec![], - scope_for: HashMap::new(), - } - } - fn root_scope(&mut self) -> ScopeId { - let res = self.scopes.len(); - self.scopes.push(ScopeData { parent: None, entries: vec![] }); - res - } - fn new_scope(&mut self, parent: ScopeId) -> ScopeId { - let res = self.scopes.len(); - self.scopes.push(ScopeData { parent: Some(parent), entries: vec![] }); - res - } - fn add_bindings(&mut self, scope: ScopeId, pat: ast::Pat) { - let entries = preorder(pat.syntax()) - .filter_map(ast::BindPat::cast) - .filter_map(ScopeEntry::new); - self.scopes[scope].entries.extend(entries); - } - fn set_scope(&mut self, node: SyntaxNodeRef, scope: ScopeId) { - self.scope_for.insert(node.owned(), scope); - } - fn entries(&self, scope: ScopeId) -> &[ScopeEntry] { - &self.scopes[scope].entries - } - fn scope_for(&self, node: SyntaxNodeRef) -> Option { - ancestors(node) - .filter_map(|it| self.scope_for.get(&it.owned()).map(|&scope| scope)) - .next() - } - fn scope_chain<'a>(&'a self, node: SyntaxNodeRef) -> impl Iterator + 'a { - generate(self.scope_for(node), move |&scope| self.scopes[scope].parent) - } -} - -#[derive(Debug)] -struct ScopeData { - parent: Option, - entries: Vec -} - -struct ScopeEntry { - syntax: SyntaxNode -} - -impl ScopeEntry { - fn new(pat: ast::BindPat) -> Option { - if pat.name().is_some() { - Some(ScopeEntry { syntax: pat.syntax().owned() }) - } else { - None - } - } - - fn name(&self) -> SmolStr { - self.ast().name() - .unwrap() - .text() - } - - fn ast(&self) -> ast::BindPat { - ast::BindPat::cast(self.syntax.borrowed()) - .unwrap() - } -} - -impl fmt::Debug for ScopeEntry { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("ScopeEntry") - .field("name", &self.name()) - .field("syntax", &self.syntax) - .finish() - } -} diff --git a/crates/libeditor/src/lib.rs b/crates/libeditor/src/lib.rs index 60489f7e3b..d39e56d81d 100644 --- a/crates/libeditor/src/lib.rs +++ b/crates/libeditor/src/lib.rs @@ -9,6 +9,7 @@ mod edit; mod code_actions; mod typing; mod completion; +mod scope; use libsyntax2::{ File, TextUnit, TextRange, SyntaxNodeRef, diff --git a/crates/libeditor/src/scope.rs b/crates/libeditor/src/scope.rs new file mode 100644 index 0000000000..1fec0b24e4 --- /dev/null +++ b/crates/libeditor/src/scope.rs @@ -0,0 +1,183 @@ +use std::{ + fmt, + collections::HashMap, +}; + +use libsyntax2::{ + SyntaxNodeRef, SyntaxNode, SmolStr, AstNode, + ast::{self, NameOwner}, + algo::{ancestors, generate, walk::preorder} +}; + +pub fn compute_scopes(fn_def: ast::FnDef) -> FnScopes { + let mut scopes = FnScopes::new(); + let root = scopes.root_scope(); + fn_def.param_list().into_iter() + .flat_map(|it| it.params()) + .filter_map(|it| it.pat()) + .for_each(|it| scopes.add_bindings(root, it)); + + if let Some(body) = fn_def.body() { + compute_block_scopes(body, &mut scopes, root) + } + scopes +} + +fn compute_block_scopes(block: ast::Block, scopes: &mut FnScopes, mut scope: ScopeId) { + for stmt in block.statements() { + match stmt { + ast::Stmt::LetStmt(stmt) => { + scope = scopes.new_scope(scope); + if let Some(pat) = stmt.pat() { + scopes.add_bindings(scope, pat); + } + if let Some(expr) = stmt.initializer() { + scopes.set_scope(expr.syntax(), scope) + } + } + ast::Stmt::ExprStmt(expr_stmt) => { + if let Some(expr) = expr_stmt.expr() { + scopes.set_scope(expr.syntax(), scope); + compute_expr_scopes(expr, scopes, scope); + } + } + } + } + if let Some(expr) = block.expr() { + scopes.set_scope(expr.syntax(), scope); + compute_expr_scopes(expr, scopes, scope); + } +} + +fn compute_expr_scopes(expr: ast::Expr, scopes: &mut FnScopes, scope: ScopeId) { + match expr { + ast::Expr::IfExpr(e) => { + let cond_scope = e.condition().and_then(|cond| { + compute_cond_scopes(cond, scopes, scope) + }); + if let Some(block) = e.then_branch() { + compute_block_scopes(block, scopes, cond_scope.unwrap_or(scope)); + } + if let Some(block) = e.else_branch() { + compute_block_scopes(block, scopes, scope); + } + }, + ast::Expr::WhileExpr(e) => { + let cond_scope = e.condition().and_then(|cond| { + compute_cond_scopes(cond, scopes, scope) + }); + if let Some(block) = e.body() { + compute_block_scopes(block, scopes, cond_scope.unwrap_or(scope)); + } + }, + ast::Expr::BlockExpr(e) => { + if let Some(block) = e.block() { + compute_block_scopes(block, scopes, scope); + } + } + // ForExpr(e) => TODO, + _ => { + expr.syntax().children() + .filter_map(ast::Expr::cast) + .for_each(|expr| compute_expr_scopes(expr, scopes, scope)) + } + }; + + fn compute_cond_scopes(cond: ast::Condition, scopes: &mut FnScopes, scope: ScopeId) -> Option { + if let Some(expr) = cond.expr() { + compute_expr_scopes(expr, scopes, scope); + } + if let Some(pat) = cond.pat() { + let s = scopes.new_scope(scope); + scopes.add_bindings(s, pat); + Some(s) + } else { + None + } + } +} + +type ScopeId = usize; + +#[derive(Debug)] +pub struct FnScopes { + scopes: Vec, + scope_for: HashMap, +} + +impl FnScopes { + fn new() -> FnScopes { + FnScopes { + scopes: vec![], + scope_for: HashMap::new(), + } + } + pub fn entries(&self, scope: ScopeId) -> &[ScopeEntry] { + &self.scopes[scope].entries + } + pub fn scope_chain<'a>(&'a self, node: SyntaxNodeRef) -> impl Iterator + 'a { + generate(self.scope_for(node), move |&scope| self.scopes[scope].parent) + } + fn root_scope(&mut self) -> ScopeId { + let res = self.scopes.len(); + self.scopes.push(ScopeData { parent: None, entries: vec![] }); + res + } + fn new_scope(&mut self, parent: ScopeId) -> ScopeId { + let res = self.scopes.len(); + self.scopes.push(ScopeData { parent: Some(parent), entries: vec![] }); + res + } + fn add_bindings(&mut self, scope: ScopeId, pat: ast::Pat) { + let entries = preorder(pat.syntax()) + .filter_map(ast::BindPat::cast) + .filter_map(ScopeEntry::new); + self.scopes[scope].entries.extend(entries); + } + fn set_scope(&mut self, node: SyntaxNodeRef, scope: ScopeId) { + self.scope_for.insert(node.owned(), scope); + } + fn scope_for(&self, node: SyntaxNodeRef) -> Option { + ancestors(node) + .filter_map(|it| self.scope_for.get(&it.owned()).map(|&scope| scope)) + .next() + } +} + +#[derive(Debug)] +struct ScopeData { + parent: Option, + entries: Vec +} + +pub struct ScopeEntry { + syntax: SyntaxNode +} + +impl ScopeEntry { + fn new(pat: ast::BindPat) -> Option { + if pat.name().is_some() { + Some(ScopeEntry { syntax: pat.syntax().owned() }) + } else { + None + } + } + pub fn name(&self) -> SmolStr { + self.ast().name() + .unwrap() + .text() + } + fn ast(&self) -> ast::BindPat { + ast::BindPat::cast(self.syntax.borrowed()) + .unwrap() + } +} + +impl fmt::Debug for ScopeEntry { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("ScopeEntry") + .field("name", &self.name()) + .field("syntax", &self.syntax) + .finish() + } +} diff --git a/crates/libsyntax2/src/grammar/expressions/atom.rs b/crates/libsyntax2/src/grammar/expressions/atom.rs index bb5402af7f..e83c82c92d 100644 --- a/crates/libsyntax2/src/grammar/expressions/atom.rs +++ b/crates/libsyntax2/src/grammar/expressions/atom.rs @@ -29,8 +29,8 @@ pub(crate) fn literal(p: &mut Parser) -> Option { pub(super) const ATOM_EXPR_FIRST: TokenSet = token_set_union![ LITERAL_FIRST, - token_set![L_PAREN, PIPE, MOVE_KW, IF_KW, WHILE_KW, MATCH_KW, UNSAFE_KW, L_CURLY, RETURN_KW, - IDENT, SELF_KW, SUPER_KW, COLONCOLON, BREAK_KW, CONTINUE_KW, LIFETIME ], + token_set![L_CURLY, L_PAREN, L_BRACK, PIPE, MOVE_KW, IF_KW, WHILE_KW, MATCH_KW, UNSAFE_KW, + RETURN_KW, IDENT, SELF_KW, SUPER_KW, COLONCOLON, BREAK_KW, CONTINUE_KW, LIFETIME ], ]; pub(super) fn atom_expr(p: &mut Parser, r: Restrictions) -> Option { diff --git a/crates/server/src/main_loop/handlers.rs b/crates/server/src/main_loop/handlers.rs index e7ac53028a..350eda7dfc 100644 --- a/crates/server/src/main_loop/handlers.rs +++ b/crates/server/src/main_loop/handlers.rs @@ -7,7 +7,7 @@ use languageserver_types::{ CompletionItem, }; use serde_json::{to_value, from_value}; -use libanalysis::{Query}; +use libanalysis::{Query, QuickFix}; use libeditor; use libsyntax2::{ TextUnit, @@ -177,6 +177,30 @@ pub fn handle_code_action( }; res.push(cmd); } + + for (diag, quick_fix) in world.analysis().diagnostics(file_id)? { + let quick_fix = match quick_fix { + Some(quick_fix) => quick_fix, + None => continue, + }; + if !contains_offset_nonstrict(diag.range, offset) { + continue; + } + let cmd = match quick_fix { + QuickFix::CreateFile(path) => { + let path = &path.to_str().unwrap()[3..]; // strip `../` b/c url is weird + let uri = params.text_document.uri.join(path) + .unwrap(); + let uri = ::url_serde::Ser::new(&uri); + Command { + title: "Create file".to_string(), + command: "libsyntax-rust.createFile".to_string(), + arguments: Some(vec![to_value(uri).unwrap()]), + } + } + }; + res.push(cmd) + } return Ok(Some(res)); } @@ -355,11 +379,10 @@ pub fn publish_diagnostics( uri: Url ) -> Result { let file_id = world.uri_to_file_id(&uri)?; - let file = world.analysis().file_syntax(file_id)?; let line_index = world.analysis().file_line_index(file_id)?; - let diagnostics = libeditor::diagnostics(&file) + let diagnostics = world.analysis().diagnostics(file_id)? .into_iter() - .map(|d| Diagnostic { + .map(|(d, _quick_fix)| Diagnostic { range: d.range.conv_with(&line_index), severity: Some(DiagnosticSeverity::Error), code: None,