diff --git a/sway-core/src/language/parsed/declaration/storage.rs b/sway-core/src/language/parsed/declaration/storage.rs index e51a519eb1d..efc4b28aa09 100644 --- a/sway-core/src/language/parsed/declaration/storage.rs +++ b/sway-core/src/language/parsed/declaration/storage.rs @@ -8,6 +8,7 @@ pub struct StorageDeclaration { pub attributes: transform::AttributesMap, pub fields: Vec, pub span: Span, + pub storage_keyword: Ident, } /// An individual field in a storage declaration. diff --git a/sway-core/src/language/parsed/expression/mod.rs b/sway-core/src/language/parsed/expression/mod.rs index 7a51bb969ed..daa5db83c44 100644 --- a/sway-core/src/language/parsed/expression/mod.rs +++ b/sway-core/src/language/parsed/expression/mod.rs @@ -223,7 +223,7 @@ pub enum ExpressionKind { #[derive(Debug, Clone)] pub enum ReassignmentTarget { VariableExpression(Box), - StorageField(Vec), + StorageField(Span, Vec), } #[derive(Debug, Clone)] diff --git a/sway-core/src/language/ty/declaration/storage.rs b/sway-core/src/language/ty/declaration/storage.rs index 514437c2793..275cc93cb2c 100644 --- a/sway-core/src/language/ty/declaration/storage.rs +++ b/sway-core/src/language/ty/declaration/storage.rs @@ -13,12 +13,12 @@ pub struct TyStorageDeclaration { pub fields: Vec, pub span: Span, pub attributes: transform::AttributesMap, - name: Ident, + pub storage_keyword: Ident, } impl Named for TyStorageDeclaration { fn name(&self) -> &Ident { - &self.name + &self.storage_keyword } } @@ -37,7 +37,7 @@ impl HashWithEngines for TyStorageDeclaration { // reliable source of obj v. obj distinction span: _, attributes: _, - name: _, + storage_keyword: _, } = self; fields.hash(state, engines); } @@ -50,19 +50,6 @@ impl Spanned for TyStorageDeclaration { } impl TyStorageDeclaration { - pub fn new( - fields: Vec, - span: Span, - attributes: transform::AttributesMap, - ) -> Self { - TyStorageDeclaration { - name: Ident::new_with_override("storage".to_string(), span.clone()), - fields, - span, - attributes, - } - } - /// Given a field, find its type information in the declaration and return it. If the field has not /// been declared as a part of storage, return an error. pub fn apply_storage_load( diff --git a/sway-core/src/language/ty/expression/reassignment.rs b/sway-core/src/language/ty/expression/reassignment.rs index dadf6b0785d..5cf6a03443f 100644 --- a/sway-core/src/language/ty/expression/reassignment.rs +++ b/sway-core/src/language/ty/expression/reassignment.rs @@ -164,6 +164,7 @@ pub struct TyStorageReassignment { pub fields: Vec, pub(crate) ix: StateIndex, pub rhs: TyExpression, + pub storage_keyword_span: Span, } impl EqWithEngines for TyStorageReassignment {} @@ -177,7 +178,14 @@ impl PartialEqWithEngines for TyStorageReassignment { impl HashWithEngines for TyStorageReassignment { fn hash(&self, state: &mut H, engines: Engines<'_>) { - let TyStorageReassignment { fields, ix, rhs } = self; + let TyStorageReassignment { + fields, + ix, + rhs, + // these fields are not hashed because they aren't relevant/a + // reliable source of obj v. obj distinction + storage_keyword_span: _, + } = self; fields.hash(state, engines); ix.hash(state); rhs.hash(state, engines); 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 0a9bf578ed0..fe787606112 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/declaration.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/declaration.rs @@ -411,7 +411,7 @@ impl ty::TyDeclaration { span, fields, attributes, - .. + storage_keyword, }) => { let mut fields_buf = Vec::with_capacity(fields.len()); for parsed::StorageField { @@ -446,7 +446,12 @@ impl ty::TyDeclaration { attributes, }); } - let decl = ty::TyStorageDeclaration::new(fields_buf, span, attributes); + let decl = ty::TyStorageDeclaration { + fields: fields_buf, + span, + attributes, + storage_keyword, + }; let decl_ref = decl_engine.insert(decl); // insert the storage declaration into the symbols // if there already was one, return an error that duplicate storage diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs index 716e3a12dfe..85ec9d47bcf 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs @@ -1733,12 +1733,12 @@ impl ty::TyExpression { errors, ) } - ReassignmentTarget::StorageField(fields) => { + ReassignmentTarget::StorageField(storage_keyword_span, fields) => { let ctx = ctx .with_type_annotation(type_engine.insert(decl_engine, TypeInfo::Unknown)) .with_help_text(""); let reassignment = check!( - reassign_storage_subfield(ctx, fields, rhs, span.clone()), + reassign_storage_subfield(ctx, fields, rhs, span.clone(), storage_keyword_span), return err(warnings, errors), warnings, errors, diff --git a/sway-core/src/semantic_analysis/ast_node/mod.rs b/sway-core/src/semantic_analysis/ast_node/mod.rs index 4b4ed0c09e5..5b39527a28e 100644 --- a/sway-core/src/semantic_analysis/ast_node/mod.rs +++ b/sway-core/src/semantic_analysis/ast_node/mod.rs @@ -120,6 +120,7 @@ pub(crate) fn reassign_storage_subfield( fields: Vec, rhs: Expression, span: Span, + storage_keyword_span: Span, ) -> CompileResult { let mut errors = vec![]; let mut warnings = vec![]; @@ -219,6 +220,7 @@ pub(crate) fn reassign_storage_subfield( ok( ty::TyStorageReassignment { + storage_keyword_span, fields: type_checked_buf, ix, rhs, diff --git a/sway-core/src/semantic_analysis/namespace/items.rs b/sway-core/src/semantic_analysis/namespace/items.rs index 818cca24788..81793320db0 100644 --- a/sway-core/src/semantic_analysis/namespace/items.rs +++ b/sway-core/src/semantic_analysis/namespace/items.rs @@ -1,5 +1,10 @@ use crate::{ - decl_engine::*, engine_threading::Engines, error::*, language::ty, namespace::*, type_system::*, + decl_engine::*, + engine_threading::Engines, + error::*, + language::ty::{self, TyStorageDeclaration}, + namespace::*, + type_system::*, }; use super::TraitMap; @@ -169,17 +174,20 @@ impl Items { self.declared_storage.is_some() } + pub fn get_declared_storage(&self, decl_engine: &DeclEngine) -> Option { + self.declared_storage + .as_ref() + .map(|decl_ref| decl_engine.get_storage(decl_ref)) + } + pub(crate) fn get_storage_field_descriptors( &self, decl_engine: &DeclEngine, ) -> CompileResult> { let warnings = vec![]; let mut errors = vec![]; - match self.declared_storage { - Some(ref decl_ref) => { - let storage = decl_engine.get_storage(decl_ref); - ok(storage.fields, warnings, errors) - } + match self.get_declared_storage(decl_engine) { + Some(storage) => ok(storage.fields, warnings, errors), None => { let msg = "unknown source location"; let span = Span::new(Arc::from(msg), 0, msg.len(), None).unwrap(); 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 1012c4dc553..3c7d5f323fb 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 @@ -830,6 +830,7 @@ fn item_storage_to_storage_declaration( attributes, span, fields, + storage_keyword: item_storage.storage_token.into(), }; Ok(storage_declaration) } @@ -3529,7 +3530,7 @@ fn assignable_to_reassignment_target( Assignable::Var(name) => { if name.as_str() == "storage" { let idents = idents.into_iter().rev().cloned().collect(); - return Ok(ReassignmentTarget::StorageField(idents)); + return Ok(ReassignmentTarget::StorageField(name.span(), idents)); } break; } diff --git a/sway-lsp/src/capabilities/document_symbol.rs b/sway-lsp/src/capabilities/document_symbol.rs index ddf657344da..dd52eaf4b08 100644 --- a/sway-lsp/src/capabilities/document_symbol.rs +++ b/sway-lsp/src/capabilities/document_symbol.rs @@ -24,7 +24,7 @@ pub(crate) fn symbol_kind(symbol_kind: &SymbolKind) -> lsp_types::SymbolKind { SymbolKind::Function | SymbolKind::DeriveHelper => lsp_types::SymbolKind::FUNCTION, SymbolKind::Const => lsp_types::SymbolKind::CONSTANT, SymbolKind::Struct => lsp_types::SymbolKind::STRUCT, - SymbolKind::Trait => lsp_types::SymbolKind::INTERFACE, + SymbolKind::Trait | SymbolKind::Storage => lsp_types::SymbolKind::INTERFACE, SymbolKind::Module => lsp_types::SymbolKind::MODULE, SymbolKind::Enum => lsp_types::SymbolKind::ENUM, SymbolKind::Variant => lsp_types::SymbolKind::ENUM_MEMBER, diff --git a/sway-lsp/src/capabilities/semantic_tokens.rs b/sway-lsp/src/capabilities/semantic_tokens.rs index 785fa1f16ab..ca58964b236 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::Struct => SemanticTokenType::STRUCT, SymbolKind::Enum => SemanticTokenType::ENUM, SymbolKind::Variant => SemanticTokenType::ENUM_MEMBER, - SymbolKind::Trait => SemanticTokenType::INTERFACE, + SymbolKind::Trait | SymbolKind::Storage => SemanticTokenType::INTERFACE, SymbolKind::TypeParameter => SemanticTokenType::TYPE_PARAMETER, SymbolKind::Module => SemanticTokenType::NAMESPACE, SymbolKind::StringLiteral => SemanticTokenType::STRING, diff --git a/sway-lsp/src/core/token.rs b/sway-lsp/src/core/token.rs index 28d10d3fde7..f12fc574c1d 100644 --- a/sway-lsp/src/core/token.rs +++ b/sway-lsp/src/core/token.rs @@ -68,6 +68,7 @@ pub enum TypedAstToken { TypedTraitFn(ty::TyTraitFn), TypedSupertrait(Supertrait), TypedStorageField(ty::TyStorageField), + TyStorageResassignment(Box), TyStorageAccessDescriptor(ty::TyStorageAccessDescriptor), TypeCheckedStorageReassignDescriptor(ty::TyStorageReassignDescriptor), TypedReassignment(ty::TyReassignment), @@ -88,6 +89,7 @@ pub enum SymbolKind { Const, Struct, Trait, + Storage, Enum, Variant, BoolLiteral, diff --git a/sway-lsp/src/traverse/parsed_tree.rs b/sway-lsp/src/traverse/parsed_tree.rs index c248e616b26..2723398fb5e 100644 --- a/sway-lsp/src/traverse/parsed_tree.rs +++ b/sway-lsp/src/traverse/parsed_tree.rs @@ -320,7 +320,13 @@ impl Parse for ReassignmentExpression { ReassignmentTarget::VariableExpression(exp) => { exp.parse(ctx); } - ReassignmentTarget::StorageField(idents) => { + ReassignmentTarget::StorageField(storage_keyword_span, idents) => { + let storage_ident = Ident::new(storage_keyword_span.clone()); + ctx.tokens.insert( + to_ident_key(&storage_ident), + Token::from_parsed(AstToken::Ident(storage_ident), SymbolKind::Storage), + ); + for ident in idents { ctx.tokens.insert( to_ident_key(ident), diff --git a/sway-lsp/src/traverse/typed_tree.rs b/sway-lsp/src/traverse/typed_tree.rs index eaf0557777f..ddb7c7fc2b3 100644 --- a/sway-lsp/src/traverse/typed_tree.rs +++ b/sway-lsp/src/traverse/typed_tree.rs @@ -798,18 +798,91 @@ impl<'a> TypedTree<'a> { } } ty::TyExpressionVariant::StorageReassignment(storage_reassignment) => { - for field in &storage_reassignment.fields { + let type_engine = self.ctx.engines.te(); + let decl_engine = self.ctx.engines.de(); + + // collect storage keyword + if let Some(mut token) = self + .ctx + .tokens + .try_get_mut(&to_ident_key(&Ident::new( + storage_reassignment.storage_keyword_span.clone(), + ))) + .try_unwrap() + { + token.typed = Some(TypedAstToken::TyStorageResassignment( + storage_reassignment.clone(), + )); + if let Some(storage) = self.namespace.get_declared_storage(decl_engine) { + token.type_def = Some(TypeDefinition::Ident(storage.storage_keyword)); + } + } + + if let Some((head_field, tail_fields)) = storage_reassignment.fields.split_first() { + // collect the first ident as a field of the storage definition if let Some(mut token) = self .ctx .tokens - .try_get_mut(&to_ident_key(&field.name)) + .try_get_mut(&to_ident_key(&head_field.name)) .try_unwrap() { token.typed = Some(TypedAstToken::TypeCheckedStorageReassignDescriptor( - field.clone(), + head_field.clone(), )); + + if let Some(storage_field) = self + .namespace + .get_declared_storage(decl_engine) + .and_then(|storage| { + // find the corresponding field in the storage declaration + storage + .fields + .into_iter() + .find(|f| f.name.as_str() == head_field.name.as_str()) + }) + { + token.type_def = Some(TypeDefinition::Ident(storage_field.name)); + } + } + + // collect the rest of the idents as fields of their respective types + for (field, container_type_id) in tail_fields + .iter() + .zip(storage_reassignment.fields.iter().map(|f| f.type_id)) + { + if let Some(mut token) = self + .ctx + .tokens + .try_get_mut(&to_ident_key(&field.name)) + .try_unwrap() + { + token.typed = Some( + TypedAstToken::TypeCheckedStorageReassignDescriptor(field.clone()), + ); + + match type_engine.get(container_type_id) { + TypeInfo::Struct(decl_ref) => { + if let Some(field_name) = decl_engine + .get_struct(&decl_ref) + .fields + .iter() + .find(|struct_field| { + // find the corresponding field in the containing type declaration + struct_field.name.as_str() == field.name.as_str() + }) + .map(|struct_field| struct_field.name.clone()) + { + token.type_def = Some(TypeDefinition::Ident(field_name)); + } + } + _ => { + token.type_def = Some(TypeDefinition::TypeId(field.type_id)); + } + } + } } } + self.handle_expression(&storage_reassignment.rhs); } ty::TyExpressionVariant::Return(exp) => self.handle_expression(exp), diff --git a/sway-lsp/tests/fixtures/tokens/storage/.gitignore b/sway-lsp/tests/fixtures/tokens/storage/.gitignore new file mode 100644 index 00000000000..77d3844f58c --- /dev/null +++ b/sway-lsp/tests/fixtures/tokens/storage/.gitignore @@ -0,0 +1,2 @@ +out +target diff --git a/sway-lsp/tests/fixtures/tokens/storage/Forc.toml b/sway-lsp/tests/fixtures/tokens/storage/Forc.toml new file mode 100644 index 00000000000..bad2ce107c6 --- /dev/null +++ b/sway-lsp/tests/fixtures/tokens/storage/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "storage" + +[dependencies] +std = { path = "../../../../../sway-lib-std" } diff --git a/sway-lsp/tests/fixtures/tokens/storage/src/main.sw b/sway-lsp/tests/fixtures/tokens/storage/src/main.sw new file mode 100644 index 00000000000..c7369cdbe8e --- /dev/null +++ b/sway-lsp/tests/fixtures/tokens/storage/src/main.sw @@ -0,0 +1,29 @@ +contract; + +struct Type1 { + x: u64, + y: bool, + z: Type2, +} + +struct Type2 { + x: u64, +} + +storage { + var1: Type1 = Type1 { x:0, y: false, z: Type2 { x:0 } }, +} + +abi StorageExample { + #[storage(write)] + fn store_something(); +} + +impl StorageExample for Contract { + #[storage(write)] + fn store_something() { + storage.var1.x = 42; + storage.var1.y = true; + storage.var1.z.x = 1337; + } +} diff --git a/sway-lsp/tests/lib.rs b/sway-lsp/tests/lib.rs index 89a48bd6a79..1d8da7bda3c 100644 --- a/sway-lsp/tests/lib.rs +++ b/sway-lsp/tests/lib.rs @@ -905,6 +905,95 @@ async fn go_to_definition_for_traits() { shutdown_and_exit(&mut service).await; } +#[tokio::test] +async fn go_to_definition_for_storage() { + let (mut service, _) = LspService::new(Backend::new); + let uri = init_and_open( + &mut service, + test_fixtures_dir().join("tokens/storage/src/main.sw"), + ) + .await; + let mut i = 0..; + + let mut go_to = GotoDefinition { + req_uri: &uri, + req_line: 24, + req_char: 9, + def_line: 12, + def_start_char: 0, + def_end_char: 7, + def_path: "sway-lsp/tests/fixtures/tokens/storage/src/main.sw", + }; + // storage + let _ = lsp::definition_check(&mut service, &go_to, &mut i).await; + definition_check_with_req_offset(&mut service, &mut go_to, 25, 8, &mut i).await; + definition_check_with_req_offset(&mut service, &mut go_to, 26, 8, &mut i).await; + + let mut go_to = GotoDefinition { + req_uri: &uri, + req_line: 24, + req_char: 17, + def_line: 13, + def_start_char: 4, + def_end_char: 8, + def_path: "sway-lsp/tests/fixtures/tokens/storage/src/main.sw", + }; + // storage.var1 + let _ = lsp::definition_check(&mut service, &go_to, &mut i).await; + definition_check_with_req_offset(&mut service, &mut go_to, 25, 17, &mut i).await; + definition_check_with_req_offset(&mut service, &mut go_to, 26, 17, &mut i).await; + + let go_to = GotoDefinition { + req_uri: &uri, + req_line: 24, + req_char: 21, + def_line: 3, + def_start_char: 4, + def_end_char: 5, + def_path: "sway-lsp/tests/fixtures/tokens/storage/src/main.sw", + }; + // storage.var1.x + let _ = lsp::definition_check(&mut service, &go_to, &mut i).await; + + let go_to = GotoDefinition { + req_uri: &uri, + req_line: 25, + req_char: 21, + def_line: 4, + def_start_char: 4, + def_end_char: 5, + def_path: "sway-lsp/tests/fixtures/tokens/storage/src/main.sw", + }; + // storage.var1.y + let _ = lsp::definition_check(&mut service, &go_to, &mut i).await; + + let go_to = GotoDefinition { + req_uri: &uri, + req_line: 26, + req_char: 21, + def_line: 5, + def_start_char: 4, + def_end_char: 5, + def_path: "sway-lsp/tests/fixtures/tokens/storage/src/main.sw", + }; + // storage.var1.z + let _ = lsp::definition_check(&mut service, &go_to, &mut i).await; + + let go_to = GotoDefinition { + req_uri: &uri, + req_line: 26, + req_char: 23, + def_line: 9, + def_start_char: 4, + def_end_char: 5, + def_path: "sway-lsp/tests/fixtures/tokens/storage/src/main.sw", + }; + // storage.var1.z.x + let _ = lsp::definition_check(&mut service, &go_to, &mut i).await; + + shutdown_and_exit(&mut service).await; +} + #[tokio::test] async fn go_to_definition_for_variables() { let (mut service, _) = LspService::new(Backend::new);