From f075b77ff406bd095d2702bddb582fe6ae7a21fc Mon Sep 17 00:00:00 2001 From: Pig Fang Date: Thu, 9 Jan 2025 15:46:35 +0800 Subject: [PATCH] feat(service): detect mutating on immutable global --- crates/service/src/checker/global_mut.rs | 75 +++++++++++++++++++ crates/service/src/checker/mod.rs | 10 +++ .../service/tests/diagnostics/global_mut.rs | 72 ++++++++++++++++++ crates/service/tests/diagnostics/mod.rs | 2 + ...s__diagnostics__global_mut__immutable.snap | 43 +++++++++++ ...tures__diagnostics__global_mut__undef.snap | 41 ++++++++++ 6 files changed, 243 insertions(+) create mode 100644 crates/service/src/checker/global_mut.rs create mode 100644 crates/service/tests/diagnostics/global_mut.rs create mode 100644 crates/service/tests/diagnostics/snapshots/features__diagnostics__global_mut__immutable.snap create mode 100644 crates/service/tests/diagnostics/snapshots/features__diagnostics__global_mut__undef.snap diff --git a/crates/service/src/checker/global_mut.rs b/crates/service/src/checker/global_mut.rs new file mode 100644 index 00000000..fc53b1b1 --- /dev/null +++ b/crates/service/src/checker/global_mut.rs @@ -0,0 +1,75 @@ +use crate::{ + binder::{SymbolItemKey, SymbolTable}, + files::FilesCtx, + helpers, InternUri, LanguageService, +}; +use line_index::LineIndex; +use lsp_types::{ + Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, Location, NumberOrString, +}; +use rowan::ast::AstNode; +use wat_syntax::{ + ast::{ModuleFieldGlobal, PlainInstr}, + SyntaxNode, +}; + +const DIAGNOSTIC_CODE: &str = "global-mutation"; + +pub fn check( + service: &LanguageService, + diags: &mut Vec, + uri: InternUri, + line_index: &LineIndex, + root: &SyntaxNode, + symbol_table: &SymbolTable, + node: &SyntaxNode, +) { + let Some(instr) = PlainInstr::cast(node.clone()) else { + return; + }; + match instr.instr_name() { + Some(name) if name.text() == "global.set" => {} + _ => return, + } + diags.extend(instr.immediates().filter_map(|immediate| { + let defs = symbol_table.find_defs(SymbolItemKey::new(immediate.syntax()))?; + let related_information = defs + .filter_map(|def| { + ModuleFieldGlobal::cast(def.key.to_node(root)) + .and_then(|global| global.global_type()) + .and_then(|global_type| { + if global_type.mut_keyword().is_none() { + Some(DiagnosticRelatedInformation { + location: Location { + uri: service.lookup_uri(uri), + range: helpers::rowan_range_to_lsp_range( + line_index, + global_type.syntax().text_range(), + ), + }, + message: "immutable global type".into(), + }) + } else { + None + } + }) + }) + .collect::>(); + if related_information.is_empty() { + None + } else { + Some(Diagnostic { + range: helpers::rowan_range_to_lsp_range( + line_index, + immediate.syntax().text_range(), + ), + severity: Some(DiagnosticSeverity::ERROR), + source: Some("wat".into()), + code: Some(NumberOrString::String(DIAGNOSTIC_CODE.into())), + message: "mutating an immutable global is not allowed".into(), + related_information: Some(related_information), + ..Default::default() + }) + } + })); +} diff --git a/crates/service/src/checker/mod.rs b/crates/service/src/checker/mod.rs index f818b00f..a52ae972 100644 --- a/crates/service/src/checker/mod.rs +++ b/crates/service/src/checker/mod.rs @@ -3,6 +3,7 @@ use lsp_types::Diagnostic; use wat_syntax::{SyntaxKind, SyntaxNode}; mod dup_names; +mod global_mut; mod immediates; mod implicit_module; mod multi_modules; @@ -65,6 +66,15 @@ pub fn check(service: &LanguageService, uri: InternUri) -> Vec { SyntaxKind::PLAIN_INSTR => { unknown_instr::check(&mut diagnostics, &line_index, &node); immediates::check(&mut diagnostics, &line_index, &node); + global_mut::check( + service, + &mut diagnostics, + uri, + &line_index, + &root, + &symbol_table, + &node, + ); } _ => {} }); diff --git a/crates/service/tests/diagnostics/global_mut.rs b/crates/service/tests/diagnostics/global_mut.rs new file mode 100644 index 00000000..0cb24c28 --- /dev/null +++ b/crates/service/tests/diagnostics/global_mut.rs @@ -0,0 +1,72 @@ +use super::*; +use insta::assert_json_snapshot; +use lsp_types::Uri; +use wat_service::LanguageService; + +#[test] +fn global_get() { + let uri = "untitled:test".parse::().unwrap(); + let source = " +(module + (global i32 + i32.const 0) + (func (result i32) + (global.get 0))) +"; + let mut service = LanguageService::default(); + service.commit(uri.clone(), source.into()); + calm(&mut service, uri.clone()); + let response = service.pull_diagnostics(create_params(uri)); + assert!(pick_diagnostics(response).is_empty()); +} + +#[test] +fn undef() { + let uri = "untitled:test".parse::().unwrap(); + let source = " +(module + (func + (global.set 0))) +"; + let mut service = LanguageService::default(); + service.commit(uri.clone(), source.into()); + calm(&mut service, uri.clone()); + let response = service.pull_diagnostics(create_params(uri)); + assert_json_snapshot!(response); +} + +#[test] +fn mutable() { + let uri = "untitled:test".parse::().unwrap(); + let source = " +(module + (global (mut i32) + i32.const 0) + (func + (global.set 0 + (i32.const 0)))) +"; + let mut service = LanguageService::default(); + service.commit(uri.clone(), source.into()); + calm(&mut service, uri.clone()); + let response = service.pull_diagnostics(create_params(uri)); + assert!(pick_diagnostics(response).is_empty()); +} + +#[test] +fn immutable() { + let uri = "untitled:test".parse::().unwrap(); + let source = " +(module + (global i32 + i32.const 0) + (func + (global.set 0 + (i32.const 0)))) +"; + let mut service = LanguageService::default(); + service.commit(uri.clone(), source.into()); + calm(&mut service, uri.clone()); + let response = service.pull_diagnostics(create_params(uri)); + assert_json_snapshot!(response); +} diff --git a/crates/service/tests/diagnostics/mod.rs b/crates/service/tests/diagnostics/mod.rs index d3efb516..45473e4c 100644 --- a/crates/service/tests/diagnostics/mod.rs +++ b/crates/service/tests/diagnostics/mod.rs @@ -7,6 +7,8 @@ use wat_service::{LanguageService, LintLevel, Lints, ServiceConfig}; #[cfg(test)] mod dup_names; #[cfg(test)] +mod global_mut; +#[cfg(test)] mod immediates; #[cfg(test)] mod implicit_module; diff --git a/crates/service/tests/diagnostics/snapshots/features__diagnostics__global_mut__immutable.snap b/crates/service/tests/diagnostics/snapshots/features__diagnostics__global_mut__immutable.snap new file mode 100644 index 00000000..7856863c --- /dev/null +++ b/crates/service/tests/diagnostics/snapshots/features__diagnostics__global_mut__immutable.snap @@ -0,0 +1,43 @@ +--- +source: crates/service/tests/diagnostics/global_mut.rs +expression: response +--- +{ + "kind": "full", + "items": [ + { + "range": { + "start": { + "line": 5, + "character": 16 + }, + "end": { + "line": 5, + "character": 17 + } + }, + "severity": 1, + "code": "global-mutation", + "source": "wat", + "message": "mutating an immutable global is not allowed", + "relatedInformation": [ + { + "location": { + "uri": "untitled:test", + "range": { + "start": { + "line": 2, + "character": 10 + }, + "end": { + "line": 2, + "character": 13 + } + } + }, + "message": "immutable global type" + } + ] + } + ] +} diff --git a/crates/service/tests/diagnostics/snapshots/features__diagnostics__global_mut__undef.snap b/crates/service/tests/diagnostics/snapshots/features__diagnostics__global_mut__undef.snap new file mode 100644 index 00000000..ccfb6674 --- /dev/null +++ b/crates/service/tests/diagnostics/snapshots/features__diagnostics__global_mut__undef.snap @@ -0,0 +1,41 @@ +--- +source: crates/service/tests/diagnostics/global_mut.rs +expression: response +--- +{ + "kind": "full", + "items": [ + { + "range": { + "start": { + "line": 3, + "character": 4 + }, + "end": { + "line": 3, + "character": 18 + } + }, + "severity": 1, + "code": "type-check", + "source": "wat", + "message": "expected types [any], found []" + }, + { + "range": { + "start": { + "line": 3, + "character": 16 + }, + "end": { + "line": 3, + "character": 17 + } + }, + "severity": 1, + "code": "undef", + "source": "wat", + "message": "cannot find `0` in this scope" + } + ] +}