diff --git a/src/bin/languageserver/mod.rs b/src/bin/languageserver/mod.rs index 4f7042bee..5a90941c8 100644 --- a/src/bin/languageserver/mod.rs +++ b/src/bin/languageserver/mod.rs @@ -3,6 +3,7 @@ use num_traits::ToPrimitive; use rust_lapper::{Interval, Lapper}; use serde_json::Value; +use solang::sema::ast::Type; use solang::{ codegen::codegen, codegen::{self, Expression}, @@ -11,7 +12,6 @@ use solang::{ sema::{ast, builtin::get_prototype, symtable, tags::render}, Target, }; -use solang::sema::ast::Type; use solang_parser::pt; use std::{collections::HashMap, ffi::OsString, path::PathBuf}; use tokio::sync::Mutex; @@ -26,10 +26,14 @@ type ReferenceEntry = Interval; enum DefinitionIndex { Function(usize), Variable(usize), + ConstantVariable(Option, usize), + ContractVariable(usize, usize), Struct(usize), Field(Type, usize), Enum(usize), Variant(usize, usize), + Contract(usize), + Event(usize), } type Definitions = HashMap; @@ -40,19 +44,18 @@ struct Cache { references: Lapper, } -// /// Stores information used by language server for every opened file -// struct Files { -// hovers: HashMap, -// text_buffers: HashMap, -// } +/// Stores information used by language server for every opened file +struct Files { + caches: HashMap, + text_buffers: HashMap, +} pub struct SolangServer { client: Client, target: Target, importpaths: Vec, importmaps: Vec<(String, PathBuf)>, - files: Mutex>, - text_buffers: Mutex>, + files: Mutex, definitions: Mutex, } @@ -83,8 +86,10 @@ pub async fn start_server(language_args: &LanguageServerCommand) -> ! { target, importpaths, importmaps, - files: Mutex::new(HashMap::new()), - text_buffers: Mutex::new(HashMap::new()), + files: Mutex::new(Files { + caches: HashMap::new(), + text_buffers: HashMap::new(), + }), definitions: Mutex::new(HashMap::new()), }); @@ -97,7 +102,7 @@ impl SolangServer { /// Parse file async fn parse_file(&self, uri: Url) { let mut resolver = FileResolver::new(); - for (path, contents) in self.text_buffers.lock().await.iter() { + for (path, contents) in self.files.lock().await.text_buffers.iter() { resolver.set_file_contents(path.to_str().unwrap(), contents.clone()); } if let Ok(path) = uri.to_file_path() { @@ -134,9 +139,6 @@ impl SolangServer { // codegen all the contracts; some additional errors/warnings will be detected here codegen(&mut ns, &Default::default()); - // use std::fs; - // fs::write("/tmp/foo",format!("{:#?}", ns)).expect("Unable to write file"); - diags.extend(ns.diagnostics.iter().filter_map(|diag| { if diag.loc.file_no() != ns.top_file_no() { // The first file is the one we wanted to parse; others are imported @@ -185,25 +187,13 @@ impl SolangServer { let (caches, definitions) = Builder::build(&ns); - use std::fs::OpenOptions; - use std::io::Write; - let mut data_file = OpenOptions::new() - .append(true) - .open("/tmp/caches") - .expect("cannot open file"); let mut files = self.files.lock().await; for (f, c) in ns.files.iter().zip(caches.into_iter()) { - // TODO + // TODO files from different folder? let fname = dir.join(f.file_name()); - data_file - .write(format!("{:#?}\n", fname).as_bytes()) - .expect("write failed"); - files.insert(fname, c); + files.caches.insert(fname, c); } *self.definitions.lock().await = definitions; - data_file - .write(format!("=======================================\n").as_bytes()) - .expect("write failed"); res.await; } } @@ -267,7 +257,13 @@ impl<'a> Builder<'a> { val: make_code_block(val), }, )); - self.definitions.insert(DefinitionIndex::Variable(*var_no), (self.ns.files[loc.file_no()].path.clone(), loc_to_range(loc, &self.ns.files[loc.file_no()]))); + self.definitions.insert( + DefinitionIndex::Variable(*var_no), + ( + self.ns.files[loc.file_no()].path.clone(), + loc_to_range(loc, &self.ns.files[loc.file_no()]), + ), + ); } ast::Statement::If(_, _, expr, stat1, stat2) => { self.expression(expr, symtab); @@ -333,7 +329,13 @@ impl<'a> Builder<'a> { }, )); // TODO - self.definitions.insert(DefinitionIndex::Variable(*var_no), (self.ns.files[param.loc.file_no()].path.clone(), loc_to_range(¶m.loc, &self.ns.files[param.loc.file_no()]))); + self.definitions.insert( + DefinitionIndex::Variable(*var_no), + ( + self.ns.files[param.loc.file_no()].path.clone(), + loc_to_range(¶m.loc, &self.ns.files[param.loc.file_no()]), + ), + ); } ast::DestructureField::None => (), } @@ -449,10 +451,6 @@ impl<'a> Builder<'a> { )); } ast::Expression::NumberLiteral { loc, ty, value,.. } => { - // let reference = match ty { - // Type::Enum(id) => Some(DefinitionIndex::Variant(*id, value.to_u64().unwrap() as _)), - // _ => None, - // }; if let Type::Enum(id) = ty { self.references.push(( get_file_no_from_loc(loc).unwrap(), @@ -700,7 +698,7 @@ impl<'a> Builder<'a> { ReferenceEntry { start: loc.start(), stop: loc.end(), - val: DefinitionIndex::Variable(*var_no), + val: DefinitionIndex::ConstantVariable(*contract_no, *var_no), }, )); } @@ -716,6 +714,14 @@ impl<'a> Builder<'a> { val: make_code_block(val), }, )); + self.references.push(( + get_file_no_from_loc(loc).unwrap(), + ReferenceEntry { + start: loc.start(), + stop: loc.end(), + val: DefinitionIndex::ContractVariable(*contract_no, *var_no), + }, + )); } // Load expression ast::Expression::Load { expr, .. } @@ -756,18 +762,15 @@ impl<'a> Builder<'a> { ast::Expression::StructMember { loc, expr, field, ty } => { self.expression(expr, symtab); // TODO - match &**expr { - solang::sema::ast::Expression::Variable {ty, .. } => { - self.references.push(( - get_file_no_from_loc(loc).unwrap(), - ReferenceEntry { - start: loc.start(), - stop: loc.end(), - val: DefinitionIndex::Field(ty.clone(), *field), - }, - )); - } - _ => {} + if let solang::sema::ast::Expression::Variable {ty, .. } = &**expr { + self.references.push(( + get_file_no_from_loc(loc).unwrap(), + ReferenceEntry { + start: loc.start(), + stop: loc.end(), + val: DefinitionIndex::Field(ty.clone(), *field), + }, + )); } self.hovers.push(( get_file_no_from_loc(loc).unwrap(), @@ -1053,7 +1056,7 @@ impl<'a> Builder<'a> { fn build(ns: &ast::Namespace) -> (Vec, Definitions) { let mut builder = Builder { hovers: Vec::new(), - definitions: HashMap::new(), + definitions: HashMap::new(), references: Vec::new(), ns, }; @@ -1068,7 +1071,13 @@ impl<'a> Builder<'a> { val: format!("_{nam}_ {discriminant}"), }, )); - builder.definitions.insert(DefinitionIndex::Variant(ei, discriminant), (ns.files[loc.file_no()].path.clone(), loc_to_range(loc, &ns.files[loc.file_no()]))); + builder.definitions.insert( + DefinitionIndex::Variant(ei, discriminant), + ( + ns.files[loc.file_no()].path.clone(), + loc_to_range(loc, &ns.files[loc.file_no()]), + ), + ); } builder.hovers.push(( @@ -1079,14 +1088,26 @@ impl<'a> Builder<'a> { val: render(&enum_decl.tags[..]), }, )); - builder.definitions.insert(DefinitionIndex::Enum(ei), (ns.files[enum_decl.loc.file_no()].path.clone(), loc_to_range(&enum_decl.loc, &ns.files[enum_decl.loc.file_no()]))); + builder.definitions.insert( + DefinitionIndex::Enum(ei), + ( + ns.files[enum_decl.loc.file_no()].path.clone(), + loc_to_range(&enum_decl.loc, &ns.files[enum_decl.loc.file_no()]), + ), + ); } for (si, struct_decl) in builder.ns.structs.iter().enumerate() { if let pt::Loc::File(_, start, _) = &struct_decl.loc { for (fi, field) in struct_decl.fields.iter().enumerate() { builder.field(field); - builder.definitions.insert(DefinitionIndex::Field(Type::Struct(ast::StructType::UserDefined(si)), fi), (ns.files[field.loc.file_no()].path.clone(), loc_to_range(&field.loc, &ns.files[field.loc.file_no()]))); + builder.definitions.insert( + DefinitionIndex::Field(Type::Struct(ast::StructType::UserDefined(si)), fi), + ( + ns.files[field.loc.file_no()].path.clone(), + loc_to_range(&field.loc, &ns.files[field.loc.file_no()]), + ), + ); } builder.hovers.push(( @@ -1097,7 +1118,13 @@ impl<'a> Builder<'a> { val: render(&struct_decl.tags[..]), }, )); - builder.definitions.insert(DefinitionIndex::Struct(si), (ns.files[struct_decl.loc.file_no()].path.clone(), loc_to_range(&struct_decl.loc, &ns.files[struct_decl.loc.file_no()]))); + builder.definitions.insert( + DefinitionIndex::Struct(si), + ( + ns.files[struct_decl.loc.file_no()].path.clone(), + loc_to_range(&struct_decl.loc, &ns.files[struct_decl.loc.file_no()]), + ), + ); } } @@ -1137,10 +1164,14 @@ impl<'a> Builder<'a> { val: builder.expanded_ty(¶m.ty), }, )); - if let Some(var_no) = func.symtable.arguments.get(i) { - if let Some(var_no) = var_no { - builder.definitions.insert(DefinitionIndex::Variable(*var_no), (builder.ns.files[param.loc.file_no()].path.clone(), loc_to_range(¶m.loc, &builder.ns.files[param.loc.file_no()]))); - } + if let Some(Some(var_no)) = func.symtable.arguments.get(i) { + builder.definitions.insert( + DefinitionIndex::Variable(*var_no), + ( + builder.ns.files[param.loc.file_no()].path.clone(), + loc_to_range(¶m.loc, &builder.ns.files[param.loc.file_no()]), + ), + ); } } @@ -1159,10 +1190,16 @@ impl<'a> Builder<'a> { builder.statement(stmt, &func.symtable); } - builder.definitions.insert(DefinitionIndex::Function(i), (ns.files[func.loc.file_no()].path.clone(), loc_to_range(&func.loc, &ns.files[func.loc.file_no()]))); + builder.definitions.insert( + DefinitionIndex::Function(i), + ( + ns.files[func.loc.file_no()].path.clone(), + loc_to_range(&func.loc, &ns.files[func.loc.file_no()]), + ), + ); } - for constant in &builder.ns.constants { + for (i, constant) in builder.ns.constants.iter().enumerate() { let samptb = symtable::Symtable::new(); builder.contract_variable(constant, &samptb); @@ -1174,10 +1211,17 @@ impl<'a> Builder<'a> { val: render(&constant.tags[..]), }, )); - // builder.definitions.insert(DefinitionIndex::Variable(i), (ns.files[func.loc.file_no()].clone(), func.loc)); + + builder.definitions.insert( + DefinitionIndex::ConstantVariable(None, i), + ( + ns.files[constant.loc.file_no()].path.clone(), + loc_to_range(&constant.loc, &ns.files[constant.loc.file_no()]), + ), + ); } - for contract in &builder.ns.contracts { + for (ci, contract) in builder.ns.contracts.iter().enumerate() { builder.hovers.push(( get_file_no_from_loc(&contract.loc).unwrap(), HoverEntry { @@ -1187,7 +1231,15 @@ impl<'a> Builder<'a> { }, )); - for variable in &contract.variables { + builder.definitions.insert( + DefinitionIndex::Contract(ci), + ( + ns.files[contract.loc.file_no()].path.clone(), + loc_to_range(&contract.loc, &ns.files[contract.loc.file_no()]), + ), + ); + + for (i, variable) in contract.variables.iter().enumerate() { let symtable = symtable::Symtable::new(); builder.contract_variable(variable, &symtable); @@ -1199,10 +1251,18 @@ impl<'a> Builder<'a> { val: render(&variable.tags[..]), }, )); + + builder.definitions.insert( + DefinitionIndex::ContractVariable(ci, i), + ( + ns.files[variable.loc.file_no()].path.clone(), + loc_to_range(&variable.loc, &ns.files[variable.loc.file_no()]), + ), + ); } } - for event in &builder.ns.events { + for (ei, event) in builder.ns.events.iter().enumerate() { for field in &event.fields { builder.field(field); } @@ -1215,29 +1275,55 @@ impl<'a> Builder<'a> { val: render(&event.tags[..]), }, )); + + builder.definitions.insert( + DefinitionIndex::Event(ei), + ( + ns.files[event.loc.file_no()].path.clone(), + loc_to_range(&event.loc, &ns.files[event.loc.file_no()]), + ), + ); } for lookup in builder.hovers.iter_mut() { - if let Some(msg) = - builder - .ns - .hover_overrides - .get(&pt::Loc::File(lookup.0, lookup.1.start, lookup.1.stop)) - { + if let Some(msg) = builder.ns.hover_overrides.get(&pt::Loc::File( + lookup.0, + lookup.1.start, + lookup.1.stop, + )) { lookup.1.val = msg.clone(); } } - use std::fs; - fs::write("/tmp/definitions",format!("{:#?}", builder.definitions)).expect("Unable to write file"); - - let caches = ns.files.iter().enumerate().map(|(i, f)| Cache { - file: f.clone(), - hovers: Lapper::new(builder.hovers.iter().filter(|h| h.0 == i).map(|(_, i)| i.clone()).collect()), - references: Lapper::new(builder.references.iter().filter(|h| h.0 == i).map(|(_, i)| i.clone()).collect()), - // definitions: builder.definitions.clone(), - }) - .collect(); + // use std::fs; + // fs::write("/tmp/definitions", format!("{:#?}", builder.definitions)) + // .expect("Unable to write file"); + + let caches = ns + .files + .iter() + .enumerate() + .map(|(i, f)| Cache { + file: f.clone(), + hovers: Lapper::new( + builder + .hovers + .iter() + .filter(|h| h.0 == i) + .map(|(_, i)| i.clone()) + .collect(), + ), + references: Lapper::new( + builder + .references + .iter() + .filter(|h| h.0 == i) + .map(|(_, i)| i.clone()) + .collect(), + ), + // definitions: builder.definitions.clone(), + }) + .collect(); // Cache { // file: ns.files[ns.top_file_no()].clone(), @@ -1378,9 +1464,10 @@ impl LanguageServer for SolangServer { match uri.to_file_path() { Ok(path) => { - self.text_buffers + self.files .lock() .await + .text_buffers .insert(path, params.text_document.text); self.parse_file(uri).await; } @@ -1397,7 +1484,7 @@ impl LanguageServer for SolangServer { match uri.to_file_path() { Ok(path) => { - if let Some(text_buf) = self.text_buffers.lock().await.get_mut(&path) { + if let Some(text_buf) = self.files.lock().await.text_buffers.get_mut(&path) { *text_buf = params .content_changes .into_iter() @@ -1418,7 +1505,7 @@ impl LanguageServer for SolangServer { if let Some(text) = params.text { if let Ok(path) = uri.to_file_path() { - if let Some(text_buf) = self.text_buffers.lock().await.get_mut(&path) { + if let Some(text_buf) = self.files.lock().await.text_buffers.get_mut(&path) { *text_buf = text; } } @@ -1431,8 +1518,9 @@ impl LanguageServer for SolangServer { let uri = params.text_document.uri; if let Ok(path) = uri.to_file_path() { - self.files.lock().await.remove(&path); - self.text_buffers.lock().await.remove(&path); + let mut files = self.files.lock().await; + files.caches.remove(&path); + files.text_buffers.remove(&path); } self.client.publish_diagnostics(uri, vec![], None).await; @@ -1453,7 +1541,7 @@ impl LanguageServer for SolangServer { if let Ok(path) = uri.to_file_path() { let files = &self.files.lock().await; - if let Some(cache) = files.get(&path) { + if let Some(cache) = files.caches.get(&path) { let offset = cache .file .get_offset(pos.line as usize, pos.character as usize); @@ -1480,60 +1568,34 @@ impl LanguageServer for SolangServer { Ok(None) } - async fn goto_definition(&self, params: GotoDefinitionParams) -> Result> { - use std::fs::OpenOptions; - use std::io::Write; - let mut data_file = OpenOptions::new() - .append(true) - .open("/tmp/def") - .expect("cannot open file"); - data_file - .write(format!("=======================================\ndefinition executed! {:#?}\n", params).as_bytes()) - .expect("write failed"); - + async fn goto_definition( + &self, + params: GotoDefinitionParams, + ) -> Result> { let uri = params.text_document_position_params.text_document.uri; if let Ok(path) = uri.to_file_path() { let files = self.files.lock().await; - if let Some(cache) = files.get(&path) { - data_file - .write(format!("definition continuation! found file in files\n").as_bytes()) - .expect("write failed"); + if let Some(cache) = files.caches.get(&path) { let f = &cache.file; - let offset = f.get_offset(params.text_document_position_params.position.line as _, params.text_document_position_params.position.character as _); - data_file - .write(format!("definition continuation! {:#?}, {:#?}, {:#?}\n", f.file_name(), f.cache_no.unwrap(), offset).as_bytes()) - .expect("write failed"); + let offset = f.get_offset( + params.text_document_position_params.position.line as _, + params.text_document_position_params.position.character as _, + ); if let Some(reference) = cache .references .find(offset, offset) .min_by(|a, b| (a.stop - a.start).cmp(&(b.stop - b.start))) { - data_file - .write(format!("found hover: {:#?}\n", reference).as_bytes()) - .expect("write failed"); - let di = &reference.val; - data_file - .write(format!("found definition index from hover: {:#?}\n", di).as_bytes()) - .expect("write failed"); let definitions = self.definitions.lock().await; if let Some((path, range)) = definitions.get(di) { - data_file - .write(format!("found corresponding definition index in the cache: {:#?} - {:#?}\n", path, range).as_bytes()) - .expect("write failed"); let uri = Url::from_file_path(path).unwrap(); - data_file - .write(format!("uri: {:#?}\n", uri).as_bytes()) - .expect("write failed"); - let ret = Ok(Some(GotoDefinitionResponse::Scalar(Location { + let ret = Ok(Some(GotoDefinitionResponse::Scalar(Location { uri, range: *range, }))); - data_file - .write(format!("definition response: {:#?}\n", ret).as_bytes()) - .expect("write failed"); return ret; } } @@ -1560,6 +1622,7 @@ fn get_file_no_from_loc(loc: &pt::Loc) -> Option { None } } + fn make_code_block(s: impl AsRef) -> String { format!("```solidity\n{}\n```", s.as_ref()) }