move scopes to file

This commit is contained in:
Aleksey Kladov 2018-08-27 20:58:38 +03:00
parent 846114a6e9
commit aaca7d003b
11 changed files with 280 additions and 196 deletions

View file

@ -51,6 +51,10 @@
}
],
"commands": [
{
"command": "libsyntax-rust.createFile",
"title": "Show Rust syntax tree"
},
{
"command": "libsyntax-rust.syntaxTree",
"title": "Show Rust syntax tree"

View file

@ -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',

View file

@ -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<File> {
@ -210,6 +213,29 @@ impl World {
Ok(vec![])
}
pub fn diagnostics(&self, file_id: FileId) -> Result<Vec<(Diagnostic, Option<QuickFix>)>> {
let syntax = self.file_syntax(file_id)?;
let mut res = libeditor::diagnostics(&syntax)
.into_iter()
.map(|d| (d, None))
.collect::<Vec<_>>();
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());

View file

@ -93,6 +93,11 @@ impl ModuleMap {
res
}
pub fn suggested_child_mod_path(&self, m: ast::Module) -> Option<PathBuf> {
let name = m.name()?;
Some(PathBuf::from(format!("../{}.rs", name.text())))
}
fn links(
&self,
file_resolver: &FileResolver,

View file

@ -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();

View file

View file

@ -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<CompletionItem> {
})
.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<ScopeId> {
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<ScopeData>,
scope_for: HashMap<SyntaxNode, ScopeId>,
}
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<ScopeId> {
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<Item=ScopeId> + 'a {
generate(self.scope_for(node), move |&scope| self.scopes[scope].parent)
}
}
#[derive(Debug)]
struct ScopeData {
parent: Option<ScopeId>,
entries: Vec<ScopeEntry>
}
struct ScopeEntry {
syntax: SyntaxNode
}
impl ScopeEntry {
fn new(pat: ast::BindPat) -> Option<ScopeEntry> {
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()
}
}

View file

@ -9,6 +9,7 @@ mod edit;
mod code_actions;
mod typing;
mod completion;
mod scope;
use libsyntax2::{
File, TextUnit, TextRange, SyntaxNodeRef,

View file

@ -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<ScopeId> {
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<ScopeData>,
scope_for: HashMap<SyntaxNode, ScopeId>,
}
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<Item=ScopeId> + '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<ScopeId> {
ancestors(node)
.filter_map(|it| self.scope_for.get(&it.owned()).map(|&scope| scope))
.next()
}
}
#[derive(Debug)]
struct ScopeData {
parent: Option<ScopeId>,
entries: Vec<ScopeEntry>
}
pub struct ScopeEntry {
syntax: SyntaxNode
}
impl ScopeEntry {
fn new(pat: ast::BindPat) -> Option<ScopeEntry> {
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()
}
}

View file

@ -29,8 +29,8 @@ pub(crate) fn literal(p: &mut Parser) -> Option<CompletedMarker> {
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<CompletedMarker> {

View file

@ -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<req::PublishDiagnosticsParams> {
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,