From 25192213bbf988e0db59d9fb3acf94cb22d463b3 Mon Sep 17 00:00:00 2001 From: Sophie Date: Tue, 26 Sep 2023 20:53:04 -0700 Subject: [PATCH 01/25] Checkpoint --- sway-core/src/language/call_path.rs | 34 +++++- .../code_actions/abi_decl/abi_impl.rs | 2 +- .../capabilities/code_actions/abi_decl/mod.rs | 2 +- .../code_actions/common/basic_doc_comment.rs | 2 +- .../code_actions/common/fn_doc_comment.rs | 2 +- .../code_actions/constant_decl/mod.rs | 2 +- .../code_actions/diagnostic/mod.rs | 113 ++++++++++++++++++ .../code_actions/enum_decl/enum_impl.rs | 2 +- .../code_actions/enum_decl/mod.rs | 4 +- .../code_actions/enum_variant/mod.rs | 2 +- .../code_actions/function_decl/mod.rs | 2 +- sway-lsp/src/capabilities/code_actions/mod.rs | 62 ++++++---- .../code_actions/storage_field/mod.rs | 2 +- .../code_actions/struct_decl/mod.rs | 6 +- .../code_actions/struct_decl/struct_impl.rs | 2 +- .../code_actions/struct_decl/struct_new.rs | 2 +- .../code_actions/struct_field/mod.rs | 2 +- .../capabilities/code_actions/trait_fn/mod.rs | 2 +- sway-lsp/src/capabilities/diagnostic.rs | 34 +++++- sway-lsp/src/core/token_map.rs | 14 +++ sway-lsp/src/handlers/request.rs | 1 + 21 files changed, 254 insertions(+), 40 deletions(-) create mode 100644 sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs diff --git a/sway-core/src/language/call_path.rs b/sway-core/src/language/call_path.rs index b5b61c64817..d2936c5d6a2 100644 --- a/sway-core/src/language/call_path.rs +++ b/sway-core/src/language/call_path.rs @@ -4,6 +4,8 @@ use crate::{Ident, Namespace}; use sway_types::{span::Span, Spanned}; +use super::ty::TyDecl; + #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct CallPathTree { pub call_path: CallPath, @@ -82,6 +84,19 @@ impl CallPath { } } + /// Removes the first prefix. Does nothing if prefixes are empty. + pub fn lshift(&self) -> CallPath { + if self.prefixes.is_empty() { + self.clone() + } else { + CallPath { + prefixes: self.prefixes[1..self.prefixes.len()].to_vec(), + suffix: self.suffix.clone(), + is_absolute: self.is_absolute, + } + } + } + pub fn as_vec_string(&self) -> Vec { self.prefixes .iter() @@ -90,7 +105,7 @@ impl CallPath { .collect::>() } - /// Convert a given `CallPath` to an symbol to a full `CallPath` from the root of the project + /// Convert a given [CallPath] to an symbol to a full [CallPath] from the root of the project /// in which the symbol is declared. For example, given a path `pkga::SOME_CONST` where `pkga` /// is an _internal_ library of a package named `my_project`, the corresponding call path is /// `my_project::pkga::SOME_CONST`. @@ -177,4 +192,21 @@ impl CallPath { } } } + + /// Convert a given [CallPath] into a call path suitable for a `use` statement. + /// + /// For example, given a path `pkga::SOME_CONST` where `pkga` is an _internal_ library of a package named + /// `my_project`, the corresponding call path is `pkga::SOME_CONST`. + /// + /// Paths to _external_ libraries such `std::lib1::lib2::my_obj` are left unchanged. + pub fn to_import_path(&self, namespace: &Namespace) -> CallPath { + let converted = self.to_fullpath(namespace); + + if let Some(first) = converted.prefixes.first() { + if namespace.root().name == Some(first.clone()) { + return converted.lshift(); + } + } + converted + } } diff --git a/sway-lsp/src/capabilities/code_actions/abi_decl/abi_impl.rs b/sway-lsp/src/capabilities/code_actions/abi_decl/abi_impl.rs index d9357cb2027..66c5aaad1bc 100644 --- a/sway-lsp/src/capabilities/code_actions/abi_decl/abi_impl.rs +++ b/sway-lsp/src/capabilities/code_actions/abi_decl/abi_impl.rs @@ -22,7 +22,7 @@ impl<'a> GenerateImplCodeAction<'a, TyAbiDecl> for AbiImplCodeAction<'a> { } impl<'a> CodeAction<'a, TyAbiDecl> for AbiImplCodeAction<'a> { - fn new(ctx: CodeActionContext<'a>, decl: &'a TyAbiDecl) -> Self { + fn new(ctx: &CodeActionContext<'a>, decl: &'a TyAbiDecl) -> Self { Self { engines: ctx.engines, decl, diff --git a/sway-lsp/src/capabilities/code_actions/abi_decl/mod.rs b/sway-lsp/src/capabilities/code_actions/abi_decl/mod.rs index 260457bec7c..1ab1f76198e 100644 --- a/sway-lsp/src/capabilities/code_actions/abi_decl/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/abi_decl/mod.rs @@ -7,7 +7,7 @@ use sway_core::{decl_engine::id::DeclId, language::ty::TyAbiDecl}; pub(crate) fn code_actions( decl_id: &DeclId, - ctx: CodeActionContext, + ctx: &CodeActionContext, ) -> Option> { let decl = ctx.engines.de().get_abi(decl_id); Some(vec![AbiImplCodeAction::new(ctx, &decl).code_action()]) diff --git a/sway-lsp/src/capabilities/code_actions/common/basic_doc_comment.rs b/sway-lsp/src/capabilities/code_actions/common/basic_doc_comment.rs index b85305cfe88..ddcc7be2974 100644 --- a/sway-lsp/src/capabilities/code_actions/common/basic_doc_comment.rs +++ b/sway-lsp/src/capabilities/code_actions/common/basic_doc_comment.rs @@ -12,7 +12,7 @@ pub struct BasicDocCommentCodeAction<'a, T: Spanned> { impl<'a, T: Spanned> GenerateDocCodeAction<'a, T> for BasicDocCommentCodeAction<'a, T> {} impl<'a, T: Spanned> CodeAction<'a, T> for BasicDocCommentCodeAction<'a, T> { - fn new(ctx: CodeActionContext<'a>, decl: &'a T) -> Self { + fn new(ctx: &CodeActionContext<'a>, decl: &'a T) -> Self { Self { decl, uri: ctx.uri } } diff --git a/sway-lsp/src/capabilities/code_actions/common/fn_doc_comment.rs b/sway-lsp/src/capabilities/code_actions/common/fn_doc_comment.rs index 6a7e503d507..20dd4650964 100644 --- a/sway-lsp/src/capabilities/code_actions/common/fn_doc_comment.rs +++ b/sway-lsp/src/capabilities/code_actions/common/fn_doc_comment.rs @@ -19,7 +19,7 @@ impl<'a, T: Spanned + Named + FunctionSignature> GenerateDocCodeAction<'a, T> impl<'a, T: Spanned + Named + FunctionSignature> CodeAction<'a, T> for FnDocCommentCodeAction<'a, T> { - fn new(ctx: CodeActionContext<'a>, decl: &'a T) -> Self { + fn new(ctx: &CodeActionContext<'a>, decl: &'a T) -> Self { Self { engines: ctx.engines, decl, diff --git a/sway-lsp/src/capabilities/code_actions/constant_decl/mod.rs b/sway-lsp/src/capabilities/code_actions/constant_decl/mod.rs index ed8e59a4c25..6b1f54a2cdb 100644 --- a/sway-lsp/src/capabilities/code_actions/constant_decl/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/constant_decl/mod.rs @@ -6,7 +6,7 @@ use super::common::basic_doc_comment::BasicDocCommentCodeAction; pub(crate) fn code_actions( decl: &ty::TyConstantDecl, - ctx: CodeActionContext, + ctx: &CodeActionContext, ) -> Option> { Some(vec![BasicDocCommentCodeAction::new(ctx, decl).code_action()]) } diff --git a/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs new file mode 100644 index 00000000000..5ae9be56952 --- /dev/null +++ b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs @@ -0,0 +1,113 @@ +use std::{collections::HashMap, ops::Deref}; + +use crate::{ + capabilities::{ + code_actions::{diagnostic, CodeAction, CodeActionContext}, + diagnostic::DiagnosticData, + }, + core::token::{TypeDefinition, TypedAstToken}, +}; +use lsp_types::{ + CodeAction as LspCodeAction, CodeActionKind, CodeActionOrCommand, Range, TextEdit, + WorkspaceEdit, +}; +use serde_json::Value; +use sway_core::{ + language::{ + ty::{self, ConstantDecl, TyDecl}, + CallPath, + }, + namespace, Namespace, +}; +use sway_types::Span; + +use super::CODE_ACTION_IMPORT_TITLE; + +pub(crate) fn code_actions( + ctx: &CodeActionContext, + namespace: &Option, +) -> Option> { + // TODO: check for diagnostics + if let Some(namespace) = namespace { + Some(vec![import_code_action(ctx, namespace)]) + } else { + None + } +} + +/// Returns a [CodeActionOrCommand] for the given code action. +fn import_code_action(ctx: &CodeActionContext, namespace: &Namespace) -> CodeActionOrCommand { + let diag_data = ctx + .diagnostics + .iter() + .find_map(|diag| serde_json::from_value::(diag.clone().data.unwrap()).ok()) + .unwrap(); + + eprintln!("diag_data: {:?}", diag_data); + + // Check if there is a type to import using the name from the diagnostic data. + let call_paths: Vec = ctx + .tokens + .tokens_for_name(&diag_data.name_to_import) + .filter_map(|(_, token)| { + // If the typed token is a declaration, then we can import it. + if let Some(TypedAstToken::TypedDeclaration(ty_decl)) = token.typed.as_ref() { + // match token.type_def.as_ref() { + // Some(TypeDefinition::Ident(_)) => + return match ty_decl { + TyDecl::StructDecl(decl) => { + let struct_decl = ctx.engines.de().get_struct(&decl.decl_id); + let call_path = struct_decl.call_path.to_import_path(&namespace); + Some(call_path) + } + TyDecl::EnumDecl(decl) => { + let enum_decl = ctx.engines.de().get_enum(&decl.decl_id); + let call_path = enum_decl.call_path.to_import_path(&namespace); + Some(call_path) + } + TyDecl::ConstantDecl(decl) => { + let constant_decl = ctx.engines.de().get_constant(&decl.decl_id); + let call_path = constant_decl.call_path.to_import_path(&namespace); + Some(call_path) + } + // TyDecl::TraitDecl(decl) => { + // let trait_decl = ctx.engines.de().get_trait(&decl.decl_id); + // let call_path = trait_decl.call_path.to_import_path(&namespace); + // Some(call_path) + // } + // TyDecl::FunctionDecl(decl) => { + // let function_decl = ctx.engines.de().get_function(&decl.decl_id); + // let call_path = function_decl.call_path.to_import_path(&namespace); + // Some(call_path) + // } + // TyDecl::TypeAliasDecl(decl) => { + // let type_alias_decl = ctx.engines.de().get_type_alias(&decl.decl_id); + // let call_path = type_alias_decl.call_path.to_import_path(&namespace); + // Some(call_path) + // } + _ => None, // TODO: other types + }; + // _ => None, + // } + } + None + }) + .collect(); + + let text_edit = TextEdit { + range: Range::default(), // TODO: sort within the imports + new_text: format!("use {};\n", call_paths[0]), // TODO: multiple imports + }; + let changes = HashMap::from([(ctx.uri.clone(), vec![text_edit])]); + + CodeActionOrCommand::CodeAction(LspCodeAction { + title: format!("{} {}", CODE_ACTION_IMPORT_TITLE, call_paths[0]), + kind: Some(CodeActionKind::QUICKFIX), + edit: Some(WorkspaceEdit { + changes: Some(changes), + ..Default::default() + }), + data: Some(Value::String(ctx.uri.to_string())), + ..Default::default() + }) +} diff --git a/sway-lsp/src/capabilities/code_actions/enum_decl/enum_impl.rs b/sway-lsp/src/capabilities/code_actions/enum_decl/enum_impl.rs index e241f1cbb27..32220432295 100644 --- a/sway-lsp/src/capabilities/code_actions/enum_decl/enum_impl.rs +++ b/sway-lsp/src/capabilities/code_actions/enum_decl/enum_impl.rs @@ -17,7 +17,7 @@ impl<'a> GenerateImplCodeAction<'a, TyEnumDecl> for EnumImplCodeAction<'a> { } impl<'a> CodeAction<'a, TyEnumDecl> for EnumImplCodeAction<'a> { - fn new(ctx: CodeActionContext<'a>, decl: &'a TyEnumDecl) -> Self { + fn new(ctx: &CodeActionContext<'a>, decl: &'a TyEnumDecl) -> Self { Self { decl, uri: ctx.uri } } diff --git a/sway-lsp/src/capabilities/code_actions/enum_decl/mod.rs b/sway-lsp/src/capabilities/code_actions/enum_decl/mod.rs index 043ae951092..665ef870558 100644 --- a/sway-lsp/src/capabilities/code_actions/enum_decl/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/enum_decl/mod.rs @@ -9,11 +9,11 @@ use super::common::basic_doc_comment::BasicDocCommentCodeAction; pub(crate) fn code_actions( decl_id: &DeclId, - ctx: CodeActionContext, + ctx: &CodeActionContext, ) -> Option> { let decl = ctx.engines.de().get_enum(decl_id); Some(vec![ - EnumImplCodeAction::new(ctx.clone(), &decl).code_action(), + EnumImplCodeAction::new(ctx, &decl).code_action(), BasicDocCommentCodeAction::new(ctx, &decl).code_action(), ]) } diff --git a/sway-lsp/src/capabilities/code_actions/enum_variant/mod.rs b/sway-lsp/src/capabilities/code_actions/enum_variant/mod.rs index bffd7ce4589..6ac8a57f1bd 100644 --- a/sway-lsp/src/capabilities/code_actions/enum_variant/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/enum_variant/mod.rs @@ -6,7 +6,7 @@ use super::common::basic_doc_comment::BasicDocCommentCodeAction; pub(crate) fn code_actions( decl: &ty::TyEnumVariant, - ctx: CodeActionContext, + ctx: &CodeActionContext, ) -> Option> { Some(vec![BasicDocCommentCodeAction::new(ctx, decl).code_action()]) } diff --git a/sway-lsp/src/capabilities/code_actions/function_decl/mod.rs b/sway-lsp/src/capabilities/code_actions/function_decl/mod.rs index 7cd6c0559ee..35f670cfd90 100644 --- a/sway-lsp/src/capabilities/code_actions/function_decl/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/function_decl/mod.rs @@ -6,7 +6,7 @@ use super::common::fn_doc_comment::FnDocCommentCodeAction; pub(crate) fn code_actions( decl: &ty::TyFunctionDecl, - ctx: CodeActionContext, + ctx: &CodeActionContext, ) -> Option> { Some(vec![FnDocCommentCodeAction::new(ctx, decl).code_action()]) } diff --git a/sway-lsp/src/capabilities/code_actions/mod.rs b/sway-lsp/src/capabilities/code_actions/mod.rs index 48abda9da65..6221c58d46b 100644 --- a/sway-lsp/src/capabilities/code_actions/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/mod.rs @@ -1,6 +1,7 @@ pub mod abi_decl; pub mod common; pub mod constant_decl; +pub mod diagnostic; pub mod enum_decl; pub mod enum_variant; pub mod function_decl; @@ -17,7 +18,7 @@ use crate::core::{ pub use crate::error::DocumentError; use lsp_types::{ CodeAction as LspCodeAction, CodeActionDisabled, CodeActionKind, CodeActionOrCommand, - CodeActionResponse, Position, Range, TextEdit, Url, WorkspaceEdit, + CodeActionResponse, Diagnostic, Position, Range, TextEdit, Url, WorkspaceEdit, }; use serde_json::Value; use std::{collections::HashMap, sync::Arc}; @@ -27,6 +28,7 @@ use sway_types::Spanned; pub(crate) const CODE_ACTION_IMPL_TITLE: &str = "Generate impl for"; pub(crate) const CODE_ACTION_NEW_TITLE: &str = "Generate `new`"; pub(crate) const CODE_ACTION_DOC_TITLE: &str = "Generate a documentation template"; +pub(crate) const CODE_ACTION_IMPORT_TITLE: &str = "Import"; #[derive(Clone)] pub(crate) struct CodeActionContext<'a> { @@ -34,6 +36,7 @@ pub(crate) struct CodeActionContext<'a> { tokens: &'a TokenMap, token: &'a Token, uri: &'a Url, + diagnostics: &'a Vec, } pub fn code_actions( @@ -41,7 +44,10 @@ pub fn code_actions( range: &Range, uri: &Url, temp_uri: &Url, + diagnostics: &Vec, ) -> Option { + eprintln!("got here 1"); + let engines = session.engines.read(); let (_, token) = session .token_map() @@ -52,34 +58,50 @@ pub fn code_actions( tokens: session.token_map(), token: &token, uri, + diagnostics, }; - match token.typed.as_ref()? { - TypedAstToken::TypedDeclaration(decl) => match decl { - ty::TyDecl::AbiDecl(ty::AbiDecl { decl_id, .. }) => { - abi_decl::code_actions(decl_id, ctx) - } - ty::TyDecl::StructDecl(ty::StructDecl { decl_id, .. }) => { - struct_decl::code_actions(decl_id, ctx) + eprintln!("got here 2"); + + let actions_by_type = token + .typed + .as_ref() + .and_then(|typed_token| match typed_token { + TypedAstToken::TypedDeclaration(decl) => match decl { + ty::TyDecl::AbiDecl(ty::AbiDecl { decl_id, .. }) => { + abi_decl::code_actions(decl_id, &ctx) + } + ty::TyDecl::StructDecl(ty::StructDecl { decl_id, .. }) => { + struct_decl::code_actions(decl_id, &ctx) + } + ty::TyDecl::EnumDecl(ty::EnumDecl { decl_id, .. }) => { + enum_decl::code_actions(decl_id, &ctx) + } + _ => None, + }, + TypedAstToken::TypedFunctionDeclaration(decl) => { + function_decl::code_actions(decl, &ctx) } - ty::TyDecl::EnumDecl(ty::EnumDecl { decl_id, .. }) => { - enum_decl::code_actions(decl_id, ctx) + TypedAstToken::TypedStorageField(decl) => storage_field::code_actions(decl, &ctx), + TypedAstToken::TypedConstantDeclaration(decl) => { + constant_decl::code_actions(decl, &ctx) } + TypedAstToken::TypedEnumVariant(decl) => enum_variant::code_actions(decl, &ctx), + TypedAstToken::TypedStructField(decl) => struct_field::code_actions(decl, &ctx), + TypedAstToken::TypedTraitFn(decl) => trait_fn::code_actions(decl, &ctx), _ => None, - }, - TypedAstToken::TypedFunctionDeclaration(decl) => function_decl::code_actions(decl, ctx), - TypedAstToken::TypedStorageField(decl) => storage_field::code_actions(decl, ctx), - TypedAstToken::TypedConstantDeclaration(decl) => constant_decl::code_actions(decl, ctx), - TypedAstToken::TypedEnumVariant(decl) => enum_variant::code_actions(decl, ctx), - TypedAstToken::TypedStructField(decl) => struct_field::code_actions(decl, ctx), - TypedAstToken::TypedTraitFn(decl) => trait_fn::code_actions(decl, ctx), - _ => None, - } + }) + .unwrap_or_default(); + + let actions_by_diagnostic = + diagnostic::code_actions(&ctx, &session.namespace()).unwrap_or_default(); + + Some([actions_by_type, actions_by_diagnostic].concat()) } pub(crate) trait CodeAction<'a, T: Spanned> { /// Creates a new [CodeAction] with the given [Engines], delcaration type, and [Url]. - fn new(ctx: CodeActionContext<'a>, decl: &'a T) -> Self; + fn new(ctx: &CodeActionContext<'a>, decl: &'a T) -> Self; /// Returns a [String] of text to insert into the document. fn new_text(&self) -> String; diff --git a/sway-lsp/src/capabilities/code_actions/storage_field/mod.rs b/sway-lsp/src/capabilities/code_actions/storage_field/mod.rs index b335d076eeb..04921d2c4e6 100644 --- a/sway-lsp/src/capabilities/code_actions/storage_field/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/storage_field/mod.rs @@ -6,7 +6,7 @@ use super::common::basic_doc_comment::BasicDocCommentCodeAction; pub(crate) fn code_actions( decl: &ty::TyStorageField, - ctx: CodeActionContext, + ctx: &CodeActionContext, ) -> Option> { Some(vec![BasicDocCommentCodeAction::new(ctx, decl).code_action()]) } diff --git a/sway-lsp/src/capabilities/code_actions/struct_decl/mod.rs b/sway-lsp/src/capabilities/code_actions/struct_decl/mod.rs index c7c0e3cdb42..4c6ffffa6dd 100644 --- a/sway-lsp/src/capabilities/code_actions/struct_decl/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/struct_decl/mod.rs @@ -10,12 +10,12 @@ use super::common::basic_doc_comment::BasicDocCommentCodeAction; pub(crate) fn code_actions( decl_id: &DeclId, - ctx: CodeActionContext, + ctx: &CodeActionContext, ) -> Option> { let decl = ctx.engines.de().get_struct(decl_id); Some(vec![ - StructImplCodeAction::new(ctx.clone(), &decl).code_action(), - StructNewCodeAction::new(ctx.clone(), &decl).code_action(), + StructImplCodeAction::new(ctx, &decl).code_action(), + StructNewCodeAction::new(ctx, &decl).code_action(), BasicDocCommentCodeAction::new(ctx, &decl).code_action(), ]) } diff --git a/sway-lsp/src/capabilities/code_actions/struct_decl/struct_impl.rs b/sway-lsp/src/capabilities/code_actions/struct_decl/struct_impl.rs index fc509675db7..b532c09b0b5 100644 --- a/sway-lsp/src/capabilities/code_actions/struct_decl/struct_impl.rs +++ b/sway-lsp/src/capabilities/code_actions/struct_decl/struct_impl.rs @@ -17,7 +17,7 @@ impl<'a> GenerateImplCodeAction<'a, TyStructDecl> for StructImplCodeAction<'a> { } impl<'a> CodeAction<'a, TyStructDecl> for StructImplCodeAction<'a> { - fn new(ctx: CodeActionContext<'a>, decl: &'a TyStructDecl) -> Self { + fn new(ctx: &CodeActionContext<'a>, decl: &'a TyStructDecl) -> Self { Self { decl, uri: ctx.uri } } diff --git a/sway-lsp/src/capabilities/code_actions/struct_decl/struct_new.rs b/sway-lsp/src/capabilities/code_actions/struct_decl/struct_new.rs index cb84b823931..08eaffabd94 100644 --- a/sway-lsp/src/capabilities/code_actions/struct_decl/struct_new.rs +++ b/sway-lsp/src/capabilities/code_actions/struct_decl/struct_new.rs @@ -22,7 +22,7 @@ impl<'a> GenerateImplCodeAction<'a, TyStructDecl> for StructNewCodeAction<'a> { } impl<'a> CodeAction<'a, TyStructDecl> for StructNewCodeAction<'a> { - fn new(ctx: CodeActionContext<'a>, decl: &'a TyStructDecl) -> Self { + fn new(ctx: &CodeActionContext<'a>, decl: &'a TyStructDecl) -> Self { // Before the other functions are called, we need to determine if the new function // should be generated in a new impl block, an existing impl block, or not at all. // Find the first impl block for this struct if it exists. diff --git a/sway-lsp/src/capabilities/code_actions/struct_field/mod.rs b/sway-lsp/src/capabilities/code_actions/struct_field/mod.rs index 2ec875de0b0..f1023a49eff 100644 --- a/sway-lsp/src/capabilities/code_actions/struct_field/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/struct_field/mod.rs @@ -6,7 +6,7 @@ use super::common::basic_doc_comment::BasicDocCommentCodeAction; pub(crate) fn code_actions( decl: &ty::TyStructField, - ctx: CodeActionContext, + ctx: &CodeActionContext, ) -> Option> { Some(vec![BasicDocCommentCodeAction::new(ctx, decl).code_action()]) } diff --git a/sway-lsp/src/capabilities/code_actions/trait_fn/mod.rs b/sway-lsp/src/capabilities/code_actions/trait_fn/mod.rs index 3e5f7dd105d..68ead347d6d 100644 --- a/sway-lsp/src/capabilities/code_actions/trait_fn/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/trait_fn/mod.rs @@ -6,7 +6,7 @@ use super::common::fn_doc_comment::FnDocCommentCodeAction; pub(crate) fn code_actions( decl: &ty::TyTraitFn, - ctx: CodeActionContext, + ctx: &CodeActionContext, ) -> Option> { Some(vec![FnDocCommentCodeAction::new(ctx, decl).code_action()]) } diff --git a/sway-lsp/src/capabilities/diagnostic.rs b/sway-lsp/src/capabilities/diagnostic.rs index 69ff60624b1..11bfd41d1eb 100644 --- a/sway-lsp/src/capabilities/diagnostic.rs +++ b/sway-lsp/src/capabilities/diagnostic.rs @@ -2,11 +2,13 @@ use std::collections::HashMap; use std::path::PathBuf; use lsp_types::{Diagnostic, DiagnosticSeverity, DiagnosticTag, Position, Range}; +use serde::{Deserialize, Serialize}; +use serde_json::json; use sway_error::warning::CompileWarning; use sway_error::{error::CompileError, warning::Warning}; use sway_types::{LineCol, SourceEngine, Spanned}; -pub type DiagnosticMap = HashMap; +pub(crate) type DiagnosticMap = HashMap; #[derive(Debug, Default, Clone)] pub struct Diagnostics { @@ -15,10 +17,13 @@ pub struct Diagnostics { } fn get_error_diagnostic(error: &CompileError) -> Diagnostic { + let data = serde_json::to_value(DiagnosticData::try_from(error.clone()).ok()).ok(); + Diagnostic { range: get_range(error.span().line_col()), severity: Some(DiagnosticSeverity::ERROR), message: format!("{error}"), + data, ..Default::default() } } @@ -90,3 +95,30 @@ fn get_warning_diagnostic_tags(warning: &Warning) -> Option> _ => None, } } + +/// Extra data to be sent with a diagnostic and provided in CodeAction context. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct DiagnosticData { + pub name_to_import: String, +} + +impl TryFrom for DiagnosticData { + type Error = anyhow::Error; + + fn try_from(_value: CompileWarning) -> Result { + anyhow::bail!("Not implemented"); + } +} + +impl TryFrom for DiagnosticData { + type Error = anyhow::Error; + + fn try_from(value: CompileError) -> Result { + match value { + CompileError::SymbolNotFound { name, .. } => Ok(DiagnosticData { + name_to_import: name.to_string(), + }), + _ => anyhow::bail!("Not implemented"), + } + } +} diff --git a/sway-lsp/src/core/token_map.rs b/sway-lsp/src/core/token_map.rs index 215f3b12132..b2a8c1eb968 100644 --- a/sway-lsp/src/core/token_map.rs +++ b/sway-lsp/src/core/token_map.rs @@ -45,6 +45,20 @@ impl TokenMap { }) } + /// Return an Iterator of tokens matching the given name. + pub fn tokens_for_name<'s>( + &'s self, + name: &'s String, + ) -> impl 's + Iterator { + self.iter().flat_map(|(ident, token)| { + if ident.name == *name { + Some((ident.clone(), token.clone())) + } else { + None + } + }) + } + /// Given a cursor [Position], return the [TokenIdent] of a token in the /// Iterator if one exists at that position. pub fn idents_at_position(&self, cursor_position: Position, tokens: I) -> Vec diff --git a/sway-lsp/src/handlers/request.rs b/sway-lsp/src/handlers/request.rs index e9d0bcf690a..3d8e5fdfc31 100644 --- a/sway-lsp/src/handlers/request.rs +++ b/sway-lsp/src/handlers/request.rs @@ -228,6 +228,7 @@ pub fn handle_code_action( ¶ms.range, ¶ms.text_document.uri, &temp_uri, + ¶ms.context.diagnostics, )), Err(err) => { tracing::error!("{}", err.to_string()); From 6e1eea70999f5002b266c21204c54edb1617c66a Mon Sep 17 00:00:00 2001 From: Sophie Date: Fri, 29 Sep 2023 17:10:01 -0700 Subject: [PATCH 02/25] recursive submodules --- sway-core/src/language/lexed/mod.rs | 14 ++ sway-core/src/language/module.rs | 66 ++++++++ sway-core/src/language/parsed/module.rs | 14 +- .../src/language/ty/declaration/function.rs | 6 +- sway-core/src/language/ty/module.rs | 14 +- .../ast_node/declaration/function.rs | 7 +- .../ast_node/declaration/trait_fn.rs | 3 +- .../code_actions/diagnostic/mod.rs | 156 ++++++++++-------- sway-lsp/src/capabilities/code_actions/mod.rs | 4 - sway-lsp/src/capabilities/diagnostic.rs | 3 + sway-lsp/src/core/session.rs | 11 +- sway-lsp/src/traverse/dependency.rs | 3 +- sway-lsp/src/traverse/lexed_tree.rs | 5 +- sway-lsp/src/traverse/parsed_tree.rs | 4 +- sway-lsp/src/traverse/typed_tree.rs | 2 +- .../tokens/paths/src/deep_mod/deeper_mod.sw | 2 + .../tests/fixtures/tokens/paths/src/main.sw | 59 ++++--- 17 files changed, 255 insertions(+), 118 deletions(-) diff --git a/sway-core/src/language/lexed/mod.rs b/sway-core/src/language/lexed/mod.rs index 2e6a30a85fc..f3d4de99b79 100644 --- a/sway-core/src/language/lexed/mod.rs +++ b/sway-core/src/language/lexed/mod.rs @@ -4,6 +4,8 @@ use crate::language::ModName; pub use program::LexedProgram; use sway_ast::Module; +use super::{HasModule, HasSubmodules}; + /// A module and its submodules in the form of a tree. #[derive(Debug, Clone)] pub struct LexedModule { @@ -20,3 +22,15 @@ pub struct LexedModule { pub struct LexedSubmodule { pub module: LexedModule, } + +impl HasModule for LexedSubmodule { + fn module(&self) -> &LexedModule { + &self.module + } +} + +impl HasSubmodules for LexedModule { + fn submodules(&self) -> &Vec<(ModName, LexedSubmodule)> { + &self.submodules + } +} diff --git a/sway-core/src/language/module.rs b/sway-core/src/language/module.rs index f19832b2367..4aa64c02ee6 100644 --- a/sway-core/src/language/module.rs +++ b/sway-core/src/language/module.rs @@ -5,3 +5,69 @@ use sway_types::Ident; /// If an alias was given to the `mod`, this will be the alias. If not, this is the submodule's /// library name. pub type ModName = Ident; + +pub trait HasModule +where + T: HasSubmodules, + Self: Sized, +{ + /// Returns the module of this submodule. + fn module(&self) -> &T; +} + +pub trait HasSubmodules +where + E: HasModule, + Self: Sized, +{ + /// Returns the submodules of this module. + fn submodules(&self) -> &Vec<(ModName, E)>; + + /// An iterator yielding all submodules recursively, depth-first. + fn submodules_recursive(&self) -> SubmodulesRecursive { + SubmodulesRecursive { + _module_type: std::marker::PhantomData, + submods: self.submodules().iter(), + current: None, + } + } +} + +/// Iterator type for iterating over submodules. +/// +/// Used rather than `impl Iterator` to enable recursive submodule iteration. +pub struct SubmodulesRecursive<'module, T, E> { + _module_type: std::marker::PhantomData, + submods: std::slice::Iter<'module, (ModName, E)>, + current: Option<( + &'module (ModName, E), + Box>, + )>, +} + +impl<'module, T, E> Iterator for SubmodulesRecursive<'module, T, E> +where + T: HasSubmodules + 'module, + E: HasModule, +{ + type Item = &'module (ModName, E); + fn next(&mut self) -> Option { + loop { + self.current = match self.current.take() { + None => match self.submods.next() { + None => return None, + Some(submod) => { + Some((submod, Box::new(submod.1.module().submodules_recursive()))) + } + }, + Some((submod, mut submods)) => match submods.next() { + Some(next) => { + self.current = Some((submod, submods)); + return Some(next); + } + None => return Some(submod), + }, + } + } + } +} diff --git a/sway-core/src/language/parsed/module.rs b/sway-core/src/language/parsed/module.rs index 661d4217404..6f2812c0473 100644 --- a/sway-core/src/language/parsed/module.rs +++ b/sway-core/src/language/parsed/module.rs @@ -1,5 +1,5 @@ use crate::{ - language::{ModName, Visibility}, + language::{HasModule, HasSubmodules, ModName, Visibility}, transform, }; @@ -33,3 +33,15 @@ pub struct ParseSubmodule { pub mod_name_span: Span, pub visibility: Visibility, } + +impl HasModule for ParseSubmodule { + fn module(&self) -> &ParseModule { + &self.module + } +} + +impl HasSubmodules for ParseModule { + fn submodules(&self) -> &Vec<(ModName, ParseSubmodule)> { + &self.submodules + } +} diff --git a/sway-core/src/language/ty/declaration/function.rs b/sway-core/src/language/ty/declaration/function.rs index 983c1b16a72..e94b8e27eff 100644 --- a/sway-core/src/language/ty/declaration/function.rs +++ b/sway-core/src/language/ty/declaration/function.rs @@ -4,10 +4,11 @@ use std::{ hash::{Hash, Hasher}, }; +use etk_ops::london::Call; use sha2::{Digest, Sha256}; use sway_error::handler::{ErrorEmitted, Handler}; -use crate::semantic_analysis::type_check_context::MonomorphizeHelper; +use crate::{language::CallPath, semantic_analysis::type_check_context::MonomorphizeHelper}; use crate::{ decl_engine::*, @@ -31,6 +32,7 @@ pub struct TyFunctionDecl { pub parameters: Vec, pub implementing_type: Option, pub span: Span, + pub call_path: CallPath, pub attributes: transform::AttributesMap, pub type_parameters: Vec, pub return_type: TypeArgument, @@ -91,6 +93,7 @@ impl HashWithEngines for TyFunctionDecl { purity, // these fields are not hashed because they aren't relevant/a // reliable source of obj v. obj distinction + call_path: _, span: _, attributes: _, implementing_type: _, @@ -249,6 +252,7 @@ impl TyFunctionDecl { }, implementing_type: None, span, + call_path: CallPath::from(Ident::new_no_span("foo".into())), attributes: Default::default(), is_contract_call: false, parameters: Default::default(), diff --git a/sway-core/src/language/ty/module.rs b/sway-core/src/language/ty/module.rs index d7012ca48f2..01e3e305a91 100644 --- a/sway-core/src/language/ty/module.rs +++ b/sway-core/src/language/ty/module.rs @@ -2,8 +2,8 @@ use sway_types::Span; use crate::{ decl_engine::{DeclEngine, DeclRef, DeclRefFunction}, - language::ty::*, language::ModName, + language::{ty::*, HasModule, HasSubmodules}, semantic_analysis::namespace, transform, }; @@ -91,3 +91,15 @@ impl<'module> Iterator for SubmodulesRecursive<'module> { } } } + +impl HasModule for TySubmodule { + fn module(&self) -> &TyModule { + &self.module + } +} + +impl HasSubmodules for TyModule { + fn submodules(&self) -> &Vec<(ModName, TySubmodule)> { + &self.submodules + } +} diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/function.rs b/sway-core/src/semantic_analysis/ast_node/declaration/function.rs index 96d3847a7ae..93701b7925b 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/function.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/function.rs @@ -11,7 +11,7 @@ use crate::{ language::{ parsed::*, ty::{self, TyCodeBlock}, - Visibility, + CallPath, Visibility, }, semantic_analysis::{type_check_context::EnforceTypeArguments, *}, type_system::*, @@ -133,12 +133,15 @@ impl ty::TyFunctionDecl { (visibility, matches!(ctx.abi_mode(), AbiMode::ImplAbiFn(..))) }; + let call_path = CallPath::from(name.clone()).to_fullpath(ctx.namespace); + let function_decl = ty::TyFunctionDecl { name, body: TyCodeBlock::default(), parameters: new_parameters, implementing_type: None, span, + call_path, attributes, return_type, type_parameters: new_type_parameters, @@ -303,6 +306,7 @@ fn test_function_selector_behavior() { body: ty::TyCodeBlock { contents: vec![] }, parameters: vec![], span: Span::dummy(), + call_path: CallPath::from(Ident::new_no_span("foo".into())), attributes: Default::default(), return_type: TypeId::from(0).into(), type_parameters: vec![], @@ -356,6 +360,7 @@ fn test_function_selector_behavior() { }, ], span: Span::dummy(), + call_path: CallPath::from(Ident::new_no_span("foo".into())), attributes: Default::default(), return_type: TypeId::from(0).into(), type_parameters: vec![], diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/trait_fn.rs b/sway-core/src/semantic_analysis/ast_node/declaration/trait_fn.rs index bda3e15f9ad..58fe370750d 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/trait_fn.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/trait_fn.rs @@ -2,7 +2,7 @@ use sway_types::{Span, Spanned}; use crate::{ decl_engine::DeclId, - language::{parsed, ty, Visibility}, + language::{parsed, ty, CallPath, Visibility}, semantic_analysis::type_check_context::EnforceTypeArguments, }; use sway_error::handler::{ErrorEmitted, Handler}; @@ -99,6 +99,7 @@ impl ty::TyTraitFn { AbiMode::NonAbi => None, }, span: self.name.span(), + call_path: CallPath::from(self.name.clone()), attributes: self.attributes.clone(), return_type: self.return_type.clone(), visibility: Visibility::Public, diff --git a/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs index 5ae9be56952..0751c0b49d0 100644 --- a/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs @@ -27,87 +27,105 @@ pub(crate) fn code_actions( ctx: &CodeActionContext, namespace: &Option, ) -> Option> { - // TODO: check for diagnostics if let Some(namespace) = namespace { - Some(vec![import_code_action(ctx, namespace)]) - } else { - None + return import_code_action(ctx, namespace); } + None } /// Returns a [CodeActionOrCommand] for the given code action. -fn import_code_action(ctx: &CodeActionContext, namespace: &Namespace) -> CodeActionOrCommand { - let diag_data = ctx - .diagnostics - .iter() - .find_map(|diag| serde_json::from_value::(diag.clone().data.unwrap()).ok()) - .unwrap(); - - eprintln!("diag_data: {:?}", diag_data); +fn import_code_action( + ctx: &CodeActionContext, + namespace: &Namespace, +) -> Option> { + if let Some(diag_data) = ctx.diagnostics.iter().find_map(|diag| { + let data = diag.clone().data?; + serde_json::from_value::(data).ok() + }) { + // Check if there is a type to import using the name from the diagnostic data. + let call_paths = ctx + .tokens + .tokens_for_name(&diag_data.name_to_import) + .filter_map(|(ident, token)| { + eprintln!("ident: {:?}, ", ident); + eprintln!("token: {:?}", token); - // Check if there is a type to import using the name from the diagnostic data. - let call_paths: Vec = ctx - .tokens - .tokens_for_name(&diag_data.name_to_import) - .filter_map(|(_, token)| { - // If the typed token is a declaration, then we can import it. - if let Some(TypedAstToken::TypedDeclaration(ty_decl)) = token.typed.as_ref() { - // match token.type_def.as_ref() { - // Some(TypeDefinition::Ident(_)) => - return match ty_decl { - TyDecl::StructDecl(decl) => { - let struct_decl = ctx.engines.de().get_struct(&decl.decl_id); - let call_path = struct_decl.call_path.to_import_path(&namespace); - Some(call_path) + // If the typed token is a declaration, then we can import it. + match token.typed.as_ref() { + Some(TypedAstToken::TypedDeclaration(ty_decl)) => { + return match ty_decl { + TyDecl::StructDecl(decl) => { + let struct_decl = ctx.engines.de().get_struct(&decl.decl_id); + let call_path = struct_decl.call_path.to_import_path(&namespace); + Some(call_path) + } + TyDecl::EnumDecl(decl) => { + let enum_decl = ctx.engines.de().get_enum(&decl.decl_id); + let call_path = enum_decl.call_path.to_import_path(&namespace); + Some(call_path) + } + // TyDecl::ConstantDecl(decl) => { + // let constant_decl = ctx.engines.de().get_constant(&decl.decl_id); + // let call_path = constant_decl.call_path.to_import_path(&namespace); + // Some(call_path) + // } + // TyDecl::TraitDecl(decl) => { + // let trait_decl = ctx.engines.de().get_trait(&decl.decl_id); + // let call_path = trait_decl.call_path.to_import_path(&namespace); + // Some(call_path) + // } + // TyDecl::FunctionDecl(decl) => { + // let function_decl = ctx.engines.de().get_function(&decl.decl_id); + // eprintln!("fn decl call_path: {:?}", function_decl.call_path); + // let call_path = function_decl.call_path.to_import_path(&namespace); + // eprintln!("fn call_path: {:?}", call_path); + // Some(call_path) + // } + // TyDecl::TypeAliasDecl(decl) => { + // let type_alias_decl = ctx.engines.de().get_type_alias(&decl.decl_id); + // let call_path = type_alias_decl.call_path.to_import_path(&namespace); + // Some(call_path) + // } + _ => None, // TODO: other types + }; } - TyDecl::EnumDecl(decl) => { - let enum_decl = ctx.engines.de().get_enum(&decl.decl_id); - let call_path = enum_decl.call_path.to_import_path(&namespace); + Some(TypedAstToken::TypedFunctionDeclaration(ty_decl)) => { + let call_path = ty_decl.call_path.to_import_path(&namespace); Some(call_path) } - TyDecl::ConstantDecl(decl) => { - let constant_decl = ctx.engines.de().get_constant(&decl.decl_id); - let call_path = constant_decl.call_path.to_import_path(&namespace); + Some(TypedAstToken::TypedConstantDeclaration(ty_decl)) => { + let call_path = ty_decl.call_path.to_import_path(&namespace); + eprintln!("constant call_path: {:?}", call_path); Some(call_path) } - // TyDecl::TraitDecl(decl) => { - // let trait_decl = ctx.engines.de().get_trait(&decl.decl_id); - // let call_path = trait_decl.call_path.to_import_path(&namespace); - // Some(call_path) - // } - // TyDecl::FunctionDecl(decl) => { - // let function_decl = ctx.engines.de().get_function(&decl.decl_id); - // let call_path = function_decl.call_path.to_import_path(&namespace); - // Some(call_path) - // } - // TyDecl::TypeAliasDecl(decl) => { - // let type_alias_decl = ctx.engines.de().get_type_alias(&decl.decl_id); - // let call_path = type_alias_decl.call_path.to_import_path(&namespace); - // Some(call_path) - // } - _ => None, // TODO: other types + _ => return None, + } + }); + + let actions = call_paths + .filter_map(|call_path| { + let text_edit = TextEdit { + range: Range::default(), // TODO: sort within the imports + new_text: format!("use {};\n", call_path), }; - // _ => None, - // } - } - None - }) - .collect(); + let changes = HashMap::from([(ctx.uri.clone(), vec![text_edit])]); - let text_edit = TextEdit { - range: Range::default(), // TODO: sort within the imports - new_text: format!("use {};\n", call_paths[0]), // TODO: multiple imports - }; - let changes = HashMap::from([(ctx.uri.clone(), vec![text_edit])]); + return Some(CodeActionOrCommand::CodeAction(LspCodeAction { + title: format!("{} `{}`", CODE_ACTION_IMPORT_TITLE, call_path), + kind: Some(CodeActionKind::QUICKFIX), + edit: Some(WorkspaceEdit { + changes: Some(changes), + ..Default::default() + }), + data: Some(Value::String(ctx.uri.to_string())), + ..Default::default() + })); + }) + .collect::>(); - CodeActionOrCommand::CodeAction(LspCodeAction { - title: format!("{} {}", CODE_ACTION_IMPORT_TITLE, call_paths[0]), - kind: Some(CodeActionKind::QUICKFIX), - edit: Some(WorkspaceEdit { - changes: Some(changes), - ..Default::default() - }), - data: Some(Value::String(ctx.uri.to_string())), - ..Default::default() - }) + if !actions.is_empty() { + return Some(actions); + } + } + None } diff --git a/sway-lsp/src/capabilities/code_actions/mod.rs b/sway-lsp/src/capabilities/code_actions/mod.rs index 6221c58d46b..74566e80850 100644 --- a/sway-lsp/src/capabilities/code_actions/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/mod.rs @@ -46,8 +46,6 @@ pub fn code_actions( temp_uri: &Url, diagnostics: &Vec, ) -> Option { - eprintln!("got here 1"); - let engines = session.engines.read(); let (_, token) = session .token_map() @@ -61,8 +59,6 @@ pub fn code_actions( diagnostics, }; - eprintln!("got here 2"); - let actions_by_type = token .typed .as_ref() diff --git a/sway-lsp/src/capabilities/diagnostic.rs b/sway-lsp/src/capabilities/diagnostic.rs index 11bfd41d1eb..c82d30c324a 100644 --- a/sway-lsp/src/capabilities/diagnostic.rs +++ b/sway-lsp/src/capabilities/diagnostic.rs @@ -118,6 +118,9 @@ impl TryFrom for DiagnosticData { CompileError::SymbolNotFound { name, .. } => Ok(DiagnosticData { name_to_import: name.to_string(), }), + CompileError::UnknownVariable { var_name, .. } => Ok(DiagnosticData { + name_to_import: var_name.to_string(), + }), _ => anyhow::bail!("Not implemented"), } } diff --git a/sway-lsp/src/core/session.rs b/sway-lsp/src/core/session.rs index f66313d685c..6eebbcd5923 100644 --- a/sway-lsp/src/core/session.rs +++ b/sway-lsp/src/core/session.rs @@ -38,7 +38,8 @@ use sway_core::{ language::{ lexed::LexedProgram, parsed::{AstNode, ParseProgram}, - ty::{self, TyProgram}, + ty::{self}, + HasSubmodules, }, BuildTarget, Engines, Namespace, Programs, }; @@ -443,7 +444,7 @@ pub fn compile( pub struct TraversalResult { pub diagnostics: (Vec, Vec), - pub programs: Option<(LexedProgram, ParseProgram, TyProgram)>, + pub programs: Option<(LexedProgram, ParseProgram, ty::TyProgram)>, pub token_map: TokenMap, } @@ -545,8 +546,7 @@ fn parse_ast_to_tokens( let root_nodes = parse_program.root.tree.root_nodes.iter(); let sub_nodes = parse_program .root - .submodules - .iter() + .submodules_recursive() .flat_map(|(_, submodule)| &submodule.module.tree.root_nodes); root_nodes @@ -564,8 +564,7 @@ fn parse_ast_to_typed_tokens( let root_nodes = typed_program.root.all_nodes.iter(); let sub_nodes = typed_program .root - .submodules - .iter() + .submodules_recursive() .flat_map(|(_, submodule)| submodule.module.all_nodes.iter()); root_nodes.chain(sub_nodes).for_each(|n| f(n, ctx)); diff --git a/sway-lsp/src/traverse/dependency.rs b/sway-lsp/src/traverse/dependency.rs index 8d7db4bf671..b111506431c 100644 --- a/sway-lsp/src/traverse/dependency.rs +++ b/sway-lsp/src/traverse/dependency.rs @@ -4,7 +4,7 @@ use crate::{ }; use sway_core::language::{ parsed::{AstNode, AstNodeContent, Declaration}, - ty, + ty::{self, TyAstNodeContent}, }; /// Insert Declaration tokens into the TokenMap. @@ -43,6 +43,7 @@ pub fn collect_typed_declaration(node: &ty::TyAstNode, ctx: &ParseContext) { | ty::TyDecl::ConstantDecl(ty::ConstantDecl { name, .. }) => name.clone(), _ => return, }; + let token_ident = ctx.ident(&ident); if let Some(mut token) = ctx.tokens.try_get_mut(&token_ident).try_unwrap() { token.typed = Some(typed_token); diff --git a/sway-lsp/src/traverse/lexed_tree.rs b/sway-lsp/src/traverse/lexed_tree.rs index 0334c7c154c..43a2ff9fc78 100644 --- a/sway-lsp/src/traverse/lexed_tree.rs +++ b/sway-lsp/src/traverse/lexed_tree.rs @@ -11,7 +11,7 @@ use sway_ast::{ MatchBranchKind, ModuleKind, Pattern, PatternStructField, Statement, StatementLet, StorageField, TraitType, Ty, TypeField, UseTree, }; -use sway_core::language::lexed::LexedProgram; +use sway_core::language::{lexed::LexedProgram, HasSubmodules}; use sway_types::{Ident, Span, Spanned}; pub fn parse(lexed_program: &LexedProgram, ctx: &ParseContext) { @@ -25,8 +25,7 @@ pub fn parse(lexed_program: &LexedProgram, ctx: &ParseContext) { lexed_program .root - .submodules - .par_iter() + .submodules_recursive() .for_each(|(_, dep)| { insert_module_kind(ctx, &dep.module.tree.kind); dep.module diff --git a/sway-lsp/src/traverse/parsed_tree.rs b/sway-lsp/src/traverse/parsed_tree.rs index d0b47e29afa..a7f490c95e5 100644 --- a/sway-lsp/src/traverse/parsed_tree.rs +++ b/sway-lsp/src/traverse/parsed_tree.rs @@ -26,7 +26,7 @@ use sway_core::{ TraitItem, TraitTypeDeclaration, TupleIndexExpression, TypeAliasDeclaration, UseStatement, VariableDeclaration, WhileLoopExpression, }, - CallPathTree, Literal, + CallPathTree, HasSubmodules, Literal, }, transform::{AttributeKind, AttributesMap}, type_system::{TypeArgument, TypeParameter}, @@ -69,7 +69,7 @@ impl<'a> ParsedTree<'a> { mod_name_span, .. }, - ) in &parse_module.submodules + ) in parse_module.submodules_recursive() { self.ctx.tokens.insert( self.ctx.ident(&Ident::new(mod_name_span.clone())), diff --git a/sway-lsp/src/traverse/typed_tree.rs b/sway-lsp/src/traverse/typed_tree.rs index 8ac11d72f9d..a5ddb52bbc6 100644 --- a/sway-lsp/src/traverse/typed_tree.rs +++ b/sway-lsp/src/traverse/typed_tree.rs @@ -44,7 +44,7 @@ impl<'a> TypedTree<'a> { module, mod_name_span, }, - ) in &typed_module.submodules + ) in typed_module.submodules_recursive() { if let Some(mut token) = self .ctx diff --git a/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod/deeper_mod.sw b/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod/deeper_mod.sw index 081e10146bf..08861912ad8 100644 --- a/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod/deeper_mod.sw +++ b/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod/deeper_mod.sw @@ -2,6 +2,8 @@ library; pub fn deep_fun(){} +pub const DEEPER_CONST: u32 = 0; + pub enum DeepEnum { Variant: (), Number: u32, diff --git a/sway-lsp/tests/fixtures/tokens/paths/src/main.sw b/sway-lsp/tests/fixtures/tokens/paths/src/main.sw index d2170afee55..9b20ac52a56 100644 --- a/sway-lsp/tests/fixtures/tokens/paths/src/main.sw +++ b/sway-lsp/tests/fixtures/tokens/paths/src/main.sw @@ -3,33 +3,38 @@ contract; mod test_mod; mod deep_mod; -use test_mod::A; -use deep_mod::deeper_mod::deep_fun as dfun; -use std::constants::{self, ZERO_B256}; +// use test_mod::A; +// use deep_mod::deeper_mod::deep_fun as dfun; +// use std::constants::{self, ZERO_B256}; + + pub fn fun() { - let _ = std::option::Option::None; - let _ = Option::None; - let _ = std::vm::evm::evm_address::EvmAddress { - value: b256::min(), - }; - - test_mod::test_fun(); - deep_mod::deeper_mod::deep_fun(); - std::assert::assert(true); - let _ = core::primitives::u64::min(); - - A::fun(); - test_mod::A::fun(); - - let _ = std::constants::ZERO_B256; - let _ = core::primitives::b256::min(); - - let _ = ::deep_mod::deeper_mod::DeepEnum::Variant; - let _ = deep_mod::deeper_mod::DeepEnum::Variant; - let _ = ::deep_mod::deeper_mod::DeepEnum::Number(0); - let _ = deep_mod::deeper_mod::DeepEnum::Number(0); - - let _ = ::deep_mod::deeper_mod::DeepStruct:: { field: 0 }; - let _ = deep_mod::deeper_mod::DeepStruct:: { field: 0 }; + // TODO: Get constants working + let i = DEEPER_CONST; + // deep_fun(); + // let _ = std::option::Option::None; + // let _ = Option::None; + // let _ = std::vm::evm::evm_address::EvmAddress { + // value: b256::min(), + // }; + + // test_mod::test_fun(); + // deep_mod::deeper_mod::deep_fun(); + // std::assert::assert(true); + // let _ = core::primitives::u64::min(); + + // A::fun(); + // test_mod::A::fun(); + + // let _ = std::constants::ZERO_B256; + // let _ = core::primitives::b256::min(); + + // let _ = ::deep_mod::deeper_mod::DeepEnum::Variant; + // let _ = deep_mod::deeper_mod::DeepEnum::Variant; + // let _ = ::deep_mod::deeper_mod::DeepEnum::Number(0); + // let _ = deep_mod::deeper_mod::DeepEnum::Number(0); + + // let _ = ::deep_mod::deeper_mod::DeepStruct:: { field: 0 }; + // let _ = deep_mod::deeper_mod::DeepStruct:: { field: 0 }; } From 58dd12dc6088df602cce30ef1d9cf766cc14b2b9 Mon Sep 17 00:00:00 2001 From: Sophie Date: Mon, 2 Oct 2023 17:35:29 +0100 Subject: [PATCH 03/25] fix consts --- .../code_actions/diagnostic/mod.rs | 16 ++++++---------- sway-lsp/src/traverse/typed_tree.rs | 18 +++++++++--------- .../fixtures/tokens/paths/src/deep_mod.sw | 2 ++ 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs index 0751c0b49d0..b298280779a 100644 --- a/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs @@ -5,7 +5,7 @@ use crate::{ code_actions::{diagnostic, CodeAction, CodeActionContext}, diagnostic::DiagnosticData, }, - core::token::{TypeDefinition, TypedAstToken}, + core::token::{AstToken, TypeDefinition, TypedAstToken}, }; use lsp_types::{ CodeAction as LspCodeAction, CodeActionKind, CodeActionOrCommand, Range, TextEdit, @@ -14,6 +14,7 @@ use lsp_types::{ use serde_json::Value; use sway_core::{ language::{ + parsed::Declaration, ty::{self, ConstantDecl, TyDecl}, CallPath, }, @@ -74,16 +75,11 @@ fn import_code_action( // let call_path = trait_decl.call_path.to_import_path(&namespace); // Some(call_path) // } - // TyDecl::FunctionDecl(decl) => { - // let function_decl = ctx.engines.de().get_function(&decl.decl_id); - // eprintln!("fn decl call_path: {:?}", function_decl.call_path); - // let call_path = function_decl.call_path.to_import_path(&namespace); - // eprintln!("fn call_path: {:?}", call_path); - // Some(call_path) - // } // TyDecl::TypeAliasDecl(decl) => { - // let type_alias_decl = ctx.engines.de().get_type_alias(&decl.decl_id); - // let call_path = type_alias_decl.call_path.to_import_path(&namespace); + // let type_alias_decl = + // ctx.engines.de().get_type_alias(&decl.decl_id); + // let call_path = + // type_alias_decl.call_path.to_import_path(&namespace); // Some(call_path) // } _ => None, // TODO: other types diff --git a/sway-lsp/src/traverse/typed_tree.rs b/sway-lsp/src/traverse/typed_tree.rs index a5ddb52bbc6..32e125fbd46 100644 --- a/sway-lsp/src/traverse/typed_tree.rs +++ b/sway-lsp/src/traverse/typed_tree.rs @@ -16,7 +16,7 @@ use sway_core::{ type_system::TypeArgument, TraitConstraint, TypeId, TypeInfo, }; -use sway_types::{Ident, Span, Spanned}; +use sway_types::{Ident, Named, Span, Spanned}; use sway_utils::iter_prefixes; pub struct TypedTree<'a> { @@ -268,10 +268,10 @@ impl Parse for ty::TyExpression { } ty::TyExpressionVariant::ConstantExpression { ref const_decl, - span, call_path, + .. } => { - collect_const_decl(ctx, const_decl, span); + collect_const_decl(ctx, const_decl); if let Some(call_path) = call_path { collect_call_path_prefixes(ctx, &call_path.prefixes); } @@ -594,7 +594,7 @@ impl Parse for ty::TyVariableDecl { impl Parse for ty::ConstantDecl { fn parse(&self, ctx: &ParseContext) { let const_decl = ctx.engines.de().get_constant(&self.decl_id); - collect_const_decl(ctx, &const_decl, &self.decl_span); + collect_const_decl(ctx, &const_decl); } } @@ -677,7 +677,7 @@ impl Parse for ty::TraitDecl { } ty::TyTraitInterfaceItem::Constant(decl_ref) => { let constant = ctx.engines.de().get_constant(decl_ref); - collect_const_decl(ctx, &constant, &decl_ref.span()); + collect_const_decl(ctx, &constant); } ty::TyTraitInterfaceItem::Type(decl_ref) => { let trait_type = ctx.engines.de().get_type(decl_ref); @@ -785,7 +785,7 @@ impl Parse for ty::ImplTrait { } ty::TyTraitItem::Constant(const_ref) => { let constant = ctx.engines.de().get_constant(const_ref); - collect_const_decl(ctx, &constant, &const_ref.span()); + collect_const_decl(ctx, &constant); } ty::TyTraitItem::Type(type_ref) => { let trait_type = ctx.engines.de().get_type(type_ref); @@ -834,7 +834,7 @@ impl Parse for ty::AbiDecl { } ty::TyTraitInterfaceItem::Constant(const_ref) => { let constant = ctx.engines.de().get_constant(const_ref); - collect_const_decl(ctx, &constant, &const_ref.span()); + collect_const_decl(ctx, &constant); } ty::TyTraitInterfaceItem::Type(type_ref) => { let trait_type = ctx.engines.de().get_type(type_ref); @@ -1195,10 +1195,10 @@ fn collect_call_path_prefixes(ctx: &ParseContext, prefixes: &[Ident]) { } } -fn collect_const_decl(ctx: &ParseContext, const_decl: &ty::TyConstantDecl, span: &Span) { +fn collect_const_decl(ctx: &ParseContext, const_decl: &ty::TyConstantDecl) { if let Some(mut token) = ctx .tokens - .try_get_mut(&ctx.ident(&Ident::new(span.clone()))) + .try_get_mut(&ctx.ident(const_decl.name())) .try_unwrap() { token.typed = Some(TypedAstToken::TypedConstantDeclaration(const_decl.clone())); diff --git a/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod.sw b/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod.sw index 874041a52a2..5946ca3a1e4 100644 --- a/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod.sw +++ b/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod.sw @@ -1,3 +1,5 @@ library; mod deeper_mod; + +pub const DEEPER_CONST: u32 = 0; From 3a27ad6e514c5acd701a8a8fd0b837f280fd16e3 Mon Sep 17 00:00:00 2001 From: Sophie Date: Mon, 2 Oct 2023 18:04:25 +0100 Subject: [PATCH 04/25] traits --- sway-core/src/language/call_path.rs | 2 - .../src/language/ty/declaration/function.rs | 1 - .../src/language/ty/declaration/trait.rs | 4 +- .../src/language/ty/declaration/type_alias.rs | 9 ++- .../ast_node/declaration/declaration.rs | 2 + .../ast_node/declaration/trait.rs | 3 +- .../code_actions/diagnostic/mod.rs | 55 +++++------------ .../fixtures/tokens/paths/src/deep_mod.sw | 2 - .../tokens/paths/src/deep_mod/deeper_mod.sw | 2 - .../tests/fixtures/tokens/paths/src/main.sw | 59 +++++++++---------- 10 files changed, 58 insertions(+), 81 deletions(-) diff --git a/sway-core/src/language/call_path.rs b/sway-core/src/language/call_path.rs index d2936c5d6a2..43cf8d1fb20 100644 --- a/sway-core/src/language/call_path.rs +++ b/sway-core/src/language/call_path.rs @@ -4,8 +4,6 @@ use crate::{Ident, Namespace}; use sway_types::{span::Span, Spanned}; -use super::ty::TyDecl; - #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct CallPathTree { pub call_path: CallPath, diff --git a/sway-core/src/language/ty/declaration/function.rs b/sway-core/src/language/ty/declaration/function.rs index e94b8e27eff..b88019aa1bc 100644 --- a/sway-core/src/language/ty/declaration/function.rs +++ b/sway-core/src/language/ty/declaration/function.rs @@ -4,7 +4,6 @@ use std::{ hash::{Hash, Hasher}, }; -use etk_ops::london::Call; use sha2::{Digest, Sha256}; use sway_error::handler::{ErrorEmitted, Handler}; diff --git a/sway-core/src/language/ty/declaration/trait.rs b/sway-core/src/language/ty/declaration/trait.rs index 974ce59262e..37509478c4d 100644 --- a/sway-core/src/language/ty/declaration/trait.rs +++ b/sway-core/src/language/ty/declaration/trait.rs @@ -9,7 +9,7 @@ use crate::{ ReplaceFunctionImplementingType, }, engine_threading::*, - language::{parsed, Visibility}, + language::{parsed, CallPath, Visibility}, semantic_analysis::type_check_context::MonomorphizeHelper, semantic_analysis::{TypeCheckFinalization, TypeCheckFinalizationContext}, transform, @@ -27,6 +27,7 @@ pub struct TyTraitDecl { pub supertraits: Vec, pub visibility: Visibility, pub attributes: transform::AttributesMap, + pub call_path: CallPath, pub span: Span, } @@ -81,6 +82,7 @@ impl HashWithEngines for TyTraitDecl { // reliable source of obj v. obj distinction attributes: _, span: _, + call_path: _, } = self; name.hash(state); type_parameters.hash(state, engines); diff --git a/sway-core/src/language/ty/declaration/type_alias.rs b/sway-core/src/language/ty/declaration/type_alias.rs index 2c1829fcfc8..3554c563f24 100644 --- a/sway-core/src/language/ty/declaration/type_alias.rs +++ b/sway-core/src/language/ty/declaration/type_alias.rs @@ -2,11 +2,17 @@ use std::hash::{Hash, Hasher}; use sway_types::{Ident, Named, Span, Spanned}; -use crate::{engine_threading::*, language::Visibility, transform, type_system::*}; +use crate::{ + engine_threading::*, + language::{CallPath, Visibility}, + transform, + type_system::*, +}; #[derive(Clone, Debug)] pub struct TyTypeAliasDecl { pub name: Ident, + pub call_path: CallPath, pub attributes: transform::AttributesMap, pub ty: TypeArgument, pub visibility: Visibility, @@ -36,6 +42,7 @@ impl HashWithEngines for TyTypeAliasDecl { visibility, // these fields are not hashed because they aren't relevant/a // reliable source of obj v. obj distinction + call_path: _, span: _, attributes: _, } = self; diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/declaration.rs b/sway-core/src/semantic_analysis/ast_node/declaration/declaration.rs index 159c8520561..1e8afccbe0e 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/declaration.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/declaration.rs @@ -6,6 +6,7 @@ use crate::{ language::{ parsed, ty::{self, TyDecl}, + CallPath, }, namespace::{IsExtendingExistingImpl, IsImplSelf}, semantic_analysis::{ @@ -358,6 +359,7 @@ impl TyDecl { // create the type alias decl using the resolved type above let decl = ty::TyTypeAliasDecl { name: name.clone(), + call_path: CallPath::from(name.clone()).to_fullpath(ctx.namespace), attributes: decl.attributes, ty: TypeArgument { initial_type_id: ty.initial_type_id, diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs b/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs index 8dce60824da..8db453086e8 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs @@ -202,13 +202,14 @@ impl TyTraitDecl { } let typed_trait_decl = ty::TyTraitDecl { - name, + name: name.clone(), type_parameters: new_type_parameters, interface_surface: new_interface_surface, items: new_items, supertraits, visibility, attributes, + call_path: CallPath::from(name.clone()).to_fullpath(ctx.namespace), span, }; Ok(typed_trait_decl) diff --git a/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs index b298280779a..e727a3654d1 100644 --- a/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs @@ -1,26 +1,15 @@ -use std::{collections::HashMap, ops::Deref}; +use std::collections::HashMap; use crate::{ - capabilities::{ - code_actions::{diagnostic, CodeAction, CodeActionContext}, - diagnostic::DiagnosticData, - }, - core::token::{AstToken, TypeDefinition, TypedAstToken}, + capabilities::{code_actions::CodeActionContext, diagnostic::DiagnosticData}, + core::token::TypedAstToken, }; use lsp_types::{ CodeAction as LspCodeAction, CodeActionKind, CodeActionOrCommand, Range, TextEdit, WorkspaceEdit, }; use serde_json::Value; -use sway_core::{ - language::{ - parsed::Declaration, - ty::{self, ConstantDecl, TyDecl}, - CallPath, - }, - namespace, Namespace, -}; -use sway_types::Span; +use sway_core::{language::ty::TyDecl, Namespace}; use super::CODE_ACTION_IMPORT_TITLE; @@ -48,9 +37,6 @@ fn import_code_action( .tokens .tokens_for_name(&diag_data.name_to_import) .filter_map(|(ident, token)| { - eprintln!("ident: {:?}, ", ident); - eprintln!("token: {:?}", token); - // If the typed token is a declaration, then we can import it. match token.typed.as_ref() { Some(TypedAstToken::TypedDeclaration(ty_decl)) => { @@ -65,24 +51,12 @@ fn import_code_action( let call_path = enum_decl.call_path.to_import_path(&namespace); Some(call_path) } - // TyDecl::ConstantDecl(decl) => { - // let constant_decl = ctx.engines.de().get_constant(&decl.decl_id); - // let call_path = constant_decl.call_path.to_import_path(&namespace); - // Some(call_path) - // } - // TyDecl::TraitDecl(decl) => { - // let trait_decl = ctx.engines.de().get_trait(&decl.decl_id); - // let call_path = trait_decl.call_path.to_import_path(&namespace); - // Some(call_path) - // } - // TyDecl::TypeAliasDecl(decl) => { - // let type_alias_decl = - // ctx.engines.de().get_type_alias(&decl.decl_id); - // let call_path = - // type_alias_decl.call_path.to_import_path(&namespace); - // Some(call_path) - // } - _ => None, // TODO: other types + TyDecl::TraitDecl(decl) => { + let trait_decl = ctx.engines.de().get_trait(&decl.decl_id); + let call_path = trait_decl.call_path.to_import_path(&namespace); + Some(call_path) + } + _ => None, }; } Some(TypedAstToken::TypedFunctionDeclaration(ty_decl)) => { @@ -91,7 +65,10 @@ fn import_code_action( } Some(TypedAstToken::TypedConstantDeclaration(ty_decl)) => { let call_path = ty_decl.call_path.to_import_path(&namespace); - eprintln!("constant call_path: {:?}", call_path); + Some(call_path) + } + Some(TypedAstToken::TypedTypeAliasDeclaration(ty_decl)) => { + let call_path = ty_decl.call_path.to_import_path(&namespace); Some(call_path) } _ => return None, @@ -106,7 +83,7 @@ fn import_code_action( }; let changes = HashMap::from([(ctx.uri.clone(), vec![text_edit])]); - return Some(CodeActionOrCommand::CodeAction(LspCodeAction { + Some(CodeActionOrCommand::CodeAction(LspCodeAction { title: format!("{} `{}`", CODE_ACTION_IMPORT_TITLE, call_path), kind: Some(CodeActionKind::QUICKFIX), edit: Some(WorkspaceEdit { @@ -115,7 +92,7 @@ fn import_code_action( }), data: Some(Value::String(ctx.uri.to_string())), ..Default::default() - })); + })) }) .collect::>(); diff --git a/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod.sw b/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod.sw index 5946ca3a1e4..874041a52a2 100644 --- a/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod.sw +++ b/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod.sw @@ -1,5 +1,3 @@ library; mod deeper_mod; - -pub const DEEPER_CONST: u32 = 0; diff --git a/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod/deeper_mod.sw b/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod/deeper_mod.sw index 08861912ad8..081e10146bf 100644 --- a/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod/deeper_mod.sw +++ b/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod/deeper_mod.sw @@ -2,8 +2,6 @@ library; pub fn deep_fun(){} -pub const DEEPER_CONST: u32 = 0; - pub enum DeepEnum { Variant: (), Number: u32, diff --git a/sway-lsp/tests/fixtures/tokens/paths/src/main.sw b/sway-lsp/tests/fixtures/tokens/paths/src/main.sw index 9b20ac52a56..d2170afee55 100644 --- a/sway-lsp/tests/fixtures/tokens/paths/src/main.sw +++ b/sway-lsp/tests/fixtures/tokens/paths/src/main.sw @@ -3,38 +3,33 @@ contract; mod test_mod; mod deep_mod; -// use test_mod::A; -// use deep_mod::deeper_mod::deep_fun as dfun; -// use std::constants::{self, ZERO_B256}; - - +use test_mod::A; +use deep_mod::deeper_mod::deep_fun as dfun; +use std::constants::{self, ZERO_B256}; pub fn fun() { - // TODO: Get constants working - let i = DEEPER_CONST; - // deep_fun(); - // let _ = std::option::Option::None; - // let _ = Option::None; - // let _ = std::vm::evm::evm_address::EvmAddress { - // value: b256::min(), - // }; - - // test_mod::test_fun(); - // deep_mod::deeper_mod::deep_fun(); - // std::assert::assert(true); - // let _ = core::primitives::u64::min(); - - // A::fun(); - // test_mod::A::fun(); - - // let _ = std::constants::ZERO_B256; - // let _ = core::primitives::b256::min(); - - // let _ = ::deep_mod::deeper_mod::DeepEnum::Variant; - // let _ = deep_mod::deeper_mod::DeepEnum::Variant; - // let _ = ::deep_mod::deeper_mod::DeepEnum::Number(0); - // let _ = deep_mod::deeper_mod::DeepEnum::Number(0); - - // let _ = ::deep_mod::deeper_mod::DeepStruct:: { field: 0 }; - // let _ = deep_mod::deeper_mod::DeepStruct:: { field: 0 }; + let _ = std::option::Option::None; + let _ = Option::None; + let _ = std::vm::evm::evm_address::EvmAddress { + value: b256::min(), + }; + + test_mod::test_fun(); + deep_mod::deeper_mod::deep_fun(); + std::assert::assert(true); + let _ = core::primitives::u64::min(); + + A::fun(); + test_mod::A::fun(); + + let _ = std::constants::ZERO_B256; + let _ = core::primitives::b256::min(); + + let _ = ::deep_mod::deeper_mod::DeepEnum::Variant; + let _ = deep_mod::deeper_mod::DeepEnum::Variant; + let _ = ::deep_mod::deeper_mod::DeepEnum::Number(0); + let _ = deep_mod::deeper_mod::DeepEnum::Number(0); + + let _ = ::deep_mod::deeper_mod::DeepStruct:: { field: 0 }; + let _ = deep_mod::deeper_mod::DeepStruct:: { field: 0 }; } From 09757d7a348790c2eb5625689ff4fefa47a1b53d Mon Sep 17 00:00:00 2001 From: Sophie Date: Mon, 2 Oct 2023 22:50:27 +0100 Subject: [PATCH 05/25] fix typo --- sway-core/src/language/parsed/use_statement.rs | 2 +- sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs | 5 ++++- sway-lsp/src/capabilities/code_actions/mod.rs | 2 ++ sway-lsp/src/capabilities/document_symbol.rs | 2 +- sway-lsp/src/capabilities/semantic_tokens.rs | 2 +- sway-lsp/src/core/token.rs | 2 +- sway-lsp/src/traverse/lexed_tree.rs | 8 ++++++++ sway-lsp/src/traverse/parsed_tree.rs | 2 +- 8 files changed, 19 insertions(+), 6 deletions(-) diff --git a/sway-core/src/language/parsed/use_statement.rs b/sway-core/src/language/parsed/use_statement.rs index c5e0c1db475..4b004b238c6 100644 --- a/sway-core/src/language/parsed/use_statement.rs +++ b/sway-core/src/language/parsed/use_statement.rs @@ -1,5 +1,5 @@ use crate::parsed::Span; -use sway_types::ident::Ident; +use sway_types::{ident::Ident, Spanned}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ImportType { diff --git a/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs index e727a3654d1..0077957dd95 100644 --- a/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs @@ -2,7 +2,10 @@ use std::collections::HashMap; use crate::{ capabilities::{code_actions::CodeActionContext, diagnostic::DiagnosticData}, - core::token::TypedAstToken, + core::{ + session, + token::{AstToken, TypedAstToken}, + }, }; use lsp_types::{ CodeAction as LspCodeAction, CodeActionKind, CodeActionOrCommand, Range, TextEdit, diff --git a/sway-lsp/src/capabilities/code_actions/mod.rs b/sway-lsp/src/capabilities/code_actions/mod.rs index 74566e80850..af3e917995c 100644 --- a/sway-lsp/src/capabilities/code_actions/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/mod.rs @@ -36,6 +36,7 @@ pub(crate) struct CodeActionContext<'a> { tokens: &'a TokenMap, token: &'a Token, uri: &'a Url, + temp_uri: &'a Url, diagnostics: &'a Vec, } @@ -56,6 +57,7 @@ pub fn code_actions( tokens: session.token_map(), token: &token, uri, + temp_uri, diagnostics, }; diff --git a/sway-lsp/src/capabilities/document_symbol.rs b/sway-lsp/src/capabilities/document_symbol.rs index f142b929cc5..99ae28a5f20 100644 --- a/sway-lsp/src/capabilities/document_symbol.rs +++ b/sway-lsp/src/capabilities/document_symbol.rs @@ -37,7 +37,7 @@ pub(crate) fn symbol_kind(symbol_kind: &SymbolKind) -> lsp_types::SymbolKind { | SymbolKind::ByteLiteral | SymbolKind::Variable | SymbolKind::TypeAlias - | SymbolKind::TraiType + | SymbolKind::TraitType | SymbolKind::Keyword | SymbolKind::SelfKeyword | SymbolKind::SelfTypeKeyword diff --git a/sway-lsp/src/capabilities/semantic_tokens.rs b/sway-lsp/src/capabilities/semantic_tokens.rs index 9e990f00747..bc47c0f0241 100644 --- a/sway-lsp/src/capabilities/semantic_tokens.rs +++ b/sway-lsp/src/capabilities/semantic_tokens.rs @@ -156,7 +156,7 @@ fn semantic_token_type(kind: &SymbolKind) -> SemanticTokenType { SymbolKind::ByteLiteral | SymbolKind::NumericLiteral => SemanticTokenType::NUMBER, SymbolKind::BoolLiteral => SemanticTokenType::new("boolean"), SymbolKind::TypeAlias => SemanticTokenType::new("typeAlias"), - SymbolKind::TraiType => SemanticTokenType::new("traitType"), + SymbolKind::TraitType => SemanticTokenType::new("traitType"), SymbolKind::Keyword => SemanticTokenType::new("keyword"), SymbolKind::Unknown => SemanticTokenType::new("generic"), SymbolKind::BuiltinType => SemanticTokenType::new("builtinType"), diff --git a/sway-lsp/src/core/token.rs b/sway-lsp/src/core/token.rs index b01d3275967..dc7ba0ac4a6 100644 --- a/sway-lsp/src/core/token.rs +++ b/sway-lsp/src/core/token.rs @@ -120,7 +120,7 @@ pub enum SymbolKind { /// Emitted for traits. Trait, /// Emitted for associated types. - TraiType, + TraitType, /// Emitted for type aliases. TypeAlias, /// Emitted for type parameters. diff --git a/sway-lsp/src/traverse/lexed_tree.rs b/sway-lsp/src/traverse/lexed_tree.rs index 43a2ff9fc78..40b61fe560a 100644 --- a/sway-lsp/src/traverse/lexed_tree.rs +++ b/sway-lsp/src/traverse/lexed_tree.rs @@ -66,6 +66,9 @@ impl Parse for ItemKind { insert_keyword(ctx, submod.mod_token.span()); } ItemKind::Use(item_use) => { + // TODO: store the item use in the token map + // as well as include (submodule) + // use it to find the end of the use statement block item_use.parse(ctx); } ItemKind::Struct(item_struct) => { @@ -254,6 +257,11 @@ impl Parse for ItemUse { insert_keyword(ctx, visibility.span()); } insert_keyword(ctx, self.use_token.span()); + + let ident = Ident::new(span); + let token = Token::from_parsed(AstToken::UseStatement(ident.clone()), SymbolKind::); + ctx.tokens.insert(ctx.ident(&ident), token); + self.tree.parse(ctx); } } diff --git a/sway-lsp/src/traverse/parsed_tree.rs b/sway-lsp/src/traverse/parsed_tree.rs index a7f490c95e5..a643fcdad72 100644 --- a/sway-lsp/src/traverse/parsed_tree.rs +++ b/sway-lsp/src/traverse/parsed_tree.rs @@ -906,7 +906,7 @@ impl Parse for TraitTypeDeclaration { ctx.ident(&self.name), Token::from_parsed( AstToken::Declaration(Declaration::TraitTypeDeclaration(self.clone())), - SymbolKind::TraiType, + SymbolKind::TraitType, ), ); if let Some(ty) = &self.ty_opt { From 30494e582407a61ad5ba34e1d20a1a0ec8dddc4f Mon Sep 17 00:00:00 2001 From: Sophie Date: Thu, 5 Oct 2023 18:59:00 +0100 Subject: [PATCH 06/25] sort imports --- sway-ast/src/item/item_use.rs | 13 ++ .../src/language/parsed/use_statement.rs | 3 +- .../language/ty/side_effect/use_statement.rs | 9 +- .../src/semantic_analysis/ast_node/mod.rs | 1 + .../to_parsed_lang/convert_parse_tree.rs | 11 +- .../code_actions/diagnostic/mod.rs | 200 +++++++++++++++++- sway-lsp/src/traverse/lexed_tree.rs | 5 - sway-lsp/src/traverse/typed_tree.rs | 4 + .../fixtures/tokens/paths/src/deep_mod.sw | 2 +- .../tokens/paths/src/deep_mod/deeper_mod.sw | 2 + .../tests/fixtures/tokens/paths/src/main.sw | 11 +- 11 files changed, 241 insertions(+), 20 deletions(-) diff --git a/sway-ast/src/item/item_use.rs b/sway-ast/src/item/item_use.rs index c780631dc6a..fbd77585887 100644 --- a/sway-ast/src/item/item_use.rs +++ b/sway-ast/src/item/item_use.rs @@ -46,3 +46,16 @@ pub enum UseTree { spans: Box<[Span]>, }, } + +impl Spanned for UseTree { + fn span(&self) -> Span { + match self { + UseTree::Group { imports } => imports.span(), + UseTree::Name { name } => name.span(), + UseTree::Rename { name, alias, .. } => Span::join(name.span(), alias.span()), + UseTree::Glob { star_token } => star_token.span(), + UseTree::Path { prefix, suffix, .. } => Span::join(prefix.span(), suffix.span()), + UseTree::Error { spans } => Span::join_all(spans.to_vec().clone().into_iter()), + } + } +} diff --git a/sway-core/src/language/parsed/use_statement.rs b/sway-core/src/language/parsed/use_statement.rs index 4b004b238c6..9734d4f8bfc 100644 --- a/sway-core/src/language/parsed/use_statement.rs +++ b/sway-core/src/language/parsed/use_statement.rs @@ -1,5 +1,5 @@ use crate::parsed::Span; -use sway_types::{ident::Ident, Spanned}; +use sway_types::ident::Ident; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ImportType { @@ -12,6 +12,7 @@ pub enum ImportType { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct UseStatement { pub call_path: Vec, + pub span: Span, pub import_type: ImportType, // If `is_absolute` is true, then this use statement is an absolute path from // the project root namespace. If not, then it is relative to the current namespace. diff --git a/sway-core/src/language/ty/side_effect/use_statement.rs b/sway-core/src/language/ty/side_effect/use_statement.rs index a47b43b630b..5d9c0cf1ca1 100644 --- a/sway-core/src/language/ty/side_effect/use_statement.rs +++ b/sway-core/src/language/ty/side_effect/use_statement.rs @@ -1,12 +1,19 @@ use crate::language::parsed; -use sway_types::ident::Ident; +use sway_types::{ident::Ident, Span, Spanned}; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct TyUseStatement { pub call_path: Vec, + pub span: Span, pub import_type: parsed::ImportType, // If `is_absolute` is true, then this use statement is an absolute path from // the project root namespace. If not, then it is relative to the current namespace. pub is_absolute: bool, pub alias: Option, } + +impl Spanned for TyUseStatement { + fn span(&self) -> Span { + self.span.clone() + } +} diff --git a/sway-core/src/semantic_analysis/ast_node/mod.rs b/sway-core/src/semantic_analysis/ast_node/mod.rs index aec5da2b558..a91a16fa86d 100644 --- a/sway-core/src/semantic_analysis/ast_node/mod.rs +++ b/sway-core/src/semantic_analysis/ast_node/mod.rs @@ -121,6 +121,7 @@ impl ty::TyAstNode { side_effect: ty::TySideEffectVariant::UseStatement(ty::TyUseStatement { alias: a.alias, call_path: a.call_path, + span: a.span, is_absolute: a.is_absolute, import_type: a.import_type, }), diff --git a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs index 8e446f5746a..415b683ef80 100644 --- a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs +++ b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs @@ -249,11 +249,14 @@ fn item_use_to_use_statements( } let mut ret = Vec::new(); let mut prefix = Vec::new(); + let item_span = item_use.span(); + use_tree_to_use_statements( item_use.tree, item_use.root_import.is_some(), &mut prefix, &mut ret, + item_span, ); debug_assert!(prefix.is_empty()); Ok(ret) @@ -264,11 +267,12 @@ fn use_tree_to_use_statements( is_absolute: bool, path: &mut Vec, ret: &mut Vec, + item_span: Span, ) { match use_tree { UseTree::Group { imports } => { for use_tree in imports.into_inner() { - use_tree_to_use_statements(use_tree, is_absolute, path, ret); + use_tree_to_use_statements(use_tree, is_absolute, path, ret, item_span.clone()); } } UseTree::Name { name } => { @@ -279,6 +283,7 @@ fn use_tree_to_use_statements( }; ret.push(UseStatement { call_path: path.clone(), + span: item_span.clone(), import_type, is_absolute, alias: None, @@ -292,6 +297,7 @@ fn use_tree_to_use_statements( }; ret.push(UseStatement { call_path: path.clone(), + span: item_span.clone(), import_type, is_absolute, alias: Some(alias), @@ -300,6 +306,7 @@ fn use_tree_to_use_statements( UseTree::Glob { .. } => { ret.push(UseStatement { call_path: path.clone(), + span: item_span.clone(), import_type: ImportType::Star, is_absolute, alias: None, @@ -307,7 +314,7 @@ fn use_tree_to_use_statements( } UseTree::Path { prefix, suffix, .. } => { path.push(prefix); - use_tree_to_use_statements(*suffix, is_absolute, path, ret); + use_tree_to_use_statements(*suffix, is_absolute, path, ret, item_span.clone()); path.pop().unwrap(); } UseTree::Error { .. } => { diff --git a/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs index 0077957dd95..fd02e11e2cd 100644 --- a/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs @@ -1,18 +1,26 @@ -use std::collections::HashMap; +use std::{cmp::Ordering, collections::HashMap, iter}; use crate::{ capabilities::{code_actions::CodeActionContext, diagnostic::DiagnosticData}, core::{ session, - token::{AstToken, TypedAstToken}, + token::{get_range_from_span, AstToken, Token, TypedAstToken}, }, }; use lsp_types::{ - CodeAction as LspCodeAction, CodeActionKind, CodeActionOrCommand, Range, TextEdit, + CodeAction as LspCodeAction, CodeActionKind, CodeActionOrCommand, Position, Range, TextEdit, WorkspaceEdit, }; use serde_json::Value; -use sway_core::{language::ty::TyDecl, Namespace}; +use sway_core::{ + fuel_prelude::fuel_vm::call, + language::{ + parsed::{ImportType, TreeType}, + ty::{TyDecl, TyUseStatement}, + }, + Namespace, +}; +use sway_types::{BaseIdent, Ident, Spanned}; use super::CODE_ACTION_IMPORT_TITLE; @@ -78,12 +86,190 @@ fn import_code_action( } }); + // eprintln!("before res1"); + // let res1 = ctx + // .tokens + // .tokens_for_file(ctx.temp_uri) + // .filter_map(|(ident, token)| { + // eprintln!("1 foreach ident: {:?}", ident); + // if let Some(TypedAstToken::TypedUseStatement(use_stmt)) = token.typed { + // eprintln!("use_stmt: {:?}", use_stmt); + // return Some("hi"); + // } + + // None + // }); + + // let (range_line, prefix) = ctx + // let res = ctx + // .tokens + // .tokens_for_file(ctx.temp_uri) + // // .filter_map(|(ident, token)| { + // .reduce(|(acc_ident, acc_token), (ident, token)| { + // // if line > acc.0 { + // // (line, prefix) + // // } else { + // // acc + // // } + + // // eprintln!("2 foreach ident: {:?}", ident); + + // if let Some(TypedAstToken::TypedUseStatement(use_stmt)) = token.typed { + // eprintln!("use_stmt: {:?}", use_stmt); + // // return Some((use_stmt.span.end_pos().line_col().0, "")); + // // todo: sort + // return (ident, token); + // } + + // // if let AstToken::Keyword(_) = token.parsed { + // // if ident.name == "use" { + // // return Some((ident.range.start.line, "")); + // // } else if ["mod", "contract", "script", "library", "predicate"] + // // .contains(&ident.name.as_str()) + // // { + // // return Some((ident.range.end.line + 1, "\n")); + // // } + // // } + + // return (acc_ident, acc_token); + // }); + // .reduce( + // |acc, (line, prefix)| { + // if line > acc.0 { + // (line, prefix) + // } else { + // acc + // } + // }, + // ) + // .unwrap_or((1, "\n")); + let actions = call_paths .filter_map(|call_path| { - let text_edit = TextEdit { - range: Range::default(), // TODO: sort within the imports - new_text: format!("use {};\n", call_path), + let mut use_statements = Vec::::new(); + let mut keywords = Vec::::new(); + + ctx.tokens + .tokens_for_file(ctx.temp_uri) + .for_each(|(_, token)| { + if let Some(TypedAstToken::TypedUseStatement(use_stmt)) = token.typed { + use_statements.push(use_stmt); + } + if let AstToken::Keyword(ident) = token.parsed { + keywords.push(ident); + } + }); + + let text_edit: TextEdit = { + // First, check if this import can be added to an existing use statement. + let group_statement = use_statements.iter().find(|use_stmt| { + call_path + .prefixes + .iter() + .zip(use_stmt.call_path.iter()) + .all(|(prefix, stmt_prefix)| prefix.as_str() == stmt_prefix.as_str()) + }); + + if let Some(statement) = group_statement { + let prefix_string = statement + .call_path + .iter() + .map(|path| path.as_str()) + .collect::>() + .join("::"); + let statement_suffix_string = { + let name = match &statement.import_type { + ImportType::Star => "*".to_string(), + ImportType::SelfImport(_) => "self".to_string(), + ImportType::Item(ident) => ident.to_string(), + }; + match &statement.alias { + Some(alias) => format!("{} as {}", name, alias.to_string()), + None => name, + } + }; + let mut suffixes = [statement_suffix_string, call_path.suffix.to_string()]; + suffixes.sort(); //todo + let suffix_string = suffixes.join(", "); + + TextEdit { + range: get_range_from_span(&statement.span()), // todo: fix + new_text: format!("use {}::{{{}}};\n", prefix_string, suffix_string), + } + } else { + // Find the best position in the file to insert a new use statement. + // First, check if it can be inserted relative to existing use statements. + if !use_statements.is_empty() { + let after_statement = use_statements + .iter() + .reduce(|acc, curr| { + if call_path.span().as_str().cmp(curr.span().as_str()) + == Ordering::Greater + && curr.span().as_str().cmp(acc.span().as_str()) + == Ordering::Greater + { + return curr; + } + return acc; + }) + .unwrap(); + + let after_range = get_range_from_span(&after_statement.span()); + + let range_line = if call_path + .span() + .as_str() + .cmp(after_statement.span().as_str()) + == Ordering::Greater + { + after_range.end.line + 1 + } else { + after_range.start.line + }; + + TextEdit { + range: Range::new( + Position::new(range_line, 0), + Position::new(range_line, 0), + ), + new_text: format!("use {};\n", call_path), + } + } else { + // Otherwise, insert it at the top of the file, after any mod statements. + let range_line = keywords + .iter() + .filter_map(|kw| { + if kw.as_str() == "mod" { + return Some(get_range_from_span(&kw.span()).end.line + 1); + } + None + }) + .max() + .or_else(|| { + keywords.iter().find_map(|kw| { + if ["mod", "contract", "script", "library", "predicate"] + .contains(&kw.as_str()) + { + return Some( + get_range_from_span(&kw.span()).end.line + 1, + ); + } + None + }) + }) + .unwrap_or(1); + + TextEdit { + range: Range::new( + Position::new(range_line, 0), + Position::new(range_line, 0), + ), + new_text: format!("\nuse {};\n", call_path), + } + } + } }; + let changes = HashMap::from([(ctx.uri.clone(), vec![text_edit])]); Some(CodeActionOrCommand::CodeAction(LspCodeAction { diff --git a/sway-lsp/src/traverse/lexed_tree.rs b/sway-lsp/src/traverse/lexed_tree.rs index 40b61fe560a..79913af7a02 100644 --- a/sway-lsp/src/traverse/lexed_tree.rs +++ b/sway-lsp/src/traverse/lexed_tree.rs @@ -257,11 +257,6 @@ impl Parse for ItemUse { insert_keyword(ctx, visibility.span()); } insert_keyword(ctx, self.use_token.span()); - - let ident = Ident::new(span); - let token = Token::from_parsed(AstToken::UseStatement(ident.clone()), SymbolKind::); - ctx.tokens.insert(ctx.ident(&ident), token); - self.tree.parse(ctx); } } diff --git a/sway-lsp/src/traverse/typed_tree.rs b/sway-lsp/src/traverse/typed_tree.rs index 32e125fbd46..050be190217 100644 --- a/sway-lsp/src/traverse/typed_tree.rs +++ b/sway-lsp/src/traverse/typed_tree.rs @@ -100,14 +100,18 @@ impl Parse for ty::TySideEffect { UseStatement( use_statement @ ty::TyUseStatement { call_path, + span: _, import_type, alias, is_absolute: _, }, ) => { for (mod_path, ident) in iter_prefixes(call_path).zip(call_path) { + eprintln!("mod_path: {:?}, ident: {:?}", mod_path, ident); if let Some(mut token) = ctx.tokens.try_get_mut(&ctx.ident(ident)).try_unwrap() { + eprintln!("key: {:?}", ctx.ident(ident)); + eprintln!("inserting use stmt: {:?}", use_statement); token.typed = Some(TypedAstToken::TypedUseStatement(use_statement.clone())); if let Some(span) = ctx diff --git a/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod.sw b/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod.sw index 874041a52a2..ea7106afe89 100644 --- a/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod.sw +++ b/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod.sw @@ -1,3 +1,3 @@ library; -mod deeper_mod; +pub mod deeper_mod; diff --git a/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod/deeper_mod.sw b/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod/deeper_mod.sw index 081e10146bf..cf104646e90 100644 --- a/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod/deeper_mod.sw +++ b/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod/deeper_mod.sw @@ -2,6 +2,8 @@ library; pub fn deep_fun(){} +pub const A: u32 = 0; + pub enum DeepEnum { Variant: (), Number: u32, diff --git a/sway-lsp/tests/fixtures/tokens/paths/src/main.sw b/sway-lsp/tests/fixtures/tokens/paths/src/main.sw index d2170afee55..f3e1c4b0325 100644 --- a/sway-lsp/tests/fixtures/tokens/paths/src/main.sw +++ b/sway-lsp/tests/fixtures/tokens/paths/src/main.sw @@ -3,8 +3,9 @@ contract; mod test_mod; mod deep_mod; -use test_mod::A; -use deep_mod::deeper_mod::deep_fun as dfun; +// use test_mod::A; +// use deep_mod::deeper_mod::deep_fun as dfun; +use deep_mod::deeper_mod::A; use std::constants::{self, ZERO_B256}; pub fn fun() { @@ -14,7 +15,11 @@ pub fn fun() { value: b256::min(), }; - test_mod::test_fun(); + deep_fun(); + + let a = A; + + test_mod::test_mod::test_fun(); deep_mod::deeper_mod::deep_fun(); std::assert::assert(true); let _ = core::primitives::u64::min(); From b4f62e7af54cb4a170beab0b872f06317bb8640a Mon Sep 17 00:00:00 2001 From: Sophie Date: Thu, 5 Oct 2023 19:24:33 +0100 Subject: [PATCH 07/25] cargo fmt --- .../capabilities/code_actions/diagnostic/mod.rs | 14 +++++--------- sway-lsp/src/capabilities/diagnostic.rs | 1 - sway-lsp/src/traverse/dependency.rs | 2 +- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs index fd02e11e2cd..55a5d9088ca 100644 --- a/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs @@ -1,11 +1,8 @@ -use std::{cmp::Ordering, collections::HashMap, iter}; +use std::{cmp::Ordering, collections::HashMap}; use crate::{ capabilities::{code_actions::CodeActionContext, diagnostic::DiagnosticData}, - core::{ - session, - token::{get_range_from_span, AstToken, Token, TypedAstToken}, - }, + core::token::{get_range_from_span, AstToken, TypedAstToken}, }; use lsp_types::{ CodeAction as LspCodeAction, CodeActionKind, CodeActionOrCommand, Position, Range, TextEdit, @@ -13,14 +10,13 @@ use lsp_types::{ }; use serde_json::Value; use sway_core::{ - fuel_prelude::fuel_vm::call, language::{ - parsed::{ImportType, TreeType}, + parsed::ImportType, ty::{TyDecl, TyUseStatement}, }, Namespace, }; -use sway_types::{BaseIdent, Ident, Spanned}; +use sway_types::{Ident, Spanned}; use super::CODE_ACTION_IMPORT_TITLE; @@ -47,7 +43,7 @@ fn import_code_action( let call_paths = ctx .tokens .tokens_for_name(&diag_data.name_to_import) - .filter_map(|(ident, token)| { + .filter_map(|(_, token)| { // If the typed token is a declaration, then we can import it. match token.typed.as_ref() { Some(TypedAstToken::TypedDeclaration(ty_decl)) => { diff --git a/sway-lsp/src/capabilities/diagnostic.rs b/sway-lsp/src/capabilities/diagnostic.rs index c82d30c324a..a0e5e75c5e6 100644 --- a/sway-lsp/src/capabilities/diagnostic.rs +++ b/sway-lsp/src/capabilities/diagnostic.rs @@ -3,7 +3,6 @@ use std::path::PathBuf; use lsp_types::{Diagnostic, DiagnosticSeverity, DiagnosticTag, Position, Range}; use serde::{Deserialize, Serialize}; -use serde_json::json; use sway_error::warning::CompileWarning; use sway_error::{error::CompileError, warning::Warning}; use sway_types::{LineCol, SourceEngine, Spanned}; diff --git a/sway-lsp/src/traverse/dependency.rs b/sway-lsp/src/traverse/dependency.rs index b111506431c..cde3f2942c6 100644 --- a/sway-lsp/src/traverse/dependency.rs +++ b/sway-lsp/src/traverse/dependency.rs @@ -4,7 +4,7 @@ use crate::{ }; use sway_core::language::{ parsed::{AstNode, AstNodeContent, Declaration}, - ty::{self, TyAstNodeContent}, + ty, }; /// Insert Declaration tokens into the TokenMap. From 340ebf7ceeeebb46c178ff77690237848250915e Mon Sep 17 00:00:00 2001 From: Sophie Date: Mon, 9 Oct 2023 13:08:32 -0700 Subject: [PATCH 08/25] pass span as option --- sway-ast/src/item/item_use.rs | 2 +- sway-lsp/benches/lsp_benchmarks/requests.rs | 4 +- .../code_actions/diagnostic/mod.rs | 58 ------------------- sway-lsp/src/traverse/typed_tree.rs | 24 ++++---- 4 files changed, 14 insertions(+), 74 deletions(-) diff --git a/sway-ast/src/item/item_use.rs b/sway-ast/src/item/item_use.rs index fbd77585887..f826988b51e 100644 --- a/sway-ast/src/item/item_use.rs +++ b/sway-ast/src/item/item_use.rs @@ -55,7 +55,7 @@ impl Spanned for UseTree { UseTree::Rename { name, alias, .. } => Span::join(name.span(), alias.span()), UseTree::Glob { star_token } => star_token.span(), UseTree::Path { prefix, suffix, .. } => Span::join(prefix.span(), suffix.span()), - UseTree::Error { spans } => Span::join_all(spans.to_vec().clone().into_iter()), + UseTree::Error { spans } => Span::join_all(spans.to_vec().clone()), } } } diff --git a/sway-lsp/benches/lsp_benchmarks/requests.rs b/sway-lsp/benches/lsp_benchmarks/requests.rs index 67ad616871a..fe87df01026 100644 --- a/sway-lsp/benches/lsp_benchmarks/requests.rs +++ b/sway-lsp/benches/lsp_benchmarks/requests.rs @@ -75,7 +75,9 @@ fn benchmarks(c: &mut Criterion) { c.bench_function("code_action", |b| { let range = Range::new(Position::new(4, 10), Position::new(4, 10)); - b.iter(|| capabilities::code_actions::code_actions(session.clone(), &range, &uri, &uri)) + b.iter(|| { + capabilities::code_actions::code_actions(session.clone(), &range, &uri, &uri, &vec![]) + }) }); c.bench_function("code_lens", |b| { diff --git a/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs index 55a5d9088ca..7689c7bbad5 100644 --- a/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs @@ -82,64 +82,6 @@ fn import_code_action( } }); - // eprintln!("before res1"); - // let res1 = ctx - // .tokens - // .tokens_for_file(ctx.temp_uri) - // .filter_map(|(ident, token)| { - // eprintln!("1 foreach ident: {:?}", ident); - // if let Some(TypedAstToken::TypedUseStatement(use_stmt)) = token.typed { - // eprintln!("use_stmt: {:?}", use_stmt); - // return Some("hi"); - // } - - // None - // }); - - // let (range_line, prefix) = ctx - // let res = ctx - // .tokens - // .tokens_for_file(ctx.temp_uri) - // // .filter_map(|(ident, token)| { - // .reduce(|(acc_ident, acc_token), (ident, token)| { - // // if line > acc.0 { - // // (line, prefix) - // // } else { - // // acc - // // } - - // // eprintln!("2 foreach ident: {:?}", ident); - - // if let Some(TypedAstToken::TypedUseStatement(use_stmt)) = token.typed { - // eprintln!("use_stmt: {:?}", use_stmt); - // // return Some((use_stmt.span.end_pos().line_col().0, "")); - // // todo: sort - // return (ident, token); - // } - - // // if let AstToken::Keyword(_) = token.parsed { - // // if ident.name == "use" { - // // return Some((ident.range.start.line, "")); - // // } else if ["mod", "contract", "script", "library", "predicate"] - // // .contains(&ident.name.as_str()) - // // { - // // return Some((ident.range.end.line + 1, "\n")); - // // } - // // } - - // return (acc_ident, acc_token); - // }); - // .reduce( - // |acc, (line, prefix)| { - // if line > acc.0 { - // (line, prefix) - // } else { - // acc - // } - // }, - // ) - // .unwrap_or((1, "\n")); - let actions = call_paths .filter_map(|call_path| { let mut use_statements = Vec::::new(); diff --git a/sway-lsp/src/traverse/typed_tree.rs b/sway-lsp/src/traverse/typed_tree.rs index 050be190217..f57b4b00d87 100644 --- a/sway-lsp/src/traverse/typed_tree.rs +++ b/sway-lsp/src/traverse/typed_tree.rs @@ -107,11 +107,8 @@ impl Parse for ty::TySideEffect { }, ) => { for (mod_path, ident) in iter_prefixes(call_path).zip(call_path) { - eprintln!("mod_path: {:?}, ident: {:?}", mod_path, ident); if let Some(mut token) = ctx.tokens.try_get_mut(&ctx.ident(ident)).try_unwrap() { - eprintln!("key: {:?}", ctx.ident(ident)); - eprintln!("inserting use stmt: {:?}", use_statement); token.typed = Some(TypedAstToken::TypedUseStatement(use_statement.clone())); if let Some(span) = ctx @@ -272,10 +269,11 @@ impl Parse for ty::TyExpression { } ty::TyExpressionVariant::ConstantExpression { ref const_decl, + span, call_path, .. } => { - collect_const_decl(ctx, const_decl); + collect_const_decl(ctx, const_decl, &Some(Ident::new(span.clone()))); if let Some(call_path) = call_path { collect_call_path_prefixes(ctx, &call_path.prefixes); } @@ -598,7 +596,7 @@ impl Parse for ty::TyVariableDecl { impl Parse for ty::ConstantDecl { fn parse(&self, ctx: &ParseContext) { let const_decl = ctx.engines.de().get_constant(&self.decl_id); - collect_const_decl(ctx, &const_decl); + collect_const_decl(ctx, &const_decl, None); } } @@ -681,7 +679,7 @@ impl Parse for ty::TraitDecl { } ty::TyTraitInterfaceItem::Constant(decl_ref) => { let constant = ctx.engines.de().get_constant(decl_ref); - collect_const_decl(ctx, &constant); + collect_const_decl(ctx, &constant, None); } ty::TyTraitInterfaceItem::Type(decl_ref) => { let trait_type = ctx.engines.de().get_type(decl_ref); @@ -789,7 +787,7 @@ impl Parse for ty::ImplTrait { } ty::TyTraitItem::Constant(const_ref) => { let constant = ctx.engines.de().get_constant(const_ref); - collect_const_decl(ctx, &constant); + collect_const_decl(ctx, &constant, None); } ty::TyTraitItem::Type(type_ref) => { let trait_type = ctx.engines.de().get_type(type_ref); @@ -838,7 +836,7 @@ impl Parse for ty::AbiDecl { } ty::TyTraitInterfaceItem::Constant(const_ref) => { let constant = ctx.engines.de().get_constant(const_ref); - collect_const_decl(ctx, &constant); + collect_const_decl(ctx, &constant, None); } ty::TyTraitInterfaceItem::Type(type_ref) => { let trait_type = ctx.engines.de().get_type(type_ref); @@ -1199,12 +1197,10 @@ fn collect_call_path_prefixes(ctx: &ParseContext, prefixes: &[Ident]) { } } -fn collect_const_decl(ctx: &ParseContext, const_decl: &ty::TyConstantDecl) { - if let Some(mut token) = ctx - .tokens - .try_get_mut(&ctx.ident(const_decl.name())) - .try_unwrap() - { +fn collect_const_decl(ctx: &ParseContext, const_decl: &ty::TyConstantDecl, ident: &Option) { + let key = ctx.ident(ident.unwrap_or(const_decl.name())); + + if let Some(mut token) = ctx.tokens.try_get_mut(&key).try_unwrap() { token.typed = Some(TypedAstToken::TypedConstantDeclaration(const_decl.clone())); token.type_def = Some(TypeDefinition::Ident(const_decl.call_path.suffix.clone())); } From 1725b71399c55ce31ca1cdfd432d8b975046f24d Mon Sep 17 00:00:00 2001 From: Sophie Date: Mon, 9 Oct 2023 13:14:57 -0700 Subject: [PATCH 09/25] Pass by reference --- sway-lsp/src/traverse/typed_tree.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sway-lsp/src/traverse/typed_tree.rs b/sway-lsp/src/traverse/typed_tree.rs index f57b4b00d87..578de01f346 100644 --- a/sway-lsp/src/traverse/typed_tree.rs +++ b/sway-lsp/src/traverse/typed_tree.rs @@ -273,7 +273,7 @@ impl Parse for ty::TyExpression { call_path, .. } => { - collect_const_decl(ctx, const_decl, &Some(Ident::new(span.clone()))); + collect_const_decl(ctx, const_decl, Some(&Ident::new(span.clone()))); if let Some(call_path) = call_path { collect_call_path_prefixes(ctx, &call_path.prefixes); } @@ -1197,7 +1197,7 @@ fn collect_call_path_prefixes(ctx: &ParseContext, prefixes: &[Ident]) { } } -fn collect_const_decl(ctx: &ParseContext, const_decl: &ty::TyConstantDecl, ident: &Option) { +fn collect_const_decl(ctx: &ParseContext, const_decl: &ty::TyConstantDecl, ident: Option<&Ident>) { let key = ctx.ident(ident.unwrap_or(const_decl.name())); if let Some(mut token) = ctx.tokens.try_get_mut(&key).try_unwrap() { From 645e4caadfba7f474688b47abf3afffe96dce84e Mon Sep 17 00:00:00 2001 From: Sophie Date: Mon, 9 Oct 2023 15:04:29 -0700 Subject: [PATCH 10/25] Revert test --- sway-lsp/tests/fixtures/tokens/paths/src/main.sw | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/sway-lsp/tests/fixtures/tokens/paths/src/main.sw b/sway-lsp/tests/fixtures/tokens/paths/src/main.sw index f3e1c4b0325..d2170afee55 100644 --- a/sway-lsp/tests/fixtures/tokens/paths/src/main.sw +++ b/sway-lsp/tests/fixtures/tokens/paths/src/main.sw @@ -3,9 +3,8 @@ contract; mod test_mod; mod deep_mod; -// use test_mod::A; -// use deep_mod::deeper_mod::deep_fun as dfun; -use deep_mod::deeper_mod::A; +use test_mod::A; +use deep_mod::deeper_mod::deep_fun as dfun; use std::constants::{self, ZERO_B256}; pub fn fun() { @@ -15,11 +14,7 @@ pub fn fun() { value: b256::min(), }; - deep_fun(); - - let a = A; - - test_mod::test_mod::test_fun(); + test_mod::test_fun(); deep_mod::deeper_mod::deep_fun(); std::assert::assert(true); let _ = core::primitives::u64::min(); From b303ad9edc0a72a9eb7c7d4ec50525062ee79c7d Mon Sep 17 00:00:00 2001 From: Sophie Date: Tue, 10 Oct 2023 16:31:47 -0700 Subject: [PATCH 11/25] add typed include_statement --- sway-core/src/language/module.rs | 13 ++++++++----- .../src/language/parsed/include_statement.rs | 9 ++++++--- sway-core/src/language/parsed/mod.rs | 2 +- .../language/ty/side_effect/include_statement.rs | 16 ++++++++++++++++ sway-core/src/language/ty/side_effect/mod.rs | 2 ++ .../src/language/ty/side_effect/side_effect.rs | 4 ++-- sway-core/src/semantic_analysis/ast_node/mod.rs | 10 ++++++++-- .../to_parsed_lang/convert_parse_tree.rs | 5 +++-- 8 files changed, 46 insertions(+), 15 deletions(-) create mode 100644 sway-core/src/language/ty/side_effect/include_statement.rs diff --git a/sway-core/src/language/module.rs b/sway-core/src/language/module.rs index 4aa64c02ee6..920838a0174 100644 --- a/sway-core/src/language/module.rs +++ b/sway-core/src/language/module.rs @@ -33,16 +33,19 @@ where } } +type NamedSubmodule = (ModName, E); +type SubmoduleItem<'module, T, E> = ( + &'module NamedSubmodule, + Box>, +); + /// Iterator type for iterating over submodules. /// /// Used rather than `impl Iterator` to enable recursive submodule iteration. pub struct SubmodulesRecursive<'module, T, E> { _module_type: std::marker::PhantomData, - submods: std::slice::Iter<'module, (ModName, E)>, - current: Option<( - &'module (ModName, E), - Box>, - )>, + submods: std::slice::Iter<'module, NamedSubmodule>, + current: Option>, } impl<'module, T, E> Iterator for SubmodulesRecursive<'module, T, E> diff --git a/sway-core/src/language/parsed/include_statement.rs b/sway-core/src/language/parsed/include_statement.rs index 1c48a3085c3..cad0fc4a48c 100644 --- a/sway-core/src/language/parsed/include_statement.rs +++ b/sway-core/src/language/parsed/include_statement.rs @@ -1,8 +1,11 @@ -use sway_types::span::Span; +use sway_types::{span::Span, Ident}; + +use crate::language::Visibility; #[derive(Clone, Debug)] pub struct IncludeStatement { // this span may be used for errors in the future, although it is not right now. - pub(crate) _span: Span, - pub(crate) _mod_name_span: Span, + pub span: Span, + pub mod_name: Ident, + pub visibility: Visibility, } diff --git a/sway-core/src/language/parsed/mod.rs b/sway-core/src/language/parsed/mod.rs index 7c3860f36f5..d4f93d20a26 100644 --- a/sway-core/src/language/parsed/mod.rs +++ b/sway-core/src/language/parsed/mod.rs @@ -11,7 +11,7 @@ mod use_statement; pub use code_block::*; pub use declaration::*; pub use expression::*; -pub(crate) use include_statement::IncludeStatement; +pub use include_statement::IncludeStatement; pub use module::{ParseModule, ParseSubmodule}; pub use program::{ParseProgram, TreeType}; pub use return_statement::*; diff --git a/sway-core/src/language/ty/side_effect/include_statement.rs b/sway-core/src/language/ty/side_effect/include_statement.rs new file mode 100644 index 00000000000..5d35f02b722 --- /dev/null +++ b/sway-core/src/language/ty/side_effect/include_statement.rs @@ -0,0 +1,16 @@ +use crate::language::Visibility; + +use sway_types::{ident::Ident, Span, Spanned}; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct TyIncludeStatement { + pub span: Span, + pub visibility: Visibility, + pub mod_name: Ident, +} + +impl Spanned for TyIncludeStatement { + fn span(&self) -> Span { + self.span.clone() + } +} diff --git a/sway-core/src/language/ty/side_effect/mod.rs b/sway-core/src/language/ty/side_effect/mod.rs index a3616223506..e70b040de37 100644 --- a/sway-core/src/language/ty/side_effect/mod.rs +++ b/sway-core/src/language/ty/side_effect/mod.rs @@ -1,6 +1,8 @@ +mod include_statement; #[allow(clippy::module_inception)] mod side_effect; mod use_statement; +pub use include_statement::*; pub use side_effect::*; pub use use_statement::*; diff --git a/sway-core/src/language/ty/side_effect/side_effect.rs b/sway-core/src/language/ty/side_effect/side_effect.rs index e92d752fc39..41f2a5b945b 100644 --- a/sway-core/src/language/ty/side_effect/side_effect.rs +++ b/sway-core/src/language/ty/side_effect/side_effect.rs @@ -1,4 +1,4 @@ -use super::TyUseStatement; +use super::{TyIncludeStatement, TyUseStatement}; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct TySideEffect { @@ -7,6 +7,6 @@ pub struct TySideEffect { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum TySideEffectVariant { - IncludeStatement, + IncludeStatement(TyIncludeStatement), UseStatement(TyUseStatement), } diff --git a/sway-core/src/semantic_analysis/ast_node/mod.rs b/sway-core/src/semantic_analysis/ast_node/mod.rs index a91a16fa86d..bfe5ed4db3b 100644 --- a/sway-core/src/semantic_analysis/ast_node/mod.rs +++ b/sway-core/src/semantic_analysis/ast_node/mod.rs @@ -127,9 +127,15 @@ impl ty::TyAstNode { }), }) } - AstNodeContent::IncludeStatement(_) => { + AstNodeContent::IncludeStatement(i) => { ty::TyAstNodeContent::SideEffect(ty::TySideEffect { - side_effect: ty::TySideEffectVariant::IncludeStatement, + side_effect: ty::TySideEffectVariant::IncludeStatement( + ty::TyIncludeStatement { + mod_name: i.mod_name, + span: i.span, + visibility: i.visibility, + }, + ), }) } AstNodeContent::Declaration(decl) => { diff --git a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs index 16b4c32db8b..65f4d28e372 100644 --- a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs +++ b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs @@ -3530,8 +3530,9 @@ fn statement_let_to_ast_nodes( fn submodule_to_include_statement(dependency: &Submodule) -> IncludeStatement { IncludeStatement { - _span: dependency.span(), - _mod_name_span: dependency.name.span(), + span: dependency.span(), + mod_name: dependency.name.clone(), + visibility: pub_token_opt_to_visibility(dependency.visibility.clone()), } } From 57c3ae60e147c9f7f99d742fce1cd1b88cd42cb0 Mon Sep 17 00:00:00 2001 From: Sophie Date: Tue, 10 Oct 2023 19:42:11 -0700 Subject: [PATCH 12/25] Clean up --- .../code_actions/diagnostic/auto_import.rs | 243 +++++++++++++++++ .../code_actions/diagnostic/mod.rs | 244 ++---------------- sway-lsp/src/capabilities/code_actions/mod.rs | 7 +- sway-lsp/src/capabilities/diagnostic.rs | 6 +- sway-lsp/src/capabilities/document_symbol.rs | 1 + sway-lsp/src/capabilities/hover/mod.rs | 5 +- sway-lsp/src/capabilities/rename.rs | 10 +- sway-lsp/src/capabilities/semantic_tokens.rs | 2 +- sway-lsp/src/core/token.rs | 14 +- sway-lsp/src/traverse/lexed_tree.rs | 17 +- sway-lsp/src/traverse/parsed_tree.rs | 34 ++- sway-lsp/src/traverse/typed_tree.rs | 23 +- .../tests/fixtures/tokens/paths/src/main.sw | 25 +- 13 files changed, 368 insertions(+), 263 deletions(-) create mode 100644 sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs diff --git a/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs b/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs new file mode 100644 index 00000000000..fba4b42061f --- /dev/null +++ b/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs @@ -0,0 +1,243 @@ +use std::{cmp::Ordering, collections::HashMap}; + +use crate::{ + capabilities::{code_actions::CodeActionContext, diagnostic::DiagnosticData}, + core::token::{get_range_from_span, AstToken, SymbolKind, TypedAstToken}, +}; +use lsp_types::{ + CodeAction as LspCodeAction, CodeActionKind, CodeActionOrCommand, Position, Range, TextEdit, + WorkspaceEdit, +}; +use serde_json::Value; +use sway_core::language::{ + parsed::ImportType, + ty::{TyDecl, TyIncludeStatement, TyUseStatement}, + CallPath, +}; +use sway_types::{Ident, Spanned}; + +use super::CODE_ACTION_IMPORT_TITLE; + +/// Returns a list of [CodeActionOrCommand] suggestions for inserting a missing import. +pub(crate) fn import_code_action( + ctx: &CodeActionContext, + diagnostics: &mut impl Iterator, +) -> Option> { + // Find a diagnostic that has the attached metadata indicating we should try to suggest an auto-import. + let symbol_name = diagnostics.find_map(|diag| diag.name_to_import)?; + + // Check if there are any matching call paths to import using the name from the diagnostic data. + let call_paths = get_call_paths_for_name(ctx, &symbol_name)?; + + // Collect the tokens we need to determine where to insert the import statement. + let mut use_statements = Vec::::new(); + let mut include_statements = Vec::::new(); + let mut program_type_keyword = None; + + ctx.tokens + .tokens_for_file(ctx.temp_uri) + .for_each(|(_, token)| { + if let Some(TypedAstToken::TypedUseStatement(use_stmt)) = token.typed { + use_statements.push(use_stmt); + } else if let Some(TypedAstToken::TypedIncludeStatement(include_stmt)) = token.typed { + include_statements.push(include_stmt); + } else if token.kind == SymbolKind::ProgramTypeKeyword { + if let AstToken::Keyword(ident) = token.parsed { + program_type_keyword = Some(ident); + } + } + }); + + let actions = call_paths + .filter_map(|call_path| { + // To determine where to insert the import statement in the file, we try these options and do + // one of the following, based on the contents of the file. + // + // 1. Add the import to an existing import that has the same prefix. + // 2. Insert the import on a new line relative to existing use statements. + // 3. Insert the import on a new line after existing mod statements. + // 4. Insert the import on a new line after the program type statement (e.g. `contract;`) + // 5. If all else fails, insert it at the beginning of the file. + + let text_edit: TextEdit = get_text_edit_for_group(&call_path, &use_statements) + .or_else(|| get_text_edit_in_use_block(&call_path, &use_statements)) + .unwrap_or(get_text_edit_fallback( + &call_path, + &include_statements, + &program_type_keyword, + )); + + let changes = HashMap::from([(ctx.uri.clone(), vec![text_edit])]); + + Some(CodeActionOrCommand::CodeAction(LspCodeAction { + title: format!("{} `{}`", CODE_ACTION_IMPORT_TITLE, call_path), + kind: Some(CodeActionKind::QUICKFIX), + edit: Some(WorkspaceEdit { + changes: Some(changes), + ..Default::default() + }), + data: Some(Value::String(ctx.uri.to_string())), + ..Default::default() + })) + }) + .collect::>(); + + if !actions.is_empty() { + return Some(actions); + } + + None +} + +/// Returns an [Iterator] of [CallPath]s that match the given symbol name. +fn get_call_paths_for_name<'s>( + ctx: &'s CodeActionContext, + symbol_name: &'s String, +) -> Option> { + let namespace = ctx.namespace.to_owned()?; + Some( + ctx.tokens + .tokens_for_name(symbol_name) + .filter_map(move |(_, token)| { + // If the typed token is a declaration, then we can import it. + match token.typed.as_ref() { + Some(TypedAstToken::TypedDeclaration(ty_decl)) => { + return match ty_decl { + TyDecl::StructDecl(decl) => { + let struct_decl = ctx.engines.de().get_struct(&decl.decl_id); + let call_path = struct_decl.call_path.to_import_path(&namespace); + Some(call_path) + } + TyDecl::EnumDecl(decl) => { + let enum_decl = ctx.engines.de().get_enum(&decl.decl_id); + let call_path = enum_decl.call_path.to_import_path(&namespace); + Some(call_path) + } + TyDecl::TraitDecl(decl) => { + let trait_decl = ctx.engines.de().get_trait(&decl.decl_id); + let call_path = trait_decl.call_path.to_import_path(&namespace); + Some(call_path) + } + _ => None, + }; + } + Some(TypedAstToken::TypedFunctionDeclaration(ty_decl)) => { + let call_path = ty_decl.call_path.to_import_path(&namespace); + Some(call_path) + } + Some(TypedAstToken::TypedConstantDeclaration(ty_decl)) => { + let call_path = ty_decl.call_path.to_import_path(&namespace); + Some(call_path) + } + Some(TypedAstToken::TypedTypeAliasDeclaration(ty_decl)) => { + let call_path = ty_decl.call_path.to_import_path(&namespace); + Some(call_path) + } + _ => return None, + } + }), + ) +} + +/// If there is an existing [TyUseStatement] with the same prefix as the given [CallPath], returns a +/// [TextEdit] that inserts the call path into the existing statement. Otherwise, returns [None]. +fn get_text_edit_for_group( + call_path: &CallPath, + use_statements: &Vec, +) -> Option { + let group_statement = use_statements.iter().find(|use_stmt| { + call_path + .prefixes + .iter() + .zip(use_stmt.call_path.iter()) + .all(|(prefix, stmt_prefix)| prefix.as_str() == stmt_prefix.as_str()) + })?; + + let prefix_string = group_statement + .call_path + .iter() + .map(|path| path.as_str()) + .collect::>() + .join("::"); + let statement_suffix_string = { + let name = match &group_statement.import_type { + ImportType::Star => "*".to_string(), + ImportType::SelfImport(_) => "self".to_string(), + ImportType::Item(ident) => ident.to_string(), + }; + match &group_statement.alias { + Some(alias) => format!("{} as {}", name, alias.to_string()), + None => name, + } + }; + let mut suffixes = [statement_suffix_string, call_path.suffix.to_string()]; + suffixes.sort(); // TODO: test this. Is there a better way to sort? + let suffix_string = suffixes.join(", "); + + return Some(TextEdit { + range: get_range_from_span(&group_statement.span()), + new_text: format!("use {}::{{{}}};\n", prefix_string, suffix_string), + }); +} + +/// If there are existing [TyUseStatement]s, returns a [TextEdit] to insert the new import statement on the +/// line above or below an existing statement, ordered alphabetically. +fn get_text_edit_in_use_block( + call_path: &CallPath, + use_statements: &Vec, +) -> Option { + let after_statement = use_statements.iter().reduce(|acc, curr| { + if call_path.span().as_str().cmp(curr.span().as_str()) == Ordering::Greater + && curr.span().as_str().cmp(acc.span().as_str()) == Ordering::Greater + { + return curr; + } + return acc; + })?; + + let after_range = get_range_from_span(&after_statement.span()); + let range_line = if call_path + .span() + .as_str() + .cmp(after_statement.span().as_str()) + == Ordering::Greater + { + after_range.end.line + 1 + } else { + after_range.start.line + }; + + Some(TextEdit { + range: Range::new(Position::new(range_line, 0), Position::new(range_line, 0)), + new_text: format!("use {};\n", call_path), + }) +} + +/// Returns a [TextEdit] to insert an import statement either after the last mod statement, after the program +/// type statement, or at the beginning of the file. +fn get_text_edit_fallback( + call_path: &CallPath, + include_statements: &Vec, + program_type_keyword: &Option, +) -> TextEdit { + let range_line = include_statements + .iter() + .map(|stmt| stmt.span()) + .reduce(|acc, span| { + if span > acc { + return span; + } + acc + }) + .map(|span| get_range_from_span(&span).end.line + 1) + .unwrap_or( + program_type_keyword + .clone() + .and_then(|keyword| Some(get_range_from_span(&keyword.span()).end.line + 1)) + .unwrap_or(1), + ); + TextEdit { + range: Range::new(Position::new(range_line, 0), Position::new(range_line, 0)), + new_text: format!("\nuse {};\n", call_path), + } +} diff --git a/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs index 7689c7bbad5..519e3b622c5 100644 --- a/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs @@ -1,231 +1,25 @@ -use std::{cmp::Ordering, collections::HashMap}; +mod auto_import; -use crate::{ - capabilities::{code_actions::CodeActionContext, diagnostic::DiagnosticData}, - core::token::{get_range_from_span, AstToken, TypedAstToken}, -}; -use lsp_types::{ - CodeAction as LspCodeAction, CodeActionKind, CodeActionOrCommand, Position, Range, TextEdit, - WorkspaceEdit, -}; -use serde_json::Value; -use sway_core::{ - language::{ - parsed::ImportType, - ty::{TyDecl, TyUseStatement}, - }, - Namespace, -}; -use sway_types::{Ident, Spanned}; +use crate::capabilities::{code_actions::CodeActionContext, diagnostic::DiagnosticData}; +use lsp_types::CodeActionOrCommand; -use super::CODE_ACTION_IMPORT_TITLE; - -pub(crate) fn code_actions( - ctx: &CodeActionContext, - namespace: &Option, -) -> Option> { - if let Some(namespace) = namespace { - return import_code_action(ctx, namespace); - } - None -} - -/// Returns a [CodeActionOrCommand] for the given code action. -fn import_code_action( - ctx: &CodeActionContext, - namespace: &Namespace, -) -> Option> { - if let Some(diag_data) = ctx.diagnostics.iter().find_map(|diag| { - let data = diag.clone().data?; - serde_json::from_value::(data).ok() - }) { - // Check if there is a type to import using the name from the diagnostic data. - let call_paths = ctx - .tokens - .tokens_for_name(&diag_data.name_to_import) - .filter_map(|(_, token)| { - // If the typed token is a declaration, then we can import it. - match token.typed.as_ref() { - Some(TypedAstToken::TypedDeclaration(ty_decl)) => { - return match ty_decl { - TyDecl::StructDecl(decl) => { - let struct_decl = ctx.engines.de().get_struct(&decl.decl_id); - let call_path = struct_decl.call_path.to_import_path(&namespace); - Some(call_path) - } - TyDecl::EnumDecl(decl) => { - let enum_decl = ctx.engines.de().get_enum(&decl.decl_id); - let call_path = enum_decl.call_path.to_import_path(&namespace); - Some(call_path) - } - TyDecl::TraitDecl(decl) => { - let trait_decl = ctx.engines.de().get_trait(&decl.decl_id); - let call_path = trait_decl.call_path.to_import_path(&namespace); - Some(call_path) - } - _ => None, - }; - } - Some(TypedAstToken::TypedFunctionDeclaration(ty_decl)) => { - let call_path = ty_decl.call_path.to_import_path(&namespace); - Some(call_path) - } - Some(TypedAstToken::TypedConstantDeclaration(ty_decl)) => { - let call_path = ty_decl.call_path.to_import_path(&namespace); - Some(call_path) - } - Some(TypedAstToken::TypedTypeAliasDeclaration(ty_decl)) => { - let call_path = ty_decl.call_path.to_import_path(&namespace); - Some(call_path) - } - _ => return None, - } - }); - - let actions = call_paths - .filter_map(|call_path| { - let mut use_statements = Vec::::new(); - let mut keywords = Vec::::new(); - - ctx.tokens - .tokens_for_file(ctx.temp_uri) - .for_each(|(_, token)| { - if let Some(TypedAstToken::TypedUseStatement(use_stmt)) = token.typed { - use_statements.push(use_stmt); - } - if let AstToken::Keyword(ident) = token.parsed { - keywords.push(ident); - } - }); - - let text_edit: TextEdit = { - // First, check if this import can be added to an existing use statement. - let group_statement = use_statements.iter().find(|use_stmt| { - call_path - .prefixes - .iter() - .zip(use_stmt.call_path.iter()) - .all(|(prefix, stmt_prefix)| prefix.as_str() == stmt_prefix.as_str()) - }); - - if let Some(statement) = group_statement { - let prefix_string = statement - .call_path - .iter() - .map(|path| path.as_str()) - .collect::>() - .join("::"); - let statement_suffix_string = { - let name = match &statement.import_type { - ImportType::Star => "*".to_string(), - ImportType::SelfImport(_) => "self".to_string(), - ImportType::Item(ident) => ident.to_string(), - }; - match &statement.alias { - Some(alias) => format!("{} as {}", name, alias.to_string()), - None => name, - } - }; - let mut suffixes = [statement_suffix_string, call_path.suffix.to_string()]; - suffixes.sort(); //todo - let suffix_string = suffixes.join(", "); +use self::auto_import::import_code_action; - TextEdit { - range: get_range_from_span(&statement.span()), // todo: fix - new_text: format!("use {}::{{{}}};\n", prefix_string, suffix_string), - } - } else { - // Find the best position in the file to insert a new use statement. - // First, check if it can be inserted relative to existing use statements. - if !use_statements.is_empty() { - let after_statement = use_statements - .iter() - .reduce(|acc, curr| { - if call_path.span().as_str().cmp(curr.span().as_str()) - == Ordering::Greater - && curr.span().as_str().cmp(acc.span().as_str()) - == Ordering::Greater - { - return curr; - } - return acc; - }) - .unwrap(); - - let after_range = get_range_from_span(&after_statement.span()); - - let range_line = if call_path - .span() - .as_str() - .cmp(after_statement.span().as_str()) - == Ordering::Greater - { - after_range.end.line + 1 - } else { - after_range.start.line - }; - - TextEdit { - range: Range::new( - Position::new(range_line, 0), - Position::new(range_line, 0), - ), - new_text: format!("use {};\n", call_path), - } - } else { - // Otherwise, insert it at the top of the file, after any mod statements. - let range_line = keywords - .iter() - .filter_map(|kw| { - if kw.as_str() == "mod" { - return Some(get_range_from_span(&kw.span()).end.line + 1); - } - None - }) - .max() - .or_else(|| { - keywords.iter().find_map(|kw| { - if ["mod", "contract", "script", "library", "predicate"] - .contains(&kw.as_str()) - { - return Some( - get_range_from_span(&kw.span()).end.line + 1, - ); - } - None - }) - }) - .unwrap_or(1); - - TextEdit { - range: Range::new( - Position::new(range_line, 0), - Position::new(range_line, 0), - ), - new_text: format!("\nuse {};\n", call_path), - } - } - } - }; - - let changes = HashMap::from([(ctx.uri.clone(), vec![text_edit])]); - - Some(CodeActionOrCommand::CodeAction(LspCodeAction { - title: format!("{} `{}`", CODE_ACTION_IMPORT_TITLE, call_path), - kind: Some(CodeActionKind::QUICKFIX), - edit: Some(WorkspaceEdit { - changes: Some(changes), - ..Default::default() - }), - data: Some(Value::String(ctx.uri.to_string())), - ..Default::default() - })) - }) - .collect::>(); +use super::CODE_ACTION_IMPORT_TITLE; - if !actions.is_empty() { - return Some(actions); +/// Returns a list of [CodeActionOrCommand] based on the relavent compiler diagnostics. +pub(crate) fn code_actions(ctx: &CodeActionContext) -> Option> { + let diagnostics_with_data = ctx.diagnostics.iter().filter_map(|diag| { + if let Some(data) = diag.clone().data { + return serde_json::from_value::(data).ok(); } - } - None + None + }); + + import_code_action(ctx, &mut diagnostics_with_data.clone()) + .into_iter() + .reduce(|mut combined, mut curr| { + combined.append(&mut curr); + combined + }) } diff --git a/sway-lsp/src/capabilities/code_actions/mod.rs b/sway-lsp/src/capabilities/code_actions/mod.rs index af3e917995c..ec8c2047e2c 100644 --- a/sway-lsp/src/capabilities/code_actions/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/mod.rs @@ -22,7 +22,7 @@ use lsp_types::{ }; use serde_json::Value; use std::{collections::HashMap, sync::Arc}; -use sway_core::{language::ty, Engines}; +use sway_core::{language::ty, Engines, Namespace}; use sway_types::Spanned; pub(crate) const CODE_ACTION_IMPL_TITLE: &str = "Generate impl for"; @@ -38,6 +38,7 @@ pub(crate) struct CodeActionContext<'a> { uri: &'a Url, temp_uri: &'a Url, diagnostics: &'a Vec, + namespace: &'a Option, } pub fn code_actions( @@ -59,6 +60,7 @@ pub fn code_actions( uri, temp_uri, diagnostics, + namespace: &session.namespace(), }; let actions_by_type = token @@ -91,8 +93,7 @@ pub fn code_actions( }) .unwrap_or_default(); - let actions_by_diagnostic = - diagnostic::code_actions(&ctx, &session.namespace()).unwrap_or_default(); + let actions_by_diagnostic = diagnostic::code_actions(&ctx).unwrap_or_default(); Some([actions_by_type, actions_by_diagnostic].concat()) } diff --git a/sway-lsp/src/capabilities/diagnostic.rs b/sway-lsp/src/capabilities/diagnostic.rs index a0e5e75c5e6..28a511bc5ec 100644 --- a/sway-lsp/src/capabilities/diagnostic.rs +++ b/sway-lsp/src/capabilities/diagnostic.rs @@ -98,7 +98,7 @@ fn get_warning_diagnostic_tags(warning: &Warning) -> Option> /// Extra data to be sent with a diagnostic and provided in CodeAction context. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct DiagnosticData { - pub name_to_import: String, + pub name_to_import: Option, } impl TryFrom for DiagnosticData { @@ -115,10 +115,10 @@ impl TryFrom for DiagnosticData { fn try_from(value: CompileError) -> Result { match value { CompileError::SymbolNotFound { name, .. } => Ok(DiagnosticData { - name_to_import: name.to_string(), + name_to_import: Some(name.to_string()), }), CompileError::UnknownVariable { var_name, .. } => Ok(DiagnosticData { - name_to_import: var_name.to_string(), + name_to_import: Some(var_name.to_string()), }), _ => anyhow::bail!("Not implemented"), } diff --git a/sway-lsp/src/capabilities/document_symbol.rs b/sway-lsp/src/capabilities/document_symbol.rs index 99ae28a5f20..018742b8d34 100644 --- a/sway-lsp/src/capabilities/document_symbol.rs +++ b/sway-lsp/src/capabilities/document_symbol.rs @@ -41,6 +41,7 @@ pub(crate) fn symbol_kind(symbol_kind: &SymbolKind) -> lsp_types::SymbolKind { | SymbolKind::Keyword | SymbolKind::SelfKeyword | SymbolKind::SelfTypeKeyword + | SymbolKind::ProgramTypeKeyword | SymbolKind::Unknown => lsp_types::SymbolKind::VARIABLE, } } diff --git a/sway-lsp/src/capabilities/hover/mod.rs b/sway-lsp/src/capabilities/hover/mod.rs index a7fa0e61f13..4ec5cb75206 100644 --- a/sway-lsp/src/capabilities/hover/mod.rs +++ b/sway-lsp/src/capabilities/hover/mod.rs @@ -33,7 +33,10 @@ pub fn hover_data( // check if our token is a keyword if matches!( token.kind, - SymbolKind::BoolLiteral | SymbolKind::Keyword | SymbolKind::SelfKeyword + SymbolKind::BoolLiteral + | SymbolKind::Keyword + | SymbolKind::SelfKeyword + | SymbolKind::ProgramTypeKeyword ) { let name = &ident.name; let documentation = keyword_docs.get(name).unwrap(); diff --git a/sway-lsp/src/capabilities/rename.rs b/sway-lsp/src/capabilities/rename.rs index 504ab7db404..3f6f19e8d45 100644 --- a/sway-lsp/src/capabilities/rename.rs +++ b/sway-lsp/src/capabilities/rename.rs @@ -117,7 +117,15 @@ pub fn prepare_rename( // Make sure we don't allow renaming of tokens that // are keywords or intrinsics. - if matches!(token.kind, SymbolKind::Keyword | SymbolKind::Intrinsic) { + if matches!( + token.kind, + SymbolKind::Keyword + | SymbolKind::SelfKeyword + | SymbolKind::SelfTypeKeyword + | SymbolKind::ProgramTypeKeyword + | SymbolKind::BoolLiteral + | SymbolKind::Intrinsic + ) { return Err(LanguageServerError::RenameError( RenameError::SymbolKindNotAllowed, )); diff --git a/sway-lsp/src/capabilities/semantic_tokens.rs b/sway-lsp/src/capabilities/semantic_tokens.rs index bc47c0f0241..61e3ce74724 100644 --- a/sway-lsp/src/capabilities/semantic_tokens.rs +++ b/sway-lsp/src/capabilities/semantic_tokens.rs @@ -157,7 +157,7 @@ fn semantic_token_type(kind: &SymbolKind) -> SemanticTokenType { SymbolKind::BoolLiteral => SemanticTokenType::new("boolean"), SymbolKind::TypeAlias => SemanticTokenType::new("typeAlias"), SymbolKind::TraitType => SemanticTokenType::new("traitType"), - SymbolKind::Keyword => SemanticTokenType::new("keyword"), + SymbolKind::Keyword | SymbolKind::ProgramTypeKeyword => SemanticTokenType::new("keyword"), SymbolKind::Unknown => SemanticTokenType::new("generic"), SymbolKind::BuiltinType => SemanticTokenType::new("builtinType"), SymbolKind::DeriveHelper => SemanticTokenType::new("deriveHelper"), diff --git a/sway-lsp/src/core/token.rs b/sway-lsp/src/core/token.rs index d8a0f836b28..3209fc20635 100644 --- a/sway-lsp/src/core/token.rs +++ b/sway-lsp/src/core/token.rs @@ -6,9 +6,9 @@ use sway_core::{ parsed::{ AbiCastExpression, AmbiguousPathExpression, Declaration, DelineatedPathExpression, EnumVariant, Expression, FunctionApplicationExpression, FunctionParameter, - MethodApplicationExpression, Scrutinee, StorageField, StructExpression, - StructExpressionField, StructField, StructScrutineeField, Supertrait, TraitFn, - UseStatement, + IncludeStatement, MethodApplicationExpression, Scrutinee, StorageField, + StructExpression, StructExpressionField, StructField, StructScrutineeField, Supertrait, + TraitFn, UseStatement, }, ty, }, @@ -35,7 +35,8 @@ pub enum AstToken { FunctionApplicationExpression(FunctionApplicationExpression), FunctionParameter(FunctionParameter), Ident(Ident), - IncludeStatement, + ModuleName, + IncludeStatement(IncludeStatement), Intrinsic(Intrinsic), Keyword(Ident), LibrarySpan(Span), @@ -77,7 +78,8 @@ pub enum TypedAstToken { TypedArgument(TypeArgument), TypedParameter(TypeParameter), TypedTraitConstraint(TraitConstraint), - TypedIncludeStatement, + TypedModuleName, + TypedIncludeStatement(ty::TyIncludeStatement), TypedUseStatement(ty::TyUseStatement), Ident(Ident), } @@ -109,6 +111,8 @@ pub enum SymbolKind { Module, /// Emitted for numeric literals. NumericLiteral, + /// Emitted for keywords. + ProgramTypeKeyword, /// Emitted for the self function parameter and self path-specifier. SelfKeyword, /// Emitted for the Self type parameter. diff --git a/sway-lsp/src/traverse/lexed_tree.rs b/sway-lsp/src/traverse/lexed_tree.rs index 79913af7a02..5dfd0f90b16 100644 --- a/sway-lsp/src/traverse/lexed_tree.rs +++ b/sway-lsp/src/traverse/lexed_tree.rs @@ -39,20 +39,29 @@ pub fn parse(lexed_program: &LexedProgram, ctx: &ParseContext) { fn insert_module_kind(ctx: &ParseContext, kind: &ModuleKind) { match kind { ModuleKind::Script { script_token } => { - insert_keyword(ctx, script_token.span()); + insert_program_type_keyword(ctx, script_token.span()); } ModuleKind::Contract { contract_token } => { - insert_keyword(ctx, contract_token.span()); + insert_program_type_keyword(ctx, contract_token.span()); } ModuleKind::Predicate { predicate_token } => { - insert_keyword(ctx, predicate_token.span()); + insert_program_type_keyword(ctx, predicate_token.span()); } ModuleKind::Library { library_token, .. } => { - insert_keyword(ctx, library_token.span()); + insert_program_type_keyword(ctx, library_token.span()); } } } +fn insert_program_type_keyword(ctx: &ParseContext, span: Span) { + let ident = Ident::new(span); + let token = Token::from_parsed( + AstToken::Keyword(ident.clone()), + SymbolKind::ProgramTypeKeyword, + ); + ctx.tokens.insert(ctx.ident(&ident), token); +} + fn insert_keyword(ctx: &ParseContext, span: Span) { let ident = Ident::new(span); let token = Token::from_parsed(AstToken::Keyword(ident.clone()), SymbolKind::Keyword); diff --git a/sway-lsp/src/traverse/parsed_tree.rs b/sway-lsp/src/traverse/parsed_tree.rs index a643fcdad72..6068dd1add2 100644 --- a/sway-lsp/src/traverse/parsed_tree.rs +++ b/sway-lsp/src/traverse/parsed_tree.rs @@ -17,14 +17,15 @@ use sway_core::{ ArrayIndexExpression, AstNode, AstNodeContent, ConstantDeclaration, Declaration, DelineatedPathExpression, EnumDeclaration, EnumVariant, Expression, ExpressionKind, FunctionApplicationExpression, FunctionDeclaration, FunctionParameter, IfExpression, - ImplItem, ImplSelf, ImplTrait, ImportType, IntrinsicFunctionExpression, - LazyOperatorExpression, MatchExpression, MethodApplicationExpression, MethodName, - ParseModule, ParseProgram, ParseSubmodule, ReassignmentExpression, ReassignmentTarget, - Scrutinee, StorageAccessExpression, StorageDeclaration, StorageField, - StructDeclaration, StructExpression, StructExpressionField, StructField, - StructScrutineeField, SubfieldExpression, Supertrait, TraitDeclaration, TraitFn, - TraitItem, TraitTypeDeclaration, TupleIndexExpression, TypeAliasDeclaration, - UseStatement, VariableDeclaration, WhileLoopExpression, + ImplItem, ImplSelf, ImplTrait, ImportType, IncludeStatement, + IntrinsicFunctionExpression, LazyOperatorExpression, MatchExpression, + MethodApplicationExpression, MethodName, ParseModule, ParseProgram, ParseSubmodule, + ReassignmentExpression, ReassignmentTarget, Scrutinee, StorageAccessExpression, + StorageDeclaration, StorageField, StructDeclaration, StructExpression, + StructExpressionField, StructField, StructScrutineeField, SubfieldExpression, + Supertrait, TraitDeclaration, TraitFn, TraitItem, TraitTypeDeclaration, + TupleIndexExpression, TypeAliasDeclaration, UseStatement, VariableDeclaration, + WhileLoopExpression, }, CallPathTree, HasSubmodules, Literal, }, @@ -73,7 +74,7 @@ impl<'a> ParsedTree<'a> { { self.ctx.tokens.insert( self.ctx.ident(&Ident::new(mod_name_span.clone())), - Token::from_parsed(AstToken::IncludeStatement, SymbolKind::Module), + Token::from_parsed(AstToken::ModuleName, SymbolKind::Module), ); self.collect_parse_module(module); } @@ -106,8 +107,7 @@ impl Parse for AstNode { expression.parse(ctx); } AstNodeContent::UseStatement(use_statement) => use_statement.parse(ctx), - // include statements are handled throught [`collect_module_spans`] - AstNodeContent::IncludeStatement(_) => {} + AstNodeContent::IncludeStatement(include_statement) => include_statement.parse(ctx), AstNodeContent::Error(_, _) => {} } } @@ -164,6 +164,18 @@ impl Parse for UseStatement { } } +impl Parse for IncludeStatement { + fn parse(&self, ctx: &ParseContext) { + ctx.tokens.insert( + ctx.ident(&self.mod_name), + Token::from_parsed( + AstToken::IncludeStatement(self.clone()), + SymbolKind::Unknown, + ), + ); + } +} + impl Parse for Expression { fn parse(&self, ctx: &ParseContext) { match &self.kind { diff --git a/sway-lsp/src/traverse/typed_tree.rs b/sway-lsp/src/traverse/typed_tree.rs index 578de01f346..3ca960f4985 100644 --- a/sway-lsp/src/traverse/typed_tree.rs +++ b/sway-lsp/src/traverse/typed_tree.rs @@ -52,7 +52,7 @@ impl<'a> TypedTree<'a> { .try_get_mut(&self.ctx.ident(&Ident::new(mod_name_span.clone()))) .try_unwrap() { - token.typed = Some(TypedAstToken::TypedIncludeStatement); + token.typed = Some(TypedAstToken::TypedModuleName); token.type_def = Some(TypeDefinition::Ident(Ident::new(module.span.clone()))); } self.collect_module(module); @@ -179,7 +179,26 @@ impl Parse for ty::TySideEffect { ImportType::Star => {} } } - IncludeStatement => {} + IncludeStatement( + include_statement @ ty::TyIncludeStatement { + span: _, + mod_name, + visibility: _, + }, + ) => { + if let Some(mut token) = ctx.tokens.try_get_mut(&ctx.ident(mod_name)).try_unwrap() { + token.typed = Some(TypedAstToken::TypedIncludeStatement( + include_statement.clone(), + )); + if let Some(span) = ctx + .namespace + .submodule(&vec![mod_name.clone()]) + .and_then(|tgt_submod| tgt_submod.span.clone()) + { + token.type_def = Some(TypeDefinition::Ident(Ident::new(span))); + } + } + } } } } diff --git a/sway-lsp/tests/fixtures/tokens/paths/src/main.sw b/sway-lsp/tests/fixtures/tokens/paths/src/main.sw index d2170afee55..b802b5e04eb 100644 --- a/sway-lsp/tests/fixtures/tokens/paths/src/main.sw +++ b/sway-lsp/tests/fixtures/tokens/paths/src/main.sw @@ -1,20 +1,31 @@ contract; mod test_mod; -mod deep_mod; -use test_mod::A; -use deep_mod::deeper_mod::deep_fun as dfun; -use std::constants::{self, ZERO_B256}; +use std::vm::evm::evm_address::EvmAddress; +// mod deep_mod; + +// use test_mod::{A, test_fun}; + +// use deep_mod::deeper_mod::deep_fun as dfun; +// use std::constants::{self, ZERO_B256}; + + + + + + + + pub fn fun() { let _ = std::option::Option::None; let _ = Option::None; - let _ = std::vm::evm::evm_address::EvmAddress { + let _ = EvmAddress { value: b256::min(), }; - test_mod::test_fun(); + test_fun(); deep_mod::deeper_mod::deep_fun(); std::assert::assert(true); let _ = core::primitives::u64::min(); @@ -22,7 +33,7 @@ pub fn fun() { A::fun(); test_mod::A::fun(); - let _ = std::constants::ZERO_B256; + let _ = ZERO_B256; let _ = core::primitives::b256::min(); let _ = ::deep_mod::deeper_mod::DeepEnum::Variant; From 600177fbc3a567106edf24be167334c644cb58e9 Mon Sep 17 00:00:00 2001 From: Sophie Date: Tue, 10 Oct 2023 20:23:10 -0700 Subject: [PATCH 13/25] clippy --- .../code_actions/diagnostic/auto_import.rs | 26 +++++++++---------- .../code_actions/diagnostic/mod.rs | 1 + sway-lsp/src/capabilities/diagnostic.rs | 6 ++--- sway-lsp/src/traverse/typed_tree.rs | 2 +- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs b/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs index fba4b42061f..0f915e53298 100644 --- a/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs +++ b/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs @@ -24,7 +24,7 @@ pub(crate) fn import_code_action( diagnostics: &mut impl Iterator, ) -> Option> { // Find a diagnostic that has the attached metadata indicating we should try to suggest an auto-import. - let symbol_name = diagnostics.find_map(|diag| diag.name_to_import)?; + let symbol_name = diagnostics.find_map(|diag| diag.unknown_symbol_name)?; // Check if there are any matching call paths to import using the name from the diagnostic data. let call_paths = get_call_paths_for_name(ctx, &symbol_name)?; @@ -49,7 +49,7 @@ pub(crate) fn import_code_action( }); let actions = call_paths - .filter_map(|call_path| { + .map(|call_path| { // To determine where to insert the import statement in the file, we try these options and do // one of the following, based on the contents of the file. // @@ -69,7 +69,7 @@ pub(crate) fn import_code_action( let changes = HashMap::from([(ctx.uri.clone(), vec![text_edit])]); - Some(CodeActionOrCommand::CodeAction(LspCodeAction { + CodeActionOrCommand::CodeAction(LspCodeAction { title: format!("{} `{}`", CODE_ACTION_IMPORT_TITLE, call_path), kind: Some(CodeActionKind::QUICKFIX), edit: Some(WorkspaceEdit { @@ -78,7 +78,7 @@ pub(crate) fn import_code_action( }), data: Some(Value::String(ctx.uri.to_string())), ..Default::default() - })) + }) }) .collect::>(); @@ -133,7 +133,7 @@ fn get_call_paths_for_name<'s>( let call_path = ty_decl.call_path.to_import_path(&namespace); Some(call_path) } - _ => return None, + _ => None, } }), ) @@ -143,7 +143,7 @@ fn get_call_paths_for_name<'s>( /// [TextEdit] that inserts the call path into the existing statement. Otherwise, returns [None]. fn get_text_edit_for_group( call_path: &CallPath, - use_statements: &Vec, + use_statements: &[TyUseStatement], ) -> Option { let group_statement = use_statements.iter().find(|use_stmt| { call_path @@ -166,7 +166,7 @@ fn get_text_edit_for_group( ImportType::Item(ident) => ident.to_string(), }; match &group_statement.alias { - Some(alias) => format!("{} as {}", name, alias.to_string()), + Some(alias) => format!("{} as {}", name, alias), None => name, } }; @@ -174,17 +174,17 @@ fn get_text_edit_for_group( suffixes.sort(); // TODO: test this. Is there a better way to sort? let suffix_string = suffixes.join(", "); - return Some(TextEdit { + Some(TextEdit { range: get_range_from_span(&group_statement.span()), new_text: format!("use {}::{{{}}};\n", prefix_string, suffix_string), - }); + }) } /// If there are existing [TyUseStatement]s, returns a [TextEdit] to insert the new import statement on the /// line above or below an existing statement, ordered alphabetically. fn get_text_edit_in_use_block( call_path: &CallPath, - use_statements: &Vec, + use_statements: &[TyUseStatement], ) -> Option { let after_statement = use_statements.iter().reduce(|acc, curr| { if call_path.span().as_str().cmp(curr.span().as_str()) == Ordering::Greater @@ -192,7 +192,7 @@ fn get_text_edit_in_use_block( { return curr; } - return acc; + acc })?; let after_range = get_range_from_span(&after_statement.span()); @@ -217,7 +217,7 @@ fn get_text_edit_in_use_block( /// type statement, or at the beginning of the file. fn get_text_edit_fallback( call_path: &CallPath, - include_statements: &Vec, + include_statements: &[TyIncludeStatement], program_type_keyword: &Option, ) -> TextEdit { let range_line = include_statements @@ -233,7 +233,7 @@ fn get_text_edit_fallback( .unwrap_or( program_type_keyword .clone() - .and_then(|keyword| Some(get_range_from_span(&keyword.span()).end.line + 1)) + .map(|keyword| get_range_from_span(&keyword.span()).end.line + 1) .unwrap_or(1), ); TextEdit { diff --git a/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs index 519e3b622c5..04f32575c3d 100644 --- a/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs +++ b/sway-lsp/src/capabilities/code_actions/diagnostic/mod.rs @@ -9,6 +9,7 @@ use super::CODE_ACTION_IMPORT_TITLE; /// Returns a list of [CodeActionOrCommand] based on the relavent compiler diagnostics. pub(crate) fn code_actions(ctx: &CodeActionContext) -> Option> { + // Find diagnostics that have attached metadata. let diagnostics_with_data = ctx.diagnostics.iter().filter_map(|diag| { if let Some(data) = diag.clone().data { return serde_json::from_value::(data).ok(); diff --git a/sway-lsp/src/capabilities/diagnostic.rs b/sway-lsp/src/capabilities/diagnostic.rs index 28a511bc5ec..dd6f4d1bd40 100644 --- a/sway-lsp/src/capabilities/diagnostic.rs +++ b/sway-lsp/src/capabilities/diagnostic.rs @@ -98,7 +98,7 @@ fn get_warning_diagnostic_tags(warning: &Warning) -> Option> /// Extra data to be sent with a diagnostic and provided in CodeAction context. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct DiagnosticData { - pub name_to_import: Option, + pub unknown_symbol_name: Option, } impl TryFrom for DiagnosticData { @@ -115,10 +115,10 @@ impl TryFrom for DiagnosticData { fn try_from(value: CompileError) -> Result { match value { CompileError::SymbolNotFound { name, .. } => Ok(DiagnosticData { - name_to_import: Some(name.to_string()), + unknown_symbol_name: Some(name.to_string()), }), CompileError::UnknownVariable { var_name, .. } => Ok(DiagnosticData { - name_to_import: Some(var_name.to_string()), + unknown_symbol_name: Some(var_name.to_string()), }), _ => anyhow::bail!("Not implemented"), } diff --git a/sway-lsp/src/traverse/typed_tree.rs b/sway-lsp/src/traverse/typed_tree.rs index 3ca960f4985..6a692e5f42c 100644 --- a/sway-lsp/src/traverse/typed_tree.rs +++ b/sway-lsp/src/traverse/typed_tree.rs @@ -192,7 +192,7 @@ impl Parse for ty::TySideEffect { )); if let Some(span) = ctx .namespace - .submodule(&vec![mod_name.clone()]) + .submodule(&[mod_name.clone()]) .and_then(|tgt_submod| tgt_submod.span.clone()) { token.type_def = Some(TypeDefinition::Ident(Ident::new(span))); From 30fb1fd18f9452dc171a5f8e1749494e80424b52 Mon Sep 17 00:00:00 2001 From: Sophie Date: Wed, 11 Oct 2023 08:53:07 -0700 Subject: [PATCH 14/25] integ tests --- .../tests/fixtures/auto_import/.gitignore | 2 + sway-lsp/tests/fixtures/auto_import/Forc.toml | 8 + .../fixtures/auto_import/src/deep_mod.sw | 3 + .../auto_import/src/deep_mod/deeper_mod.sw | 14 + .../tests/fixtures/auto_import/src/main.sw | 20 + .../fixtures/auto_import/src/test_mod.sw | 9 + .../tests/fixtures/tokens/paths/src/main.sw | 25 +- sway-lsp/tests/integration/code_actions.rs | 446 ++++++++++-------- sway-lsp/tests/lib.rs | 9 +- sway-lsp/tests/utils/src/lib.rs | 4 + 10 files changed, 335 insertions(+), 205 deletions(-) create mode 100644 sway-lsp/tests/fixtures/auto_import/.gitignore create mode 100644 sway-lsp/tests/fixtures/auto_import/Forc.toml create mode 100644 sway-lsp/tests/fixtures/auto_import/src/deep_mod.sw create mode 100644 sway-lsp/tests/fixtures/auto_import/src/deep_mod/deeper_mod.sw create mode 100644 sway-lsp/tests/fixtures/auto_import/src/main.sw create mode 100644 sway-lsp/tests/fixtures/auto_import/src/test_mod.sw diff --git a/sway-lsp/tests/fixtures/auto_import/.gitignore b/sway-lsp/tests/fixtures/auto_import/.gitignore new file mode 100644 index 00000000000..77d3844f58c --- /dev/null +++ b/sway-lsp/tests/fixtures/auto_import/.gitignore @@ -0,0 +1,2 @@ +out +target diff --git a/sway-lsp/tests/fixtures/auto_import/Forc.toml b/sway-lsp/tests/fixtures/auto_import/Forc.toml new file mode 100644 index 00000000000..6cbdeb35961 --- /dev/null +++ b/sway-lsp/tests/fixtures/auto_import/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "auto_import" + +[dependencies] +std = { path = "../../../../sway-lib-std" } diff --git a/sway-lsp/tests/fixtures/auto_import/src/deep_mod.sw b/sway-lsp/tests/fixtures/auto_import/src/deep_mod.sw new file mode 100644 index 00000000000..ea7106afe89 --- /dev/null +++ b/sway-lsp/tests/fixtures/auto_import/src/deep_mod.sw @@ -0,0 +1,3 @@ +library; + +pub mod deeper_mod; diff --git a/sway-lsp/tests/fixtures/auto_import/src/deep_mod/deeper_mod.sw b/sway-lsp/tests/fixtures/auto_import/src/deep_mod/deeper_mod.sw new file mode 100644 index 00000000000..cf104646e90 --- /dev/null +++ b/sway-lsp/tests/fixtures/auto_import/src/deep_mod/deeper_mod.sw @@ -0,0 +1,14 @@ +library; + +pub fn deep_fun(){} + +pub const A: u32 = 0; + +pub enum DeepEnum { + Variant: (), + Number: u32, +} + +pub struct DeepStruct { + field: T, +} diff --git a/sway-lsp/tests/fixtures/auto_import/src/main.sw b/sway-lsp/tests/fixtures/auto_import/src/main.sw new file mode 100644 index 00000000000..4238fd3867d --- /dev/null +++ b/sway-lsp/tests/fixtures/auto_import/src/main.sw @@ -0,0 +1,20 @@ +contract; + +mod test_mod; +mod deep_mod; + +use test_mod::A; + +pub fn fun() { + let _ = EvmAddress { + value: b256::min(), + }; + + test_fun(); + deep_fun(); + A::fun(); + + let _ = ZERO_B256; // l 18 c 19 + let _ = DeepEnum::Variant; // TODO: open an issue for this + let _ = DeepStruct:: { field: 0 }; +} diff --git a/sway-lsp/tests/fixtures/auto_import/src/test_mod.sw b/sway-lsp/tests/fixtures/auto_import/src/test_mod.sw new file mode 100644 index 00000000000..eeb8e2420bd --- /dev/null +++ b/sway-lsp/tests/fixtures/auto_import/src/test_mod.sw @@ -0,0 +1,9 @@ +library; + +pub fn test_fun(){} + +pub struct A {} + +impl A { + pub fn fun() {} +} diff --git a/sway-lsp/tests/fixtures/tokens/paths/src/main.sw b/sway-lsp/tests/fixtures/tokens/paths/src/main.sw index b802b5e04eb..d2170afee55 100644 --- a/sway-lsp/tests/fixtures/tokens/paths/src/main.sw +++ b/sway-lsp/tests/fixtures/tokens/paths/src/main.sw @@ -1,31 +1,20 @@ contract; mod test_mod; +mod deep_mod; -use std::vm::evm::evm_address::EvmAddress; -// mod deep_mod; - -// use test_mod::{A, test_fun}; - -// use deep_mod::deeper_mod::deep_fun as dfun; -// use std::constants::{self, ZERO_B256}; - - - - - - - - +use test_mod::A; +use deep_mod::deeper_mod::deep_fun as dfun; +use std::constants::{self, ZERO_B256}; pub fn fun() { let _ = std::option::Option::None; let _ = Option::None; - let _ = EvmAddress { + let _ = std::vm::evm::evm_address::EvmAddress { value: b256::min(), }; - test_fun(); + test_mod::test_fun(); deep_mod::deeper_mod::deep_fun(); std::assert::assert(true); let _ = core::primitives::u64::min(); @@ -33,7 +22,7 @@ pub fn fun() { A::fun(); test_mod::A::fun(); - let _ = ZERO_B256; + let _ = std::constants::ZERO_B256; let _ = core::primitives::b256::min(); let _ = ::deep_mod::deeper_mod::DeepEnum::Variant; diff --git a/sway-lsp/tests/integration/code_actions.rs b/sway-lsp/tests/integration/code_actions.rs index 855f429c39a..119de6b26f7 100644 --- a/sway-lsp/tests/integration/code_actions.rs +++ b/sway-lsp/tests/integration/code_actions.rs @@ -3,18 +3,22 @@ //! and assert the expected responses. use lsp_types::*; +use serde_json::json; use std::collections::HashMap; -use sway_lsp::{handlers::request, server_state::ServerState}; +use sway_lsp::{ + capabilities::diagnostic::DiagnosticData, handlers::request, server_state::ServerState, +}; fn create_code_action( uri: Url, title: String, changes: HashMap>, disabled: Option, -) -> CodeAction { - CodeAction { + kind: Option, +) -> CodeActionOrCommand { + CodeActionOrCommand::CodeAction(CodeAction { title, - kind: Some(CodeActionKind::REFACTOR), + kind, diagnostics: None, edit: Some(WorkspaceEdit { changes: Some(changes), @@ -25,15 +29,19 @@ fn create_code_action( is_preferred: None, disabled, data: Some(serde_json::to_value(uri).unwrap()), - } + }) } -fn create_code_action_params(uri: Url, range: Range) -> CodeActionParams { +fn create_code_action_params( + uri: Url, + range: Range, + diagnostics: Option>, +) -> CodeActionParams { CodeActionParams { text_document: TextDocumentIdentifier { uri }, range, context: CodeActionContext { - diagnostics: vec![], + diagnostics: diagnostics.unwrap_or_default(), only: None, trigger_kind: Some(CodeActionTriggerKind::AUTOMATIC), }, @@ -42,6 +50,30 @@ fn create_code_action_params(uri: Url, range: Range) -> CodeActionParams { } } +fn create_diagnostic_from_data(range: Range, data: DiagnosticData) -> Option> { + Some(vec![Diagnostic { + range, + data: Some(json!(data)), + ..Default::default() + }]) +} + +fn create_changes_map(uri: &Url, range: Range, new_text: &str) -> HashMap> { + HashMap::from([( + uri.clone(), + vec![TextEdit { + range, + new_text: new_text.to_string(), + }], + )]) +} + +fn send_request(server: &ServerState, params: CodeActionParams) -> Vec { + request::handle_code_action(server, params) + .unwrap() + .expect("Empty response from server") +} + pub(crate) fn code_action_abi_request(server: &ServerState, uri: &Url) { let params = create_code_action_params( uri.clone(), @@ -55,32 +87,33 @@ pub(crate) fn code_action_abi_request(server: &ServerState, uri: &Url) { character: 9, }, }, + None, ); - let res = request::handle_code_action(server, params); - let mut changes = HashMap::new(); - changes.insert( - uri.clone(), - vec![TextEdit { - range: Range { - start: Position { - line: 31, - character: 0, - }, - end: Position { - line: 31, - character: 0, - }, + + let changes = create_changes_map( + uri, + Range { + start: Position { + line: 31, + character: 0, }, - new_text: "\nimpl FooABI for Contract {\n fn main() -> u64 {}\n}\n".to_string(), - }], + end: Position { + line: 31, + character: 0, + }, + }, + "\nimpl FooABI for Contract {\n fn main() -> u64 {}\n}\n", ); - let expected = vec![CodeActionOrCommand::CodeAction(create_code_action( + let expected = vec![create_code_action( uri.clone(), "Generate impl for `FooABI`".to_string(), changes, None, - ))]; - assert_eq!(res.unwrap().unwrap(), expected); + Some(CodeActionKind::REFACTOR), + )]; + + let actual = send_request(server, params); + assert_eq!(expected, actual); } pub(crate) fn code_action_function_request(server: &ServerState, uri: &Url) { @@ -96,29 +129,30 @@ pub(crate) fn code_action_function_request(server: &ServerState, uri: &Url) { character: 4, }, }, + None, ); - let res = request::handle_code_action(server, params); - let mut changes = HashMap::new(); - changes.insert(uri.clone(), vec![TextEdit { - range: Range { - start: Position { - line: 18, - character: 0, - }, - end: Position { - line: 18, - character: 0, + + let changes = create_changes_map(uri, Range { + start: Position { + line: 18, + character: 0, + }, + end: Position { + line: 18, + character: 0, + }, }, - }, - new_text: "/// Add a brief description.\n/// \n/// ### Additional Information\n/// \n/// Provide information beyond the core purpose or functionality.\n/// \n/// ### Reverts\n/// \n/// * List any cases where the function will revert\n/// \n/// ### Number of Storage Accesses\n/// \n/// * Reads: `0`\n/// * Writes: `0`\n/// * Clears: `0`\n/// \n/// ### Examples\n/// \n/// ```sway\n/// let x = test();\n/// ```\n".to_string(), - }]); - let expected = vec![CodeActionOrCommand::CodeAction(create_code_action( + "/// Add a brief description.\n/// \n/// ### Additional Information\n/// \n/// Provide information beyond the core purpose or functionality.\n/// \n/// ### Reverts\n/// \n/// * List any cases where the function will revert\n/// \n/// ### Number of Storage Accesses\n/// \n/// * Reads: `0`\n/// * Writes: `0`\n/// * Clears: `0`\n/// \n/// ### Examples\n/// \n/// ```sway\n/// let x = test();\n/// ```\n"); + let expected = vec![create_code_action( uri.clone(), "Generate a documentation template".to_string(), changes, None, - ))]; - assert_eq!(res.unwrap().unwrap(), expected); + Some(CodeActionKind::REFACTOR), + )]; + + let actual = send_request(server, params); + assert_eq!(expected, actual); } pub(crate) fn code_action_trait_fn_request(server: &ServerState, uri: &Url) { @@ -134,12 +168,10 @@ pub(crate) fn code_action_trait_fn_request(server: &ServerState, uri: &Url) { character: 10, }, }, + None, ); - let res = request::handle_code_action(server, params); - let mut changes = HashMap::new(); - changes.insert(uri.clone(), vec![TextEdit { - range: Range { + let changes = create_changes_map(uri, Range { start: Position { line: 10, character: 0, @@ -149,15 +181,17 @@ pub(crate) fn code_action_trait_fn_request(server: &ServerState, uri: &Url) { character: 0, }, }, - new_text: " /// Add a brief description.\n /// \n /// ### Additional Information\n /// \n /// Provide information beyond the core purpose or functionality.\n /// \n /// ### Returns\n /// \n /// * [Empty] - Add description here\n /// \n /// ### Reverts\n /// \n /// * List any cases where the function will revert\n /// \n /// ### Number of Storage Accesses\n /// \n /// * Reads: `0`\n /// * Writes: `0`\n /// * Clears: `0`\n /// \n /// ### Examples\n /// \n /// ```sway\n /// let x = test_function();\n /// ```\n".to_string(), - }]); - let expected = vec![CodeActionOrCommand::CodeAction(create_code_action( + " /// Add a brief description.\n /// \n /// ### Additional Information\n /// \n /// Provide information beyond the core purpose or functionality.\n /// \n /// ### Returns\n /// \n /// * [Empty] - Add description here\n /// \n /// ### Reverts\n /// \n /// * List any cases where the function will revert\n /// \n /// ### Number of Storage Accesses\n /// \n /// * Reads: `0`\n /// * Writes: `0`\n /// * Clears: `0`\n /// \n /// ### Examples\n /// \n /// ```sway\n /// let x = test_function();\n /// ```\n"); + let expected = vec![create_code_action( uri.clone(), "Generate a documentation template".to_string(), changes, None, - ))]; - assert_eq!(res.unwrap().unwrap(), expected); + Some(CodeActionKind::REFACTOR), + )]; + + let actual = send_request(server, params); + assert_eq!(expected, actual); } pub(crate) fn code_action_struct_request(server: &ServerState, uri: &Url) { @@ -173,37 +207,33 @@ pub(crate) fn code_action_struct_request(server: &ServerState, uri: &Url) { character: 11, }, }, + None, ); - let res = request::handle_code_action(server, params); let mut expected: Vec<_> = Vec::new(); - let mut changes = HashMap::new(); - changes.insert( - uri.clone(), - vec![TextEdit { - range: Range { - start: Position { - line: 25, - character: 0, - }, - end: Position { - line: 25, - character: 0, - }, + + let changes = create_changes_map( + uri, + Range { + start: Position { + line: 25, + character: 0, }, - new_text: "\nimpl Data {\n \n}\n".to_string(), - }], + end: Position { + line: 25, + character: 0, + }, + }, + "\nimpl Data {\n \n}\n", ); - expected.push(CodeActionOrCommand::CodeAction(create_code_action( + expected.push(create_code_action( uri.clone(), "Generate impl for `Data`".to_string(), changes, None, - ))); - let mut changes = HashMap::new(); - changes.insert( - uri.clone(), - vec![TextEdit { - range: Range { + Some(CodeActionKind::REFACTOR), + )); + + let changes = create_changes_map(uri, Range { start: Position { line: 25, character: 0, @@ -213,20 +243,17 @@ pub(crate) fn code_action_struct_request(server: &ServerState, uri: &Url) { character: 0, }, }, - new_text: "\nimpl Data {\n fn new(value: NumberOrString, address: u64) -> Self { Self { value, address } }\n}\n".to_string(), - }], - ); - expected.push(CodeActionOrCommand::CodeAction(create_code_action( + "\nimpl Data {\n fn new(value: NumberOrString, address: u64) -> Self { Self { value, address } }\n}\n"); + expected.push(create_code_action( uri.clone(), "Generate `new`".to_string(), changes, None, - ))); - let mut changes = HashMap::new(); - changes.insert( - uri.clone(), - vec![TextEdit { - range: Range { + Some(CodeActionKind::REFACTOR), + )); + let changes = create_changes_map( + uri, + Range { start: Position { line: 19, character: 0, @@ -236,16 +263,17 @@ pub(crate) fn code_action_struct_request(server: &ServerState, uri: &Url) { character: 0, }, }, - new_text: "/// Add a brief description.\n/// \n/// ### Additional Information\n/// \n/// Provide information beyond the core purpose or functionality.\n".to_string(), - }], - ); - expected.push(CodeActionOrCommand::CodeAction(create_code_action( + "/// Add a brief description.\n/// \n/// ### Additional Information\n/// \n/// Provide information beyond the core purpose or functionality.\n"); + expected.push(create_code_action( uri.clone(), "Generate a documentation template".to_string(), changes, None, - ))); - assert_eq!(res.unwrap().unwrap(), expected); + Some(CodeActionKind::REFACTOR), + )); + + let actual = send_request(server, params); + assert_eq!(expected, actual); } pub(crate) fn code_action_struct_type_params_request(server: &ServerState, uri: &Url) { @@ -261,64 +289,58 @@ pub(crate) fn code_action_struct_type_params_request(server: &ServerState, uri: character: 9, }, }, + None, ); - let res = request::handle_code_action(server, params); let mut expected: Vec<_> = Vec::new(); - let mut changes = HashMap::new(); - changes.insert( - uri.clone(), - vec![TextEdit { - range: Range { - start: Position { - line: 7, - character: 0, - }, - end: Position { - line: 7, - character: 0, - }, + let changes = create_changes_map( + uri, + Range { + start: Position { + line: 7, + character: 0, }, - new_text: "\nimpl Data {\n \n}\n".to_string(), - }], + end: Position { + line: 7, + character: 0, + }, + }, + "\nimpl Data {\n \n}\n", ); - expected.push(CodeActionOrCommand::CodeAction(create_code_action( + expected.push(create_code_action( uri.clone(), "Generate impl for `Data`".to_string(), changes, None, - ))); + Some(CodeActionKind::REFACTOR), + )); - let mut changes = HashMap::new(); - changes.insert( - uri.clone(), - vec![TextEdit { - range: Range { - start: Position { - line: 9, - character: 0, - }, - end: Position { - line: 9, - character: 0, - }, + let changes = create_changes_map( + uri, + Range { + start: Position { + line: 9, + character: 0, }, - new_text: " fn new(value: T) -> Self { Self { value } }\n".to_string(), - }], + end: Position { + line: 9, + character: 0, + }, + }, + " fn new(value: T) -> Self { Self { value } }\n", ); - expected.push(CodeActionOrCommand::CodeAction(create_code_action( + expected.push(create_code_action( uri.clone(), "Generate `new`".to_string(), changes, Some(CodeActionDisabled { reason: "Struct Data already has a `new` function".to_string(), }), - ))); + Some(CodeActionKind::REFACTOR), + )); - let mut changes = HashMap::new(); - changes.insert( - uri.clone(), - vec![TextEdit { - range: Range { + let changes = create_changes_map( + uri, + Range { start: Position { line: 4, character: 0, @@ -328,16 +350,18 @@ pub(crate) fn code_action_struct_type_params_request(server: &ServerState, uri: character: 0, }, }, - new_text: "/// Add a brief description.\n/// \n/// ### Additional Information\n/// \n/// Provide information beyond the core purpose or functionality.\n".to_string(), - }], -); - expected.push(CodeActionOrCommand::CodeAction(create_code_action( + "/// Add a brief description.\n/// \n/// ### Additional Information\n/// \n/// Provide information beyond the core purpose or functionality.\n"); + + expected.push(create_code_action( uri.clone(), "Generate a documentation template".to_string(), changes, None, - ))); - assert_eq!(res.unwrap().unwrap(), expected); + Some(CodeActionKind::REFACTOR), + )); + + let actual = send_request(server, params); + assert_eq!(expected, actual); } pub(crate) fn code_action_struct_existing_impl_request(server: &ServerState, uri: &Url) { @@ -353,62 +377,55 @@ pub(crate) fn code_action_struct_existing_impl_request(server: &ServerState, uri character: 7, }, }, + None, ); - let res = request::handle_code_action(server, params); let mut expected: Vec<_> = Vec::new(); - let mut changes = HashMap::new(); - changes.insert( - uri.clone(), - vec![TextEdit { - range: Range { - start: Position { - line: 6, - character: 0, - }, - end: Position { - line: 6, - character: 0, - }, + let changes = create_changes_map( + uri, + Range { + start: Position { + line: 6, + character: 0, }, - new_text: "\nimpl A {\n \n}\n".to_string(), - }], + end: Position { + line: 6, + character: 0, + }, + }, + "\nimpl A {\n \n}\n", ); - expected.push(CodeActionOrCommand::CodeAction(create_code_action( + expected.push(create_code_action( uri.clone(), "Generate impl for `A`".to_string(), changes, None, - ))); + Some(CodeActionKind::REFACTOR), + )); - let mut changes = HashMap::new(); - changes.insert( - uri.clone(), - vec![TextEdit { - range: Range { - start: Position { - line: 8, - character: 0, - }, - end: Position { - line: 8, - character: 0, - }, + let changes = create_changes_map( + uri, + Range { + start: Position { + line: 8, + character: 0, }, - new_text: " fn new(a: u64, b: u64) -> Self { Self { a, b } }\n".to_string(), - }], + end: Position { + line: 8, + character: 0, + }, + }, + " fn new(a: u64, b: u64) -> Self { Self { a, b } }\n", ); - expected.push(CodeActionOrCommand::CodeAction(create_code_action( + expected.push(create_code_action( uri.clone(), "Generate `new`".to_string(), changes, None, - ))); - - let mut changes = HashMap::new(); - changes.insert( - uri.clone(), - vec![TextEdit { - range: Range { + Some(CodeActionKind::REFACTOR), + )); + let changes = create_changes_map( + uri, + Range { start: Position { line: 2, character: 0, @@ -418,16 +435,75 @@ pub(crate) fn code_action_struct_existing_impl_request(server: &ServerState, uri character: 0, }, }, - new_text: "/// Add a brief description.\n/// \n/// ### Additional Information\n/// \n/// Provide information beyond the core purpose or functionality.\n".to_string(), - }], - ); - expected.push(CodeActionOrCommand::CodeAction(create_code_action( + "/// Add a brief description.\n/// \n/// ### Additional Information\n/// \n/// Provide information beyond the core purpose or functionality.\n"); + expected.push(create_code_action( uri.clone(), "Generate a documentation template".to_string(), changes, None, - ))); + Some(CodeActionKind::REFACTOR), + )); + + let actual = send_request(server, params); + assert_eq!(expected, actual); +} + +pub(crate) fn code_action_auto_import_request(server: &ServerState, uri: &Url) { + // let _ = EvmAddress { // l 8 c 19 + // value: b256::min(), + // }; + + // test_fun(); // l 14 c 9 + // deep_fun(); // l 15 c 9 + // A::fun(); // l 16 c 6 + + // let _ = ZERO_B256; // l 18 c 19 + // let _ = DeepEnum::Variant; // l 19 c 17 + // let _ = DeepStruct:: { field: 0 }; // l 20 c 17 + + let range = Range { + start: Position { + line: 8, + character: 12, + }, + end: Position { + line: 8, + character: 22, + }, + }; + + let params = create_code_action_params( + uri.clone(), + range.clone(), + create_diagnostic_from_data( + range, + DiagnosticData { + unknown_symbol_name: Some("EvmAddress".to_string()), + }, + ), + ); + let changes = create_changes_map( + uri, + Range { + start: Position { + line: 5, + character: 0, + }, + end: Position { + line: 5, + character: 0, + }, + }, + "use std::vm::evm::evm_address::EvmAddress;\n", + ); + let expected = vec![create_code_action( + uri.clone(), + "Import `std::vm::evm::evm_address::EvmAddress`".to_string(), + changes, + None, + Some(CodeActionKind::QUICKFIX), + )]; - let result = res.unwrap().unwrap(); - assert_eq!(result, expected); + let actual = send_request(server, params); + assert_eq!(expected, actual); } diff --git a/sway-lsp/tests/lib.rs b/sway-lsp/tests/lib.rs index 7ff63d609f5..92cf9e2d49e 100644 --- a/sway-lsp/tests/lib.rs +++ b/sway-lsp/tests/lib.rs @@ -10,7 +10,7 @@ use sway_lsp::{ use sway_lsp_test_utils::{ assert_server_requests, dir_contains_forc_manifest, doc_comments_dir, e2e_language_dir, e2e_test_dir, generic_impl_self_dir, get_fixture, load_sway_example, runnables_test_dir, - self_impl_reassignment_dir, sway_workspace_dir, test_fixtures_dir, + self_impl_reassignment_dir, sway_workspace_dir, test_fixtures_dir, paths_dir, }; use tower_lsp::LspService; @@ -405,7 +405,7 @@ async fn go_to_definition_for_paths() { let server = ServerState::default(); let uri = open( &server, - test_fixtures_dir().join("tokens/paths/src/main.sw"), + paths_dir().join("src/main.sw"), ) .await; @@ -1746,6 +1746,11 @@ lsp_capability_test!( code_actions::code_action_struct_existing_impl_request, self_impl_reassignment_dir().join("src/main.sw") ); +lsp_capability_test!( + code_action_auto_import, + code_actions::code_action_auto_import_request, + test_fixtures_dir().join("auto_import/src/main.sw") +); lsp_capability_test!( code_lens, lsp::code_lens_request, diff --git a/sway-lsp/tests/utils/src/lib.rs b/sway-lsp/tests/utils/src/lib.rs index a4b6720b6a9..5fb1f6f8c2d 100644 --- a/sway-lsp/tests/utils/src/lib.rs +++ b/sway-lsp/tests/utils/src/lib.rs @@ -63,6 +63,10 @@ pub fn self_impl_reassignment_dir() -> PathBuf { .join("self_impl_reassignment") } +pub fn paths_dir() -> PathBuf { + test_fixtures_dir().join("tokens/paths") +} + pub fn get_absolute_path(path: &str) -> String { sway_workspace_dir().join(path).to_str().unwrap().into() } From 367459427044fe845fdc91d5ca211780967632f6 Mon Sep 17 00:00:00 2001 From: Sophie Date: Wed, 11 Oct 2023 18:48:25 -0700 Subject: [PATCH 15/25] test progress --- Cargo.lock | 23 + sway-lsp/Cargo.toml | 1 + .../code_actions/diagnostic/auto_import.rs | 14 +- sway-lsp/tests/fixtures/auto_import/Forc.lock | 13 + sway-lsp/tests/fixtures/auto_import/Forc.toml | 1 + .../auto_import/src/deep_mod/deeper_mod.sw | 8 +- .../tests/fixtures/auto_import/src/main.sw | 25 +- .../fixtures/auto_import/src/test_mod.sw | 2 + sway-lsp/tests/integration/code_actions.rs | 450 +++++++++++++++++- sway-lsp/tests/lib.rs | 29 +- 10 files changed, 529 insertions(+), 37 deletions(-) create mode 100644 sway-lsp/tests/fixtures/auto_import/Forc.lock diff --git a/Cargo.lock b/Cargo.lock index 871c6d56cda..9083cd1be9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1345,6 +1345,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.9.0" @@ -4509,6 +4515,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "prettydiff" version = "0.5.1" @@ -5882,6 +5898,7 @@ dependencies = [ "notify", "notify-debouncer-mini", "parking_lot 0.12.1", + "pretty_assertions", "proc-macro2", "quote", "rayon", @@ -7290,6 +7307,12 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "yansi-term" version = "0.1.2" diff --git a/sway-lsp/Cargo.toml b/sway-lsp/Cargo.toml index c30b1d52abd..76ac58caff9 100644 --- a/sway-lsp/Cargo.toml +++ b/sway-lsp/Cargo.toml @@ -47,6 +47,7 @@ criterion = "0.5" dirs = "4.0" futures = { version = "0.3", default-features = false, features = ["std", "async-await"] } sway-lsp-test-utils = { path = "tests/utils" } +pretty_assertions = "1.4.0" tower = { version = "0.4.12", default-features = false, features = ["util"] } [[bench]] diff --git a/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs b/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs index 0f915e53298..38f7cc6e0ee 100644 --- a/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs +++ b/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs @@ -1,5 +1,4 @@ use std::{cmp::Ordering, collections::HashMap}; - use crate::{ capabilities::{code_actions::CodeActionContext, diagnostic::DiagnosticData}, core::token::{get_range_from_span, AstToken, SymbolKind, TypedAstToken}, @@ -15,7 +14,6 @@ use sway_core::language::{ CallPath, }; use sway_types::{Ident, Spanned}; - use super::CODE_ACTION_IMPORT_TITLE; /// Returns a list of [CodeActionOrCommand] suggestions for inserting a missing import. @@ -59,6 +57,7 @@ pub(crate) fn import_code_action( // 4. Insert the import on a new line after the program type statement (e.g. `contract;`) // 5. If all else fails, insert it at the beginning of the file. + // TODO: combine into 1 function and write unit test for location in the file let text_edit: TextEdit = get_text_edit_for_group(&call_path, &use_statements) .or_else(|| get_text_edit_in_use_block(&call_path, &use_statements)) .unwrap_or(get_text_edit_fallback( @@ -89,13 +88,14 @@ pub(crate) fn import_code_action( None } -/// Returns an [Iterator] of [CallPath]s that match the given symbol name. +/// Returns an [Iterator] of [CallPath]s that match the given symbol name. The [CallPath]s are sorted +/// alphabetically. fn get_call_paths_for_name<'s>( ctx: &'s CodeActionContext, symbol_name: &'s String, ) -> Option> { let namespace = ctx.namespace.to_owned()?; - Some( + let mut call_paths = ctx.tokens .tokens_for_name(symbol_name) .filter_map(move |(_, token)| { @@ -135,8 +135,10 @@ fn get_call_paths_for_name<'s>( } _ => None, } - }), - ) + }).collect::>(); + call_paths.sort(); + Some(call_paths.into_iter()) + } /// If there is an existing [TyUseStatement] with the same prefix as the given [CallPath], returns a diff --git a/sway-lsp/tests/fixtures/auto_import/Forc.lock b/sway-lsp/tests/fixtures/auto_import/Forc.lock new file mode 100644 index 00000000000..64d2d65076b --- /dev/null +++ b/sway-lsp/tests/fixtures/auto_import/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "auto_import" +source = "member" +dependencies = ["std"] + +[[package]] +name = "core" +source = "path+from-root-C29FCA7313EFC5E1" + +[[package]] +name = "std" +source = "path+from-root-C29FCA7313EFC5E1" +dependencies = ["core"] diff --git a/sway-lsp/tests/fixtures/auto_import/Forc.toml b/sway-lsp/tests/fixtures/auto_import/Forc.toml index 6cbdeb35961..e4a46a399a6 100644 --- a/sway-lsp/tests/fixtures/auto_import/Forc.toml +++ b/sway-lsp/tests/fixtures/auto_import/Forc.toml @@ -6,3 +6,4 @@ name = "auto_import" [dependencies] std = { path = "../../../../sway-lib-std" } +core = { path = "../../../../sway-lib-core" } diff --git a/sway-lsp/tests/fixtures/auto_import/src/deep_mod/deeper_mod.sw b/sway-lsp/tests/fixtures/auto_import/src/deep_mod/deeper_mod.sw index cf104646e90..2f80f41f1f1 100644 --- a/sway-lsp/tests/fixtures/auto_import/src/deep_mod/deeper_mod.sw +++ b/sway-lsp/tests/fixtures/auto_import/src/deep_mod/deeper_mod.sw @@ -2,8 +2,6 @@ library; pub fn deep_fun(){} -pub const A: u32 = 0; - pub enum DeepEnum { Variant: (), Number: u32, @@ -12,3 +10,9 @@ pub enum DeepEnum { pub struct DeepStruct { field: T, } + +pub type A = DeepStruct; + +pub trait DeepTrait { + fn deep_method(self); +} \ No newline at end of file diff --git a/sway-lsp/tests/fixtures/auto_import/src/main.sw b/sway-lsp/tests/fixtures/auto_import/src/main.sw index 4238fd3867d..ac5eb43f871 100644 --- a/sway-lsp/tests/fixtures/auto_import/src/main.sw +++ b/sway-lsp/tests/fixtures/auto_import/src/main.sw @@ -3,7 +3,7 @@ contract; mod test_mod; mod deep_mod; -use test_mod::A; +use test_mod::test_fun; pub fn fun() { let _ = EvmAddress { @@ -14,7 +14,26 @@ pub fn fun() { deep_fun(); A::fun(); - let _ = ZERO_B256; // l 18 c 19 - let _ = DeepEnum::Variant; // TODO: open an issue for this + let a: DeepEnum = DeepEnum::Variant; // TODO: open an issue for variants let _ = DeepStruct:: { field: 0 }; + + let _ = TEST_CONST; + let _ = ZERO_B256; // TODO: fix this + + let _ = overflow(); // TODO: fix this + let _: Result = msg_sender(); +} + +struct LocalStruct { + field: u64, +} + +impl DeepTrait for LocalStruct { + fn deep_method(self) {} +} + +impl TryFrom for LocalStruct { + fn try_from(u: u32) -> Option { + None + } } diff --git a/sway-lsp/tests/fixtures/auto_import/src/test_mod.sw b/sway-lsp/tests/fixtures/auto_import/src/test_mod.sw index eeb8e2420bd..249ab122e12 100644 --- a/sway-lsp/tests/fixtures/auto_import/src/test_mod.sw +++ b/sway-lsp/tests/fixtures/auto_import/src/test_mod.sw @@ -7,3 +7,5 @@ pub struct A {} impl A { pub fn fun() {} } + +pub const TEST_CONST: u64 = 111; \ No newline at end of file diff --git a/sway-lsp/tests/integration/code_actions.rs b/sway-lsp/tests/integration/code_actions.rs index 119de6b26f7..63e23d79d54 100644 --- a/sway-lsp/tests/integration/code_actions.rs +++ b/sway-lsp/tests/integration/code_actions.rs @@ -3,6 +3,7 @@ //! and assert the expected responses. use lsp_types::*; +use pretty_assertions::assert_eq; use serde_json::json; use std::collections::HashMap; use sway_lsp::{ @@ -68,10 +69,10 @@ fn create_changes_map(uri: &Url, range: Range, new_text: &str) -> HashMap Vec { - request::handle_code_action(server, params) +fn send_request(server: &ServerState, params: &CodeActionParams) -> Vec { + request::handle_code_action(server, params.clone()) .unwrap() - .expect("Empty response from server") + .unwrap_or_else(|| panic!("Empty response from server for request: {:?}", params)) } pub(crate) fn code_action_abi_request(server: &ServerState, uri: &Url) { @@ -112,7 +113,7 @@ pub(crate) fn code_action_abi_request(server: &ServerState, uri: &Url) { Some(CodeActionKind::REFACTOR), )]; - let actual = send_request(server, params); + let actual = send_request(server, ¶ms); assert_eq!(expected, actual); } @@ -151,7 +152,7 @@ pub(crate) fn code_action_function_request(server: &ServerState, uri: &Url) { Some(CodeActionKind::REFACTOR), )]; - let actual = send_request(server, params); + let actual = send_request(server, ¶ms); assert_eq!(expected, actual); } @@ -190,7 +191,7 @@ pub(crate) fn code_action_trait_fn_request(server: &ServerState, uri: &Url) { Some(CodeActionKind::REFACTOR), )]; - let actual = send_request(server, params); + let actual = send_request(server, ¶ms); assert_eq!(expected, actual); } @@ -272,7 +273,7 @@ pub(crate) fn code_action_struct_request(server: &ServerState, uri: &Url) { Some(CodeActionKind::REFACTOR), )); - let actual = send_request(server, params); + let actual = send_request(server, ¶ms); assert_eq!(expected, actual); } @@ -360,7 +361,7 @@ pub(crate) fn code_action_struct_type_params_request(server: &ServerState, uri: Some(CodeActionKind::REFACTOR), )); - let actual = send_request(server, params); + let actual = send_request(server, ¶ms); assert_eq!(expected, actual); } @@ -444,23 +445,12 @@ pub(crate) fn code_action_struct_existing_impl_request(server: &ServerState, uri Some(CodeActionKind::REFACTOR), )); - let actual = send_request(server, params); + let actual = send_request(server, ¶ms); assert_eq!(expected, actual); } -pub(crate) fn code_action_auto_import_request(server: &ServerState, uri: &Url) { - // let _ = EvmAddress { // l 8 c 19 - // value: b256::min(), - // }; - - // test_fun(); // l 14 c 9 - // deep_fun(); // l 15 c 9 - // A::fun(); // l 16 c 6 - - // let _ = ZERO_B256; // l 18 c 19 - // let _ = DeepEnum::Variant; // l 19 c 17 - // let _ = DeepStruct:: { field: 0 }; // l 20 c 17 - +pub(crate) fn code_action_auto_import_struct_request(server: &ServerState, uri: &Url) { + // EvmAddress: external library let range = Range { start: Position { line: 8, @@ -474,7 +464,7 @@ pub(crate) fn code_action_auto_import_request(server: &ServerState, uri: &Url) { let params = create_code_action_params( uri.clone(), - range.clone(), + range, create_diagnostic_from_data( range, DiagnosticData { @@ -504,6 +494,418 @@ pub(crate) fn code_action_auto_import_request(server: &ServerState, uri: &Url) { Some(CodeActionKind::QUICKFIX), )]; - let actual = send_request(server, params); + let actual = send_request(server, ¶ms); + assert_eq!(expected, actual); + + // DeepStruct: local library + let range = Range { + start: Position { + line: 17, + character: 12, + }, + end: Position { + line: 17, + character: 22, + }, + }; + + let params = create_code_action_params( + uri.clone(), + range, + create_diagnostic_from_data( + range, + DiagnosticData { + unknown_symbol_name: Some("DeepStruct".to_string()), + }, + ), + ); + let changes = create_changes_map( + uri, + Range { + start: Position { + line: 5, + character: 0, + }, + end: Position { + line: 5, + character: 0, + }, + }, + "use deep_mod::deeper_mod::DeepStruct;\n", + ); + let expected = vec![create_code_action( + uri.clone(), + "Import `deep_mod::deeper_mod::DeepStruct`".to_string(), + changes, + None, + Some(CodeActionKind::QUICKFIX), + )]; + + let actual = send_request(server, ¶ms); + assert_eq!(expected, actual); +} + +pub(crate) fn code_action_auto_import_enum_request(server: &ServerState, uri: &Url) { + // AuthError: external library + let range = Range { + start: Position { + line: 23, + character: 28, + }, + end: Position { + line: 23, + character: 37, + }, + }; + + let params = create_code_action_params( + uri.clone(), + range, + create_diagnostic_from_data( + range, + DiagnosticData { + unknown_symbol_name: Some("AuthError".to_string()), + }, + ), + ); + let changes = create_changes_map( + uri, + Range { + start: Position { + line: 5, + character: 0, + }, + end: Position { + line: 5, + character: 0, + }, + }, + "use std::auth::AuthError;\n", + ); + let expected = vec![create_code_action( + uri.clone(), + "Import `std::auth::AuthError`".to_string(), + changes, + None, + Some(CodeActionKind::QUICKFIX), + )]; + + let actual = send_request(server, ¶ms); + assert_eq!(expected, actual); + + // DeepEnum: local library + let range = Range { + start: Position { + line: 16, + character: 11, + }, + end: Position { + line: 16, + character: 19, + }, + }; + + let params = create_code_action_params( + uri.clone(), + range, + create_diagnostic_from_data( + range, + DiagnosticData { + unknown_symbol_name: Some("DeepEnum".to_string()), + }, + ), + ); + let changes = create_changes_map( + uri, + Range { + start: Position { + line: 5, + character: 0, + }, + end: Position { + line: 5, + character: 0, + }, + }, + "use deep_mod::deeper_mod::DeepEnum;\n", + ); + let expected = vec![create_code_action( + uri.clone(), + "Import `deep_mod::deeper_mod::DeepEnum`".to_string(), + changes, + None, + Some(CodeActionKind::QUICKFIX), + )]; + + let actual = send_request(server, ¶ms); + assert_eq!(expected, actual); +} + +pub(crate) fn code_action_auto_import_function_request(server: &ServerState, uri: &Url) { + // TODO: external library, test with overflow + + // deep_fun: local library + let range = Range { + start: Position { + line: 13, + character: 4, + }, + end: Position { + line: 13, + character: 12, + }, + }; + + let params = create_code_action_params( + uri.clone(), + range, + create_diagnostic_from_data( + range, + DiagnosticData { + unknown_symbol_name: Some("deep_fun".to_string()), + }, + ), + ); + let changes = create_changes_map( + uri, + Range { + start: Position { + line: 5, + character: 0, + }, + end: Position { + line: 5, + character: 0, + }, + }, + "use deep_mod::deeper_mod::deep_fun;\n", + ); + let expected = vec![create_code_action( + uri.clone(), + "Import `deep_mod::deeper_mod::deep_fun`".to_string(), + changes, + None, + Some(CodeActionKind::QUICKFIX), + )]; + + let actual = send_request(server, ¶ms); + assert_eq!(expected, actual); +} + +pub(crate) fn code_action_auto_import_constant_request(server: &ServerState, uri: &Url) { + // TODO: external library, test with ZERO_B256 + + // TEST_CONST: import a constant from a local library + let range = Range { + start: Position { + line: 19, + character: 12, + }, + end: Position { + line: 19, + character: 22, + }, + }; + + let params = create_code_action_params( + uri.clone(), + range, + create_diagnostic_from_data( + range, + DiagnosticData { + unknown_symbol_name: Some("TEST_CONST".to_string()), + }, + ), + ); + let changes = create_changes_map( + uri, + Range { + start: Position { + line: 5, + character: 0, + }, + end: Position { + line: 5, + character: 23, + }, + }, + "use test_mod::{TEST_CONST, test_fun};\n", + ); + let expected = vec![create_code_action( + uri.clone(), + "Import `test_mod::TEST_CONST`".to_string(), + changes, + None, + Some(CodeActionKind::QUICKFIX), + )]; + + let actual = send_request(server, ¶ms); + assert_eq!(expected, actual); +} + +pub(crate) fn code_action_auto_import_trait_request(server: &ServerState, uri: &Url) { + // TryFrom: external library + let range = Range { + start: Position { + line: 33, + character: 5, + }, + end: Position { + line: 33, + character: 12, + }, + }; + + let params = create_code_action_params( + uri.clone(), + range, + create_diagnostic_from_data( + range, + DiagnosticData { + unknown_symbol_name: Some("TryFrom".to_string()), + }, + ), + ); + let changes = create_changes_map( + uri, + Range { + start: Position { + line: 5, + character: 0, + }, + end: Position { + line: 5, + character: 0, + }, + }, + "use std::convert::TryFrom;\n", + ); + let expected = vec![create_code_action( + uri.clone(), + "Import `std::convert::TryFrom`".to_string(), + changes, + None, + Some(CodeActionKind::QUICKFIX), + )]; + + let actual = send_request(server, ¶ms); + assert_eq!(expected, actual); + + // DeepTrait: local library + let range = Range { + start: Position { + line: 29, + character: 5, + }, + end: Position { + line: 29, + character: 14, + }, + }; + + let params = create_code_action_params( + uri.clone(), + range, + create_diagnostic_from_data( + range, + DiagnosticData { + unknown_symbol_name: Some("DeepTrait".to_string()), + }, + ), + ); + let changes = create_changes_map( + uri, + Range { + start: Position { + line: 5, + character: 0, + }, + end: Position { + line: 5, + character: 0, + }, + }, + "use deep_mod::deeper_mod::DeepTrait;\n", + ); + let expected = vec![create_code_action( + uri.clone(), + "Import `deep_mod::deeper_mod::DeepTrait`".to_string(), + changes, + None, + Some(CodeActionKind::QUICKFIX), + )]; + + let actual = send_request(server, ¶ms); + assert_eq!(expected, actual); +} + +pub(crate) fn code_action_auto_import_alias_request(server: &ServerState, uri: &Url) { + // TODO: find an example in an external library + // A: local library with multiple possible imports + let range = Range { + start: Position { + line: 14, + character: 4, + }, + end: Position { + line: 14, + character: 5, + }, + }; + + let params = create_code_action_params( + uri.clone(), + range, + create_diagnostic_from_data( + range, + DiagnosticData { + unknown_symbol_name: Some("A".to_string()), + }, + ), + ); + let changes = create_changes_map( + uri, + Range { + start: Position { + line: 5, + character: 0, + }, + end: Position { + line: 5, + character: 0, + }, + }, + "use deep_mod::deeper_mod::A;\n", + ); + let mut expected = vec![create_code_action( + uri.clone(), + "Import `deep_mod::deeper_mod::A`".to_string(), + changes, + None, + Some(CodeActionKind::QUICKFIX), + )]; + let changes = create_changes_map( + uri, + Range { + start: Position { + line: 5, + character: 0, + }, + end: Position { + line: 5, + character: 23, + }, + }, + "use test_mod::{A, test_fun};\n", + ); + expected.push(create_code_action( + uri.clone(), + "Import `test_mod::A`".to_string(), + changes, + None, + Some(CodeActionKind::QUICKFIX), + )); + + let actual = send_request(server, ¶ms); assert_eq!(expected, actual); } diff --git a/sway-lsp/tests/lib.rs b/sway-lsp/tests/lib.rs index 92cf9e2d49e..753640b5b4b 100644 --- a/sway-lsp/tests/lib.rs +++ b/sway-lsp/tests/lib.rs @@ -1747,8 +1747,33 @@ lsp_capability_test!( self_impl_reassignment_dir().join("src/main.sw") ); lsp_capability_test!( - code_action_auto_import, - code_actions::code_action_auto_import_request, + code_action_auto_import_struct, + code_actions::code_action_auto_import_struct_request, + test_fixtures_dir().join("auto_import/src/main.sw") +); +lsp_capability_test!( + code_action_auto_import_enum, + code_actions::code_action_auto_import_enum_request, + test_fixtures_dir().join("auto_import/src/main.sw") +); +lsp_capability_test!( + code_action_auto_import_function, + code_actions::code_action_auto_import_function_request, + test_fixtures_dir().join("auto_import/src/main.sw") +); +lsp_capability_test!( + code_action_auto_import_constant, + code_actions::code_action_auto_import_constant_request, + test_fixtures_dir().join("auto_import/src/main.sw") +); +lsp_capability_test!( + code_action_auto_import_trait, + code_actions::code_action_auto_import_trait_request, + test_fixtures_dir().join("auto_import/src/main.sw") +); +lsp_capability_test!( + code_action_auto_import_alias, + code_actions::code_action_auto_import_alias_request, test_fixtures_dir().join("auto_import/src/main.sw") ); lsp_capability_test!( From 2aa72de9814602ca21ab10a7b2b33668f09b5af6 Mon Sep 17 00:00:00 2001 From: Sophie Date: Thu, 12 Oct 2023 20:23:37 -0700 Subject: [PATCH 16/25] Unit tests --- sway-core/src/language/call_path.rs | 2 +- .../code_actions/diagnostic/auto_import.rs | 428 ++++++++++++++---- sway-lsp/src/traverse/lexed_tree.rs | 3 - .../tests/fixtures/tokens/paths/src/main.sw | 4 +- sway-lsp/tests/integration/code_actions.rs | 2 + 5 files changed, 344 insertions(+), 95 deletions(-) diff --git a/sway-core/src/language/call_path.rs b/sway-core/src/language/call_path.rs index 43cf8d1fb20..2225eaf0215 100644 --- a/sway-core/src/language/call_path.rs +++ b/sway-core/src/language/call_path.rs @@ -18,7 +18,7 @@ pub struct CallPath { pub suffix: T, // If `is_absolute` is true, then this call path is an absolute path from // the project root namespace. If not, then it is relative to the current namespace. - pub(crate) is_absolute: bool, + pub is_absolute: bool, } impl std::convert::From for CallPath { diff --git a/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs b/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs index 38f7cc6e0ee..3be7dad67af 100644 --- a/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs +++ b/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs @@ -1,4 +1,4 @@ -use std::{cmp::Ordering, collections::HashMap}; +use super::CODE_ACTION_IMPORT_TITLE; use crate::{ capabilities::{code_actions::CodeActionContext, diagnostic::DiagnosticData}, core::token::{get_range_from_span, AstToken, SymbolKind, TypedAstToken}, @@ -8,13 +8,13 @@ use lsp_types::{ WorkspaceEdit, }; use serde_json::Value; +use std::{cmp::Ordering, collections::HashMap, iter}; use sway_core::language::{ parsed::ImportType, ty::{TyDecl, TyIncludeStatement, TyUseStatement}, CallPath, }; use sway_types::{Ident, Spanned}; -use super::CODE_ACTION_IMPORT_TITLE; /// Returns a list of [CodeActionOrCommand] suggestions for inserting a missing import. pub(crate) fn import_code_action( @@ -46,26 +46,15 @@ pub(crate) fn import_code_action( } }); + // Create a list of code actions, one for each potential call path. let actions = call_paths .map(|call_path| { - // To determine where to insert the import statement in the file, we try these options and do - // one of the following, based on the contents of the file. - // - // 1. Add the import to an existing import that has the same prefix. - // 2. Insert the import on a new line relative to existing use statements. - // 3. Insert the import on a new line after existing mod statements. - // 4. Insert the import on a new line after the program type statement (e.g. `contract;`) - // 5. If all else fails, insert it at the beginning of the file. - - // TODO: combine into 1 function and write unit test for location in the file - let text_edit: TextEdit = get_text_edit_for_group(&call_path, &use_statements) - .or_else(|| get_text_edit_in_use_block(&call_path, &use_statements)) - .unwrap_or(get_text_edit_fallback( - &call_path, - &include_statements, - &program_type_keyword, - )); - + let text_edit = get_text_edit( + &call_path, + &use_statements, + &include_statements, + &program_type_keyword, + ); let changes = HashMap::from([(ctx.uri.clone(), vec![text_edit])]); CodeActionOrCommand::CodeAction(LspCodeAction { @@ -88,97 +77,138 @@ pub(crate) fn import_code_action( None } -/// Returns an [Iterator] of [CallPath]s that match the given symbol name. The [CallPath]s are sorted +/// Returns an [Iterator] of [CallPath]s that match the given symbol name. The [CallPath]s are sorted /// alphabetically. fn get_call_paths_for_name<'s>( ctx: &'s CodeActionContext, symbol_name: &'s String, ) -> Option> { let namespace = ctx.namespace.to_owned()?; - let mut call_paths = - ctx.tokens - .tokens_for_name(symbol_name) - .filter_map(move |(_, token)| { - // If the typed token is a declaration, then we can import it. - match token.typed.as_ref() { - Some(TypedAstToken::TypedDeclaration(ty_decl)) => { - return match ty_decl { - TyDecl::StructDecl(decl) => { - let struct_decl = ctx.engines.de().get_struct(&decl.decl_id); - let call_path = struct_decl.call_path.to_import_path(&namespace); - Some(call_path) - } - TyDecl::EnumDecl(decl) => { - let enum_decl = ctx.engines.de().get_enum(&decl.decl_id); - let call_path = enum_decl.call_path.to_import_path(&namespace); - Some(call_path) - } - TyDecl::TraitDecl(decl) => { - let trait_decl = ctx.engines.de().get_trait(&decl.decl_id); - let call_path = trait_decl.call_path.to_import_path(&namespace); - Some(call_path) - } - _ => None, - }; - } - Some(TypedAstToken::TypedFunctionDeclaration(ty_decl)) => { - let call_path = ty_decl.call_path.to_import_path(&namespace); - Some(call_path) - } - Some(TypedAstToken::TypedConstantDeclaration(ty_decl)) => { - let call_path = ty_decl.call_path.to_import_path(&namespace); - Some(call_path) - } - Some(TypedAstToken::TypedTypeAliasDeclaration(ty_decl)) => { - let call_path = ty_decl.call_path.to_import_path(&namespace); - Some(call_path) - } - _ => None, + let mut call_paths = ctx + .tokens + .tokens_for_name(symbol_name) + .filter_map(move |(_, token)| { + // If the typed token is a declaration, then we can import it. + match token.typed.as_ref() { + Some(TypedAstToken::TypedDeclaration(ty_decl)) => { + return match ty_decl { + TyDecl::StructDecl(decl) => { + let struct_decl = ctx.engines.de().get_struct(&decl.decl_id); + let call_path = struct_decl.call_path.to_import_path(&namespace); + Some(call_path) + } + TyDecl::EnumDecl(decl) => { + let enum_decl = ctx.engines.de().get_enum(&decl.decl_id); + let call_path = enum_decl.call_path.to_import_path(&namespace); + Some(call_path) + } + TyDecl::TraitDecl(decl) => { + let trait_decl = ctx.engines.de().get_trait(&decl.decl_id); + let call_path = trait_decl.call_path.to_import_path(&namespace); + Some(call_path) + } + _ => None, + }; + } + Some(TypedAstToken::TypedFunctionDeclaration(ty_decl)) => { + let call_path = ty_decl.call_path.to_import_path(&namespace); + Some(call_path) + } + Some(TypedAstToken::TypedConstantDeclaration(ty_decl)) => { + let call_path = ty_decl.call_path.to_import_path(&namespace); + Some(call_path) + } + Some(TypedAstToken::TypedTypeAliasDeclaration(ty_decl)) => { + let call_path = ty_decl.call_path.to_import_path(&namespace); + Some(call_path) } - }).collect::>(); + _ => None, + } + }) + .collect::>(); call_paths.sort(); Some(call_paths.into_iter()) - } -/// If there is an existing [TyUseStatement] with the same prefix as the given [CallPath], returns a -/// [TextEdit] that inserts the call path into the existing statement. Otherwise, returns [None]. +/// Returns a [TextEdit] to insert an import statement for the given [CallPath] in the appropriate location in the file. +/// +/// To determine where to insert the import statement in the file, we try these options and do +/// one of the following, based on the contents of the file. +/// +/// 1. Add the import to an existing import that has the same prefix. +/// 2. Insert the import on a new line relative to existing use statements. +/// 3. Insert the import on a new line after existing mod statements. +/// 4. Insert the import on a new line after the program type statement (e.g. `contract;`) +/// 5. If all else fails, insert it at the beginning of the file. +fn get_text_edit( + call_path: &CallPath, + use_statements: &[TyUseStatement], + include_statements: &[TyIncludeStatement], + program_type_keyword: &Option, +) -> TextEdit { + get_text_edit_for_group(call_path, use_statements) + .or_else(|| get_text_edit_in_use_block(call_path, use_statements)) + .unwrap_or(get_text_edit_fallback( + call_path, + include_statements, + program_type_keyword, + )) +} + +/// Returns a [TextEdit] that inserts the call path into the existing statement if there is an +/// existing [TyUseStatement] with the same prefix as the given [CallPath]. Otherwise, returns [None]. fn get_text_edit_for_group( call_path: &CallPath, use_statements: &[TyUseStatement], ) -> Option { - let group_statement = use_statements.iter().find(|use_stmt| { + let group_statements = use_statements.iter().filter(|use_stmt| { call_path .prefixes .iter() .zip(use_stmt.call_path.iter()) .all(|(prefix, stmt_prefix)| prefix.as_str() == stmt_prefix.as_str()) - })?; + }); - let prefix_string = group_statement - .call_path - .iter() - .map(|path| path.as_str()) - .collect::>() - .join("::"); - let statement_suffix_string = { - let name = match &group_statement.import_type { - ImportType::Star => "*".to_string(), - ImportType::SelfImport(_) => "self".to_string(), - ImportType::Item(ident) => ident.to_string(), - }; - match &group_statement.alias { - Some(alias) => format!("{} as {}", name, alias), - None => name, - } - }; - let mut suffixes = [statement_suffix_string, call_path.suffix.to_string()]; - suffixes.sort(); // TODO: test this. Is there a better way to sort? - let suffix_string = suffixes.join(", "); + let mut group_statement_span = None; + let mut suffixes = group_statements + .filter_map(|stmt| { + // Set the group statement span if it hasn't been set yet. If it has been set, filter out + // any statements that aren't part of the same import group. + if group_statement_span.is_none() { + group_statement_span = Some(stmt.span()); + } else if group_statement_span != Some(stmt.span()) { + return None; + } - Some(TextEdit { - range: get_range_from_span(&group_statement.span()), - new_text: format!("use {}::{{{}}};\n", prefix_string, suffix_string), + let name = match &stmt.import_type { + ImportType::Star => "*".to_string(), + ImportType::SelfImport(_) => "self".to_string(), + ImportType::Item(ident) => ident.to_string(), + }; + match &stmt.alias { + Some(alias) => Some(format!("{} as {}", name, alias)), + None => Some(name), + } + }) + .chain(iter::once(call_path.suffix.to_string())) + .collect::>(); + + // If there were no imports with the same prefix, return None. Otherwise, build the text edit response. + group_statement_span.map(|span| { + suffixes.sort(); + let suffix_string = suffixes.join(", "); + + let prefix_string = call_path + .prefixes + .iter() + .map(|ident| ident.as_str()) + .collect::>() + .join("::"); + + TextEdit { + range: get_range_from_span(&span.clone()), + new_text: format!("use {}::{{{}}};\n", prefix_string, suffix_string), + } }) } @@ -243,3 +273,223 @@ fn get_text_edit_fallback( new_text: format!("\nuse {};\n", call_path), } } + +#[cfg(test)] +mod tests { + use sway_core::language::Visibility; + use sway_types::Span; + + use super::*; + + fn assert_text_edit(text_edit: TextEdit, expected_range: Range, expected_text: String) { + assert_eq!(text_edit.range, expected_range); + assert_eq!(text_edit.new_text, expected_text); + } + + fn get_mock_call_path(prefixes: Vec<&str>, suffix: &str) -> CallPath { + CallPath { + prefixes: get_mock_prefixes(prefixes), + suffix: Ident::new_no_span(suffix.to_string()), + is_absolute: false, + } + } + + fn get_mock_prefixes(prefixes: Vec<&str>) -> Vec { + prefixes + .into_iter() + .map(|p| Ident::new(Span::from_string(p.into()))) + .collect() + } + + fn get_prefixes_from_src(src: &str, prefixes: Vec<&str>) -> Vec { + prefixes + .into_iter() + .filter_map(|p| get_ident_from_src(src, p)) + .collect() + } + + fn get_span_from_src(src: &str, text: &str) -> Option { + let start = src.find(text)?; + let end = start + text.len(); + Span::new(src.into(), start, end, None) + } + + fn get_ident_from_src(src: &str, name: &str) -> Option { + let span = get_span_from_src(src, name)?; + Some(Ident::new(span)) + } + + fn get_use_stmt_from_src( + src: &str, + prefixes: Vec<&str>, + import_type: ImportType, + text: &str, + ) -> TyUseStatement { + TyUseStatement { + call_path: get_prefixes_from_src(src, prefixes), + span: get_span_from_src(src, text).unwrap(), + import_type, + is_absolute: false, + alias: None, + } + } + + fn get_incl_stmt_from_src(src: &str, mod_name: &str, text: &str) -> TyIncludeStatement { + TyIncludeStatement { + span: get_span_from_src(src, text).unwrap(), + mod_name: get_ident_from_src(src, mod_name).unwrap(), + visibility: Visibility::Private, + } + } + + #[test] + fn get_text_edit_existing_import() { + let src = r#"contract; + +use a:b:C; +use b:c:*; +"#; + let new_call_path = get_mock_call_path(vec!["a", "b"], "D"); + let use_statements = vec![ + get_use_stmt_from_src( + src, + Vec::from(["a", "b"]), + ImportType::Item(get_ident_from_src(src, "C").unwrap()), + "use a:b:C;", + ), + get_use_stmt_from_src(src, Vec::from(["b", "c"]), ImportType::Star, "use b:c:*;"), + ]; + + let include_statements = vec![]; + let program_type_keyword = get_ident_from_src(src, "contract"); + + let expected_range = Range::new(Position::new(2, 0), Position::new(2, 10)); + let expected_text = "use a::b::{C, D};\n".into(); + + let text_edit = get_text_edit( + &new_call_path, + &use_statements, + &include_statements, + &program_type_keyword, + ); + assert_text_edit(text_edit, expected_range, expected_text); + } + + #[test] + fn get_text_edit_new_import() { + let src = r#"predicate; + +use b:c:*; +"#; + let new_call_path = get_mock_call_path(vec!["a", "b"], "C"); + let use_statements = vec![get_use_stmt_from_src( + src, + Vec::from(["b", "c"]), + ImportType::Star, + "use b:c:*;", + )]; + + let include_statements = vec![]; + let program_type_keyword = get_ident_from_src(src, "predicate"); + + let expected_range = Range::new(Position::new(2, 0), Position::new(2, 0)); + let expected_text = "use a::b::C;\n".into(); + + let text_edit = get_text_edit( + &new_call_path, + &use_statements, + &include_statements, + &program_type_keyword, + ); + assert_text_edit(text_edit, expected_range, expected_text); + } + + #[test] + fn get_text_edit_existing_group_import() { + let src = r#"contract; + +use b:c:{D, F}; +"#; + let new_call_path = get_mock_call_path(vec!["b", "c"], "E"); + let use_statements = vec![ + get_use_stmt_from_src( + src, + Vec::from(["b", "c"]), + ImportType::Item(get_ident_from_src(src, "D").unwrap()), + "use b:c:{D, F};", + ), + get_use_stmt_from_src( + src, + Vec::from(["b", "c"]), + ImportType::Item(get_ident_from_src(src, "F").unwrap()), + "use b:c:{D, F};", + ), + ]; + + let include_statements = vec![]; + let program_type_keyword = get_ident_from_src(src, "contract"); + + let expected_range = Range::new(Position::new(2, 0), Position::new(2, 15)); + let expected_text = "use b::c::{D, E, F};\n".into(); + + let text_edit = get_text_edit( + &new_call_path, + &use_statements, + &include_statements, + &program_type_keyword, + ); + assert_text_edit(text_edit, expected_range, expected_text); + } + + #[test] + fn get_text_edit_after_mod() { + let src = r#"library; + +mod my_module; +pub mod zz_module; +"#; + let new_call_path = get_mock_call_path(vec!["b", "c"], "D"); + let use_statements = vec![]; + + let include_statements = vec![ + get_incl_stmt_from_src(src, "my_module", "mod my_module;"), + get_incl_stmt_from_src(src, "zz_module", "pub mod zz_module"), + ]; + let program_type_keyword = get_ident_from_src(src, "library"); + + let expected_range = Range::new(Position::new(4, 0), Position::new(4, 0)); + let expected_text = "\nuse b::c::D;\n".into(); + + let text_edit = get_text_edit( + &new_call_path, + &use_statements, + &include_statements, + &program_type_keyword, + ); + assert_text_edit(text_edit, expected_range, expected_text); + } + + #[test] + fn get_text_edit_after_program() { + let src = r#"script; + +const HI: u8 = 0; +"#; + let new_call_path = get_mock_call_path(vec!["b", "c"], "D"); + let use_statements = vec![]; + + let include_statements = vec![]; + let program_type_keyword = get_ident_from_src(src, "script"); + + let expected_range = Range::new(Position::new(1, 0), Position::new(1, 0)); + let expected_text = "\nuse b::c::D;\n".into(); + + let text_edit = get_text_edit( + &new_call_path, + &use_statements, + &include_statements, + &program_type_keyword, + ); + assert_text_edit(text_edit, expected_range, expected_text); + } +} diff --git a/sway-lsp/src/traverse/lexed_tree.rs b/sway-lsp/src/traverse/lexed_tree.rs index 5dfd0f90b16..b4ec5328c12 100644 --- a/sway-lsp/src/traverse/lexed_tree.rs +++ b/sway-lsp/src/traverse/lexed_tree.rs @@ -75,9 +75,6 @@ impl Parse for ItemKind { insert_keyword(ctx, submod.mod_token.span()); } ItemKind::Use(item_use) => { - // TODO: store the item use in the token map - // as well as include (submodule) - // use it to find the end of the use statement block item_use.parse(ctx); } ItemKind::Struct(item_struct) => { diff --git a/sway-lsp/tests/fixtures/tokens/paths/src/main.sw b/sway-lsp/tests/fixtures/tokens/paths/src/main.sw index d2170afee55..492e076f92a 100644 --- a/sway-lsp/tests/fixtures/tokens/paths/src/main.sw +++ b/sway-lsp/tests/fixtures/tokens/paths/src/main.sw @@ -4,7 +4,7 @@ mod test_mod; mod deep_mod; use test_mod::A; -use deep_mod::deeper_mod::deep_fun as dfun; +use deep_mod::deeper_mod::{deep_fun, DeepEnum}; use std::constants::{self, ZERO_B256}; pub fn fun() { @@ -31,5 +31,5 @@ pub fn fun() { let _ = deep_mod::deeper_mod::DeepEnum::Number(0); let _ = ::deep_mod::deeper_mod::DeepStruct:: { field: 0 }; - let _ = deep_mod::deeper_mod::DeepStruct:: { field: 0 }; + let _ = DeepStruct:: { field: 0 }; } diff --git a/sway-lsp/tests/integration/code_actions.rs b/sway-lsp/tests/integration/code_actions.rs index 63e23d79d54..6cc40728cdf 100644 --- a/sway-lsp/tests/integration/code_actions.rs +++ b/sway-lsp/tests/integration/code_actions.rs @@ -546,6 +546,8 @@ pub(crate) fn code_action_auto_import_struct_request(server: &ServerState, uri: } pub(crate) fn code_action_auto_import_enum_request(server: &ServerState, uri: &Url) { + // TODO: Add a test for an enum variant when https://github.com/FuelLabs/sway/issues/5188 is fixed. + // AuthError: external library let range = Range { start: Position { From c15d514740cc1e1625b821d01e8bbd67437bdbfe Mon Sep 17 00:00:00 2001 From: Sophie Date: Thu, 12 Oct 2023 21:12:37 -0700 Subject: [PATCH 17/25] Update comments --- sway-lsp/tests/fixtures/auto_import/src/main.sw | 6 +++--- sway-lsp/tests/integration/code_actions.rs | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/sway-lsp/tests/fixtures/auto_import/src/main.sw b/sway-lsp/tests/fixtures/auto_import/src/main.sw index ac5eb43f871..a46fe3a4beb 100644 --- a/sway-lsp/tests/fixtures/auto_import/src/main.sw +++ b/sway-lsp/tests/fixtures/auto_import/src/main.sw @@ -14,13 +14,13 @@ pub fn fun() { deep_fun(); A::fun(); - let a: DeepEnum = DeepEnum::Variant; // TODO: open an issue for variants + let a: DeepEnum = DeepEnum::Variant; let _ = DeepStruct:: { field: 0 }; let _ = TEST_CONST; - let _ = ZERO_B256; // TODO: fix this + let _ = ZERO_B256; - let _ = overflow(); // TODO: fix this + let _ = overflow(); let _: Result = msg_sender(); } diff --git a/sway-lsp/tests/integration/code_actions.rs b/sway-lsp/tests/integration/code_actions.rs index 6cc40728cdf..e9af1475cc7 100644 --- a/sway-lsp/tests/integration/code_actions.rs +++ b/sway-lsp/tests/integration/code_actions.rs @@ -644,7 +644,8 @@ pub(crate) fn code_action_auto_import_enum_request(server: &ServerState, uri: &U } pub(crate) fn code_action_auto_import_function_request(server: &ServerState, uri: &Url) { - // TODO: external library, test with overflow + // TODO: external library, test with `overflow`` + // Tracking issue: https://github.com/FuelLabs/sway/issues/5191 // deep_fun: local library let range = Range { @@ -696,6 +697,7 @@ pub(crate) fn code_action_auto_import_function_request(server: &ServerState, uri pub(crate) fn code_action_auto_import_constant_request(server: &ServerState, uri: &Url) { // TODO: external library, test with ZERO_B256 + // Tracking issue: https://github.com/FuelLabs/sway/issues/5192 // TEST_CONST: import a constant from a local library let range = Range { From 406a0ad37b4148c7c29a21f82ca3630e393c363d Mon Sep 17 00:00:00 2001 From: Sophie Date: Thu, 12 Oct 2023 21:48:11 -0700 Subject: [PATCH 18/25] fix tests --- sway-lsp/Cargo.toml | 17 ++++++++++++++--- .../code_actions/diagnostic/auto_import.rs | 8 +++++++- .../tests/fixtures/tokens/paths/src/deep_mod.sw | 2 +- .../tokens/paths/src/deep_mod/deeper_mod.sw | 2 -- .../tests/fixtures/tokens/paths/src/main.sw | 4 ++-- sway-lsp/tests/integration/code_actions.rs | 8 ++++---- sway-lsp/tests/lib.rs | 4 ++-- sway-lsp/tests/utils/src/lib.rs | 4 ---- 8 files changed, 30 insertions(+), 19 deletions(-) diff --git a/sway-lsp/Cargo.toml b/sway-lsp/Cargo.toml index 76ac58caff9..017775fd4b8 100644 --- a/sway-lsp/Cargo.toml +++ b/sway-lsp/Cargo.toml @@ -35,7 +35,15 @@ swayfmt = { version = "0.46.1", path = "../swayfmt" } syn = { version = "1.0.73", features = ["full"] } tempfile = "3" thiserror = "1.0.30" -tokio = { version = "1.3", features = ["io-std", "io-util", "macros", "net", "rt-multi-thread", "sync", "time"] } +tokio = { version = "1.3", features = [ + "io-std", + "io-util", + "macros", + "net", + "rt-multi-thread", + "sync", + "time", +] } toml_edit = "0.19" tower-lsp = { version = "0.19", features = ["proposed"] } tracing = "0.1" @@ -45,11 +53,14 @@ urlencoding = "2.1.2" assert-json-diff = "2.0" criterion = "0.5" dirs = "4.0" -futures = { version = "0.3", default-features = false, features = ["std", "async-await"] } +futures = { version = "0.3", default-features = false, features = [ + "std", + "async-await", +] } sway-lsp-test-utils = { path = "tests/utils" } pretty_assertions = "1.4.0" tower = { version = "0.4.12", default-features = false, features = ["util"] } [[bench]] name = "bench_main" -harness = false \ No newline at end of file +harness = false diff --git a/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs b/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs index 3be7dad67af..359a76ff992 100644 --- a/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs +++ b/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs @@ -8,7 +8,11 @@ use lsp_types::{ WorkspaceEdit, }; use serde_json::Value; -use std::{cmp::Ordering, collections::HashMap, iter}; +use std::{ + cmp::Ordering, + collections::{BTreeSet, HashMap}, + iter, +}; use sway_core::language::{ parsed::ImportType, ty::{TyDecl, TyIncludeStatement, TyUseStatement}, @@ -191,6 +195,8 @@ fn get_text_edit_for_group( } }) .chain(iter::once(call_path.suffix.to_string())) + .collect::>() + .into_iter() .collect::>(); // If there were no imports with the same prefix, return None. Otherwise, build the text edit response. diff --git a/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod.sw b/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod.sw index ea7106afe89..874041a52a2 100644 --- a/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod.sw +++ b/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod.sw @@ -1,3 +1,3 @@ library; -pub mod deeper_mod; +mod deeper_mod; diff --git a/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod/deeper_mod.sw b/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod/deeper_mod.sw index cf104646e90..081e10146bf 100644 --- a/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod/deeper_mod.sw +++ b/sway-lsp/tests/fixtures/tokens/paths/src/deep_mod/deeper_mod.sw @@ -2,8 +2,6 @@ library; pub fn deep_fun(){} -pub const A: u32 = 0; - pub enum DeepEnum { Variant: (), Number: u32, diff --git a/sway-lsp/tests/fixtures/tokens/paths/src/main.sw b/sway-lsp/tests/fixtures/tokens/paths/src/main.sw index 492e076f92a..d2170afee55 100644 --- a/sway-lsp/tests/fixtures/tokens/paths/src/main.sw +++ b/sway-lsp/tests/fixtures/tokens/paths/src/main.sw @@ -4,7 +4,7 @@ mod test_mod; mod deep_mod; use test_mod::A; -use deep_mod::deeper_mod::{deep_fun, DeepEnum}; +use deep_mod::deeper_mod::deep_fun as dfun; use std::constants::{self, ZERO_B256}; pub fn fun() { @@ -31,5 +31,5 @@ pub fn fun() { let _ = deep_mod::deeper_mod::DeepEnum::Number(0); let _ = ::deep_mod::deeper_mod::DeepStruct:: { field: 0 }; - let _ = DeepStruct:: { field: 0 }; + let _ = deep_mod::deeper_mod::DeepStruct:: { field: 0 }; } diff --git a/sway-lsp/tests/integration/code_actions.rs b/sway-lsp/tests/integration/code_actions.rs index e9af1475cc7..4d3a2f4d7cc 100644 --- a/sway-lsp/tests/integration/code_actions.rs +++ b/sway-lsp/tests/integration/code_actions.rs @@ -751,11 +751,11 @@ pub(crate) fn code_action_auto_import_trait_request(server: &ServerState, uri: & // TryFrom: external library let range = Range { start: Position { - line: 33, + line: 34, character: 5, }, end: Position { - line: 33, + line: 34, character: 12, }, }; @@ -798,11 +798,11 @@ pub(crate) fn code_action_auto_import_trait_request(server: &ServerState, uri: & // DeepTrait: local library let range = Range { start: Position { - line: 29, + line: 30, character: 5, }, end: Position { - line: 29, + line: 30, character: 14, }, }; diff --git a/sway-lsp/tests/lib.rs b/sway-lsp/tests/lib.rs index 753640b5b4b..cfdf198db73 100644 --- a/sway-lsp/tests/lib.rs +++ b/sway-lsp/tests/lib.rs @@ -10,7 +10,7 @@ use sway_lsp::{ use sway_lsp_test_utils::{ assert_server_requests, dir_contains_forc_manifest, doc_comments_dir, e2e_language_dir, e2e_test_dir, generic_impl_self_dir, get_fixture, load_sway_example, runnables_test_dir, - self_impl_reassignment_dir, sway_workspace_dir, test_fixtures_dir, paths_dir, + self_impl_reassignment_dir, sway_workspace_dir, test_fixtures_dir, }; use tower_lsp::LspService; @@ -405,7 +405,7 @@ async fn go_to_definition_for_paths() { let server = ServerState::default(); let uri = open( &server, - paths_dir().join("src/main.sw"), + test_fixtures_dir().join("tokens/paths/src/main.sw"), ) .await; diff --git a/sway-lsp/tests/utils/src/lib.rs b/sway-lsp/tests/utils/src/lib.rs index 5fb1f6f8c2d..a4b6720b6a9 100644 --- a/sway-lsp/tests/utils/src/lib.rs +++ b/sway-lsp/tests/utils/src/lib.rs @@ -63,10 +63,6 @@ pub fn self_impl_reassignment_dir() -> PathBuf { .join("self_impl_reassignment") } -pub fn paths_dir() -> PathBuf { - test_fixtures_dir().join("tokens/paths") -} - pub fn get_absolute_path(path: &str) -> String { sway_workspace_dir().join(path).to_str().unwrap().into() } From aa7a0bb5b4fd9dfcd9f5061ac340f8d29aaec944 Mon Sep 17 00:00:00 2001 From: Sophie Date: Thu, 12 Oct 2023 22:11:23 -0700 Subject: [PATCH 19/25] toml sorting --- sway-lsp/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sway-lsp/Cargo.toml b/sway-lsp/Cargo.toml index 017775fd4b8..61bf5a0ed1d 100644 --- a/sway-lsp/Cargo.toml +++ b/sway-lsp/Cargo.toml @@ -57,8 +57,8 @@ futures = { version = "0.3", default-features = false, features = [ "std", "async-await", ] } -sway-lsp-test-utils = { path = "tests/utils" } pretty_assertions = "1.4.0" +sway-lsp-test-utils = { path = "tests/utils" } tower = { version = "0.4.12", default-features = false, features = ["util"] } [[bench]] From b2cf178dac760cd2a58750e66d5fcc7bf7ecec1e Mon Sep 17 00:00:00 2001 From: Sophie Date: Fri, 13 Oct 2023 14:29:03 -0700 Subject: [PATCH 20/25] feedback --- .../src/semantic_analysis/ast_node/declaration/trait.rs | 2 +- .../src/transform/to_parsed_lang/convert_parse_tree.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs b/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs index 98f9ebb6860..3ef14472ca6 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs @@ -218,7 +218,7 @@ impl TyTraitDecl { supertraits, visibility, attributes, - call_path: CallPath::from(name.clone()).to_fullpath(ctx.namespace), + call_path: CallPath::from(name).to_fullpath(ctx.namespace), span, }; Ok(typed_trait_decl) diff --git a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs index 3726de3361b..8fdd686193b 100644 --- a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs +++ b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs @@ -283,7 +283,7 @@ fn use_tree_to_use_statements( }; ret.push(UseStatement { call_path: path.clone(), - span: item_span.clone(), + span: item_span, import_type, is_absolute, alias: None, @@ -297,7 +297,7 @@ fn use_tree_to_use_statements( }; ret.push(UseStatement { call_path: path.clone(), - span: item_span.clone(), + span: item_span, import_type, is_absolute, alias: Some(alias), @@ -306,7 +306,7 @@ fn use_tree_to_use_statements( UseTree::Glob { .. } => { ret.push(UseStatement { call_path: path.clone(), - span: item_span.clone(), + span: item_span, import_type: ImportType::Star, is_absolute, alias: None, @@ -314,7 +314,7 @@ fn use_tree_to_use_statements( } UseTree::Path { prefix, suffix, .. } => { path.push(prefix); - use_tree_to_use_statements(*suffix, is_absolute, path, ret, item_span.clone()); + use_tree_to_use_statements(*suffix, is_absolute, path, ret, item_span); path.pop().unwrap(); } UseTree::Error { .. } => { From 1351f442d0709777c266731ae2e32474d02977c9 Mon Sep 17 00:00:00 2001 From: Sophie Date: Mon, 16 Oct 2023 14:32:16 -0700 Subject: [PATCH 21/25] feedback --- .../src/language/ty/declaration/function.rs | 2 +- .../ast_node/declaration/function.rs | 8 +++---- .../code_actions/diagnostic/auto_import.rs | 23 ++++++++++--------- sway-types/src/ident.rs | 8 +++++++ 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/sway-core/src/language/ty/declaration/function.rs b/sway-core/src/language/ty/declaration/function.rs index ab5d310c857..75639051e9a 100644 --- a/sway-core/src/language/ty/declaration/function.rs +++ b/sway-core/src/language/ty/declaration/function.rs @@ -242,7 +242,7 @@ impl TyFunctionDecl { }, implementing_type: None, span, - call_path: CallPath::from(Ident::new_no_span("foo".into())), + call_path: CallPath::from(Ident::dummy()), attributes: Default::default(), is_contract_call: false, parameters: Default::default(), diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/function.rs b/sway-core/src/semantic_analysis/ast_node/declaration/function.rs index 6d19ba6c2d7..3ec068f3e23 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/function.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/function.rs @@ -299,12 +299,12 @@ fn test_function_selector_behavior() { let handler = Handler::default(); let decl = ty::TyFunctionDecl { purity: Default::default(), - name: Ident::new_no_span("foo".into()), + name: Ident::dummy(), implementing_type: None, body: ty::TyCodeBlock { contents: vec![] }, parameters: vec![], span: Span::dummy(), - call_path: CallPath::from(Ident::new_no_span("foo".into())), + call_path: CallPath::from(Ident::dummy()), attributes: Default::default(), return_type: TypeId::from(0).into(), type_parameters: vec![], @@ -327,7 +327,7 @@ fn test_function_selector_behavior() { body: ty::TyCodeBlock { contents: vec![] }, parameters: vec![ ty::TyFunctionParameter { - name: Ident::new_no_span("foo".into()), + name: Ident::dummy(), is_reference: false, is_mutable: false, mutability_span: Span::dummy(), @@ -358,7 +358,7 @@ fn test_function_selector_behavior() { }, ], span: Span::dummy(), - call_path: CallPath::from(Ident::new_no_span("foo".into())), + call_path: CallPath::from(Ident::dummy()), attributes: Default::default(), return_type: TypeId::from(0).into(), type_parameters: vec![], diff --git a/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs b/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs index 359a76ff992..9c60a15f26e 100644 --- a/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs +++ b/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs @@ -15,7 +15,7 @@ use std::{ }; use sway_core::language::{ parsed::ImportType, - ty::{TyDecl, TyIncludeStatement, TyUseStatement}, + ty::{TyDecl, TyIncludeStatement, TyUseStatement, TyFunctionDecl, TyConstantDecl, TyTypeAliasDecl}, CallPath, }; use sway_types::{Ident, Spanned}; @@ -114,16 +114,17 @@ fn get_call_paths_for_name<'s>( _ => None, }; } - Some(TypedAstToken::TypedFunctionDeclaration(ty_decl)) => { - let call_path = ty_decl.call_path.to_import_path(&namespace); - Some(call_path) - } - Some(TypedAstToken::TypedConstantDeclaration(ty_decl)) => { - let call_path = ty_decl.call_path.to_import_path(&namespace); - Some(call_path) - } - Some(TypedAstToken::TypedTypeAliasDeclaration(ty_decl)) => { - let call_path = ty_decl.call_path.to_import_path(&namespace); + Some(TypedAstToken::TypedFunctionDeclaration(TyFunctionDecl { + call_path, .. + })) + | Some(TypedAstToken::TypedConstantDeclaration(TyConstantDecl { + call_path, .. + })) + | Some(TypedAstToken::TypedTypeAliasDeclaration(TyTypeAliasDecl { + call_path, + .. + })) => { + let call_path = call_path.to_import_path(&namespace); Some(call_path) } _ => None, diff --git a/sway-types/src/ident.rs b/sway-types/src/ident.rs index 20b65b38579..1c33415768f 100644 --- a/sway-types/src/ident.rs +++ b/sway-types/src/ident.rs @@ -75,6 +75,14 @@ impl BaseIdent { is_raw_ident: false, } } + + pub fn dummy() -> Ident { + Ident { + name_override_opt: Some("foo".into()), + span: Span::dummy(), + is_raw_ident: false, + } + } } /// An [Ident] is an _identifier_ with a corresponding `span` from which it was derived. From 8110a561c3af999d1c832ac2f40139a0c76dca64 Mon Sep 17 00:00:00 2001 From: Sophie Date: Mon, 16 Oct 2023 15:57:32 -0700 Subject: [PATCH 22/25] cargo fmt --- .../src/capabilities/code_actions/diagnostic/auto_import.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs b/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs index 9c60a15f26e..1eeb87506f6 100644 --- a/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs +++ b/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs @@ -15,7 +15,9 @@ use std::{ }; use sway_core::language::{ parsed::ImportType, - ty::{TyDecl, TyIncludeStatement, TyUseStatement, TyFunctionDecl, TyConstantDecl, TyTypeAliasDecl}, + ty::{ + TyConstantDecl, TyDecl, TyFunctionDecl, TyIncludeStatement, TyTypeAliasDecl, TyUseStatement, + }, CallPath, }; use sway_types::{Ident, Spanned}; From 2d8e1fdeb117081c0da4ca4ca38796570a2dfd82 Mon Sep 17 00:00:00 2001 From: Sophie Date: Tue, 17 Oct 2023 15:04:54 -0700 Subject: [PATCH 23/25] remove newline after group statement --- .../capabilities/code_actions/diagnostic/auto_import.rs | 8 ++++---- sway-lsp/tests/integration/code_actions.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs b/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs index 1eeb87506f6..76dcb48571a 100644 --- a/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs +++ b/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs @@ -206,7 +206,7 @@ fn get_text_edit_for_group( group_statement_span.map(|span| { suffixes.sort(); let suffix_string = suffixes.join(", "); - + let prefix_string = call_path .prefixes .iter() @@ -216,7 +216,7 @@ fn get_text_edit_for_group( TextEdit { range: get_range_from_span(&span.clone()), - new_text: format!("use {}::{{{}}};\n", prefix_string, suffix_string), + new_text: format!("use {}::{{{}}};", prefix_string, suffix_string), } }) } @@ -373,7 +373,7 @@ use b:c:*; let program_type_keyword = get_ident_from_src(src, "contract"); let expected_range = Range::new(Position::new(2, 0), Position::new(2, 10)); - let expected_text = "use a::b::{C, D};\n".into(); + let expected_text = "use a::b::{C, D};".into(); let text_edit = get_text_edit( &new_call_path, @@ -439,7 +439,7 @@ use b:c:{D, F}; let program_type_keyword = get_ident_from_src(src, "contract"); let expected_range = Range::new(Position::new(2, 0), Position::new(2, 15)); - let expected_text = "use b::c::{D, E, F};\n".into(); + let expected_text = "use b::c::{D, E, F};".into(); let text_edit = get_text_edit( &new_call_path, diff --git a/sway-lsp/tests/integration/code_actions.rs b/sway-lsp/tests/integration/code_actions.rs index 4d3a2f4d7cc..28d7574555f 100644 --- a/sway-lsp/tests/integration/code_actions.rs +++ b/sway-lsp/tests/integration/code_actions.rs @@ -733,7 +733,7 @@ pub(crate) fn code_action_auto_import_constant_request(server: &ServerState, uri character: 23, }, }, - "use test_mod::{TEST_CONST, test_fun};\n", + "use test_mod::{TEST_CONST, test_fun};", ); let expected = vec![create_code_action( uri.clone(), @@ -900,7 +900,7 @@ pub(crate) fn code_action_auto_import_alias_request(server: &ServerState, uri: & character: 23, }, }, - "use test_mod::{A, test_fun};\n", + "use test_mod::{A, test_fun};", ); expected.push(create_code_action( uri.clone(), From 9c26840336afe03759b09df9a96658378d35ac56 Mon Sep 17 00:00:00 2001 From: Sophie Date: Tue, 17 Oct 2023 15:08:29 -0700 Subject: [PATCH 24/25] cargo fmt --- sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs b/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs index 76dcb48571a..2ca67491a4e 100644 --- a/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs +++ b/sway-lsp/src/capabilities/code_actions/diagnostic/auto_import.rs @@ -206,7 +206,6 @@ fn get_text_edit_for_group( group_statement_span.map(|span| { suffixes.sort(); let suffix_string = suffixes.join(", "); - let prefix_string = call_path .prefixes .iter() From 4d846eaabd7351995b09e65cca6048fe538489ca Mon Sep 17 00:00:00 2001 From: Sophie Date: Thu, 19 Oct 2023 11:57:10 -0700 Subject: [PATCH 25/25] array instead of vec for submodules --- sway-core/src/language/lexed/mod.rs | 2 +- sway-core/src/language/module.rs | 2 +- sway-core/src/language/parsed/module.rs | 2 +- sway-core/src/language/ty/module.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sway-core/src/language/lexed/mod.rs b/sway-core/src/language/lexed/mod.rs index f3d4de99b79..4ca08a948a8 100644 --- a/sway-core/src/language/lexed/mod.rs +++ b/sway-core/src/language/lexed/mod.rs @@ -30,7 +30,7 @@ impl HasModule for LexedSubmodule { } impl HasSubmodules for LexedModule { - fn submodules(&self) -> &Vec<(ModName, LexedSubmodule)> { + fn submodules(&self) -> &[(ModName, LexedSubmodule)] { &self.submodules } } diff --git a/sway-core/src/language/module.rs b/sway-core/src/language/module.rs index 920838a0174..2649ddd416e 100644 --- a/sway-core/src/language/module.rs +++ b/sway-core/src/language/module.rs @@ -21,7 +21,7 @@ where Self: Sized, { /// Returns the submodules of this module. - fn submodules(&self) -> &Vec<(ModName, E)>; + fn submodules(&self) -> &[(ModName, E)]; /// An iterator yielding all submodules recursively, depth-first. fn submodules_recursive(&self) -> SubmodulesRecursive { diff --git a/sway-core/src/language/parsed/module.rs b/sway-core/src/language/parsed/module.rs index 6f2812c0473..41476852379 100644 --- a/sway-core/src/language/parsed/module.rs +++ b/sway-core/src/language/parsed/module.rs @@ -41,7 +41,7 @@ impl HasModule for ParseSubmodule { } impl HasSubmodules for ParseModule { - fn submodules(&self) -> &Vec<(ModName, ParseSubmodule)> { + fn submodules(&self) -> &[(ModName, ParseSubmodule)] { &self.submodules } } diff --git a/sway-core/src/language/ty/module.rs b/sway-core/src/language/ty/module.rs index 99fd672d6fd..6abcefe1c95 100644 --- a/sway-core/src/language/ty/module.rs +++ b/sway-core/src/language/ty/module.rs @@ -118,7 +118,7 @@ impl HasModule for TySubmodule { } impl HasSubmodules for TyModule { - fn submodules(&self) -> &Vec<(ModName, TySubmodule)> { + fn submodules(&self) -> &[(ModName, TySubmodule)] { &self.submodules } }