diff --git a/src/bin/deltin-rs.rs b/src/bin/deltin-rs.rs index 3932f10..fb2033e 100644 --- a/src/bin/deltin-rs.rs +++ b/src/bin/deltin-rs.rs @@ -292,7 +292,8 @@ fn position_offset( } fn check_path_for_cli(path: &std::path::Path, json: bool) -> deltin_rs::api::CheckReport { - let run = || deltin_rs::api::check_path(path, &deltin_rs::semantic::provider::NoopProvider::new()); + let run = + || deltin_rs::api::check_path(path, &deltin_rs::semantic::provider::NoopProvider::new()); if json { without_debug_output(run) } else { diff --git a/src/hir/lower.rs b/src/hir/lower.rs index d4efa6f..92b1ebd 100644 --- a/src/hir/lower.rs +++ b/src/hir/lower.rs @@ -1,10 +1,10 @@ //! Lowering: SemanticProgram -> HirProgram (provenance-preserving). use crate::hir::*; -use crate::semantic::SemanticProgram; use crate::semantic::resolve::{BuiltinMember, Resolution}; use crate::semantic::symbols::{SymbolId, SymbolKind}; use crate::semantic::types::Type; +use crate::semantic::SemanticProgram; use crate::syntax::ast::*; use std::collections::HashMap; diff --git a/src/semantic/check.rs b/src/semantic/check.rs index cc8469a..a73b904 100644 --- a/src/semantic/check.rs +++ b/src/semantic/check.rs @@ -4,7 +4,7 @@ //! lives here alongside checking (documented simplification of the module //! layout in docs/architecture.md ยง5). -use crate::diagnostics::{Phase, error}; +use crate::diagnostics::{error, Phase}; use crate::semantic::provider::*; use crate::semantic::resolve::{BuiltinMember, Resolution}; use crate::semantic::symbols::*; diff --git a/src/semantic/provider.rs b/src/semantic/provider.rs index aef1919..e2f3564 100644 --- a/src/semantic/provider.rs +++ b/src/semantic/provider.rs @@ -4,8 +4,8 @@ //! adapter; canonical catalog data remains in `workshop-rs`. use crate::span::{FileId, Span}; -use workshop_rs::WorkshopError; use workshop_rs::catalog::{Catalog, CatalogEntry, Kind, Locale}; +use workshop_rs::WorkshopError; /// Position a query name is used in. #[derive(Clone, Copy, PartialEq, Eq, Debug)] diff --git a/src/workshop.rs b/src/workshop.rs index c2a6262..9bf5f66 100644 --- a/src/workshop.rs +++ b/src/workshop.rs @@ -4,16 +4,16 @@ //! event shapes, variable/action/value nodes, validation, and emission remain //! owned by `workshop-rs`. -use crate::diagnostics::{Diagnostic, Phase, error}; +use crate::diagnostics::{error, Diagnostic, Phase}; use crate::hir::{ CallTarget, HirArg, HirExprId, HirExprKind, HirFuncId, HirInterpPart, HirProgram, HirStmt, HirStmtKind, HirVarId, LiteralValue, StorageIntent, }; use crate::project::Project; -use crate::semantic::SemanticProgram; use crate::semantic::provider::{ExternalBinding, ExternalParam}; use crate::semantic::resolve::Resolution; use crate::semantic::types::Type; +use crate::semantic::SemanticProgram; use crate::span::{FileId, SourceMap, Span}; use crate::syntax::ast::{ self, AssignOp, BinaryOp, Expr, ExprKind, FuncBody, Item, ItemKind, Stmt, StmtKind, UnaryOp, @@ -470,7 +470,10 @@ impl<'a> Lowerer<'a> { else { self.unsupported( param.span, - format!("subroutine parameter '{}' has no HIR variable binding", param.name), + format!( + "subroutine parameter '{}' has no HIR variable binding", + param.name + ), ); continue; }; @@ -874,17 +877,25 @@ impl<'a> Lowerer<'a> { ); } if !valid { - self.unsupported(rule.span, "parameter-runtime subroutine calls require a global Workshop rule"); + self.unsupported( + rule.span, + "parameter-runtime subroutine calls require a global Workshop rule", + ); } for expr in calls { - let Some(HirExprKind::Call { target: CallTarget::Func(fid), .. }) = - self.hir.expr(expr).map(|expr| &expr.kind) + let Some(HirExprKind::Call { + target: CallTarget::Func(fid), + .. + }) = self.hir.expr(expr).map(|expr| &expr.kind) else { continue; }; let Some(func) = self.hir.funcs.get(*fid as usize).cloned() else { valid = false; - self.unsupported(rule.span, format!("call targets unknown HIR function {fid}")); + self.unsupported( + rule.span, + format!("call targets unknown HIR function {fid}"), + ); continue; }; valid &= self.validate_parameter_subroutine(&func); @@ -892,14 +903,20 @@ impl<'a> Lowerer<'a> { valid } - fn collect_direct_parameter_calls(&self, block: &crate::hir::HirBlock, calls: &mut Vec) { + fn collect_direct_parameter_calls( + &self, + block: &crate::hir::HirBlock, + calls: &mut Vec, + ) { for stmt in &block.stmts { match &stmt.kind { HirStmtKind::Block(inner) => self.collect_direct_parameter_calls(inner, calls), HirStmtKind::Expr(expr) if self.is_parameter_call(*expr) => calls.push(*expr), HirStmtKind::If { then, els, .. } => { if self.stmt_contains_parameter_call(then) - || els.as_deref().is_some_and(|stmt| self.stmt_contains_parameter_call(stmt)) + || els + .as_deref() + .is_some_and(|stmt| self.stmt_contains_parameter_call(stmt)) { // The caller rejects control-flow parameter calls below. } @@ -913,7 +930,11 @@ impl<'a> Lowerer<'a> { let _ = self.stmt_contains_parameter_call(body); } HirStmtKind::Switch { arms, .. } => { - let _ = arms.iter().any(|arm| arm.stmts.iter().any(|stmt| self.stmt_contains_parameter_call(stmt))); + let _ = arms.iter().any(|arm| { + arm.stmts + .iter() + .any(|stmt| self.stmt_contains_parameter_call(stmt)) + }); } _ => {} } @@ -923,23 +944,43 @@ impl<'a> Lowerer<'a> { fn stmt_contains_parameter_call(&self, stmt: &HirStmt) -> bool { match &stmt.kind { HirStmtKind::Expr(expr) => self.is_parameter_call(*expr), - HirStmtKind::Block(block) => block.stmts.iter().any(|stmt| self.stmt_contains_parameter_call(stmt)), - HirStmtKind::If { then, els, .. } => self.stmt_contains_parameter_call(then) - || els.as_deref().is_some_and(|stmt| self.stmt_contains_parameter_call(stmt)), + HirStmtKind::Block(block) => block + .stmts + .iter() + .any(|stmt| self.stmt_contains_parameter_call(stmt)), + HirStmtKind::If { then, els, .. } => { + self.stmt_contains_parameter_call(then) + || els + .as_deref() + .is_some_and(|stmt| self.stmt_contains_parameter_call(stmt)) + } HirStmtKind::While { body, .. } | HirStmtKind::AutoFor { body, .. } | HirStmtKind::Foreach { body, .. } => self.stmt_contains_parameter_call(body), - HirStmtKind::For { init, step, body, .. } => init.as_deref().is_some_and(|stmt| self.stmt_contains_parameter_call(stmt)) - || step.as_deref().is_some_and(|stmt| self.stmt_contains_parameter_call(stmt)) - || self.stmt_contains_parameter_call(body), - HirStmtKind::Switch { arms, .. } => arms.iter().any(|arm| arm.stmts.iter().any(|stmt| self.stmt_contains_parameter_call(stmt))), + HirStmtKind::For { + init, step, body, .. + } => { + init.as_deref() + .is_some_and(|stmt| self.stmt_contains_parameter_call(stmt)) + || step + .as_deref() + .is_some_and(|stmt| self.stmt_contains_parameter_call(stmt)) + || self.stmt_contains_parameter_call(body) + } + HirStmtKind::Switch { arms, .. } => arms.iter().any(|arm| { + arm.stmts + .iter() + .any(|stmt| self.stmt_contains_parameter_call(stmt)) + }), _ => false, } } fn is_parameter_call(&self, id: HirExprId) -> bool { - let Some(HirExprKind::Call { target: CallTarget::Func(fid), args }) = - self.hir.expr(id).map(|expr| &expr.kind) + let Some(HirExprKind::Call { + target: CallTarget::Func(fid), + args, + }) = self.hir.expr(id).map(|expr| &expr.kind) else { return false; }; @@ -955,15 +996,28 @@ impl<'a> Lowerer<'a> { block.stmts.iter().any(|stmt| match &stmt.kind { HirStmtKind::If { then, els, .. } => { self.stmt_contains_parameter_call(then) - || els.as_deref().is_some_and(|stmt| self.stmt_contains_parameter_call(stmt)) + || els + .as_deref() + .is_some_and(|stmt| self.stmt_contains_parameter_call(stmt)) } HirStmtKind::While { body, .. } | HirStmtKind::AutoFor { body, .. } | HirStmtKind::Foreach { body, .. } => self.stmt_contains_parameter_call(body), - HirStmtKind::For { init, step, body, .. } => init.as_deref().is_some_and(|stmt| self.stmt_contains_parameter_call(stmt)) - || step.as_deref().is_some_and(|stmt| self.stmt_contains_parameter_call(stmt)) - || self.stmt_contains_parameter_call(body), - HirStmtKind::Switch { arms, .. } => arms.iter().any(|arm| arm.stmts.iter().any(|stmt| self.stmt_contains_parameter_call(stmt))), + HirStmtKind::For { + init, step, body, .. + } => { + init.as_deref() + .is_some_and(|stmt| self.stmt_contains_parameter_call(stmt)) + || step + .as_deref() + .is_some_and(|stmt| self.stmt_contains_parameter_call(stmt)) + || self.stmt_contains_parameter_call(body) + } + HirStmtKind::Switch { arms, .. } => arms.iter().any(|arm| { + arm.stmts + .iter() + .any(|stmt| self.stmt_contains_parameter_call(stmt)) + }), HirStmtKind::Block(inner) => self.block_contains_non_direct_parameter_call(inner), _ => false, }) @@ -976,9 +1030,15 @@ impl<'a> Lowerer<'a> { && !func.is_player_context && func.params.iter().all(|param| { param.mode == crate::syntax::ast::ParamMode::Value - && matches!(param.ty, Type::Number | Type::String | Type::Bool | Type::Null) + && matches!( + param.ty, + Type::Number | Type::String | Type::Bool | Type::Null + ) }) - && func.body.as_ref().is_some_and(|body| !self.block_contains_internal_call(body)); + && func + .body + .as_ref() + .is_some_and(|body| !self.block_contains_internal_call(body)); if !valid { self.unsupported( func.span, @@ -1358,10 +1418,17 @@ impl<'a> Lowerer<'a> { return; } let name = format!("__del_rule_local_{var}"); - if self.out.global_variables.iter().any(|variable| variable.name == name) { + if self + .out + .global_variables + .iter() + .any(|variable| variable.name == name) + { self.unsupported( hir_var.span, - format!("synthetic rule-local global name '{name}' collides with a declared global"), + format!( + "synthetic rule-local global name '{name}' collides with a declared global" + ), ); return; } @@ -1465,21 +1532,19 @@ impl<'a> Lowerer<'a> { self.player_vars.get(var).copied(), ) { (Some(variable), _) => { - vec![ - self.out.actions.push(wir::Action::ForGlobalVariable { - variable, - start, - stop, - step, - body, - span: self.ws_span(stmt.span), - target_span: self - .hir - .vars - .get(*var as usize) - .and_then(|v| self.ws_span(v.span)), - }), - ] + vec![self.out.actions.push(wir::Action::ForGlobalVariable { + variable, + start, + stop, + step, + body, + span: self.ws_span(stmt.span), + target_span: self + .hir + .vars + .get(*var as usize) + .and_then(|v| self.ws_span(v.span)), + })] } (None, Some(variable)) => { let player = self.out.values.push(wir::ValueNode::new( @@ -1730,10 +1795,16 @@ impl<'a> Lowerer<'a> { "__del_foreach_collection_{}", self.out.global_variables.len() ); - let index_name = format!("__del_foreach_index_{}", self.out.global_variables.len() + 1); - if self.out.global_variables.iter().any(|variable| { - variable.name == collection_name || variable.name == index_name - }) { + let index_name = format!( + "__del_foreach_index_{}", + self.out.global_variables.len() + 1 + ); + if self + .out + .global_variables + .iter() + .any(|variable| variable.name == collection_name || variable.name == index_name) + { self.unsupported( span, "foreach synthetic global name collides with a declared global", @@ -2203,16 +2274,25 @@ impl<'a> Lowerer<'a> { return None; }; let Some(target) = Self::chase_target_arg(args, info.params.as_deref()) else { - self.unsupported(span, "chase target must be a resolved global or player variable"); + self.unsupported( + span, + "chase target must be a resolved global or player variable", + ); return None; }; let Some(HirExprKind::VarRef { var }) = self.hir.expr(target).map(|expr| &expr.kind) else { - self.unsupported(span, "chase target must be a resolved global or player variable"); + self.unsupported( + span, + "chase target must be a resolved global or player variable", + ); return None; }; let player = self.player_vars.contains_key(var); if !player && !self.global_vars.contains_key(var) { - self.unsupported(span, "chase target must be a resolved global or player variable"); + self.unsupported( + span, + "chase target must be a resolved global or player variable", + ); return None; } if player && matches!(name, "StopChasingVariable" | "StopChasingPlayerVariable") { @@ -2233,10 +2313,7 @@ impl<'a> Lowerer<'a> { Some(ExternalBinding::Action(info)) } - fn chase_target_arg( - args: &[HirArg], - params: Option<&[ExternalParam]>, - ) -> Option { + fn chase_target_arg(args: &[HirArg], params: Option<&[ExternalParam]>) -> Option { let params = params?; let target_index = params .iter() @@ -2339,8 +2416,13 @@ impl<'a> Lowerer<'a> { } fn expr_is_scalar_parameter(&self, id: HirExprId) -> bool { - let Some(expr) = self.hir.expr(id) else { return false }; - if !matches!(expr.ty, Type::Number | Type::String | Type::Bool | Type::Null) { + let Some(expr) = self.hir.expr(id) else { + return false; + }; + if !matches!( + expr.ty, + Type::Number | Type::String | Type::Bool | Type::Null + ) { return false; } match &expr.kind { @@ -2363,14 +2445,28 @@ impl<'a> Lowerer<'a> { } fn lower_direct_parameter_call(&mut self, id: HirExprId) -> Vec { - let Some(expr) = self.hir.expr(id).cloned() else { return Vec::new() }; - let HirExprKind::Call { target: CallTarget::Func(fid), args } = expr.kind else { - self.unsupported(expr.span, "parameter-runtime call is not a direct subroutine call"); + let Some(expr) = self.hir.expr(id).cloned() else { + return Vec::new(); + }; + let HirExprKind::Call { + target: CallTarget::Func(fid), + args, + } = expr.kind + else { + self.unsupported( + expr.span, + "parameter-runtime call is not a direct subroutine call", + ); + return Vec::new(); + }; + let Some(func) = self.hir.funcs.get(fid as usize).cloned() else { return Vec::new(); }; - let Some(func) = self.hir.funcs.get(fid as usize).cloned() else { return Vec::new() }; if !self.validate_parameter_subroutine(&func) || args.len() != func.params.len() { - self.unsupported(expr.span, "parameter-runtime subroutine arguments do not match the declaration"); + self.unsupported( + expr.span, + "parameter-runtime subroutine arguments do not match the declaration", + ); return Vec::new(); } let mut bound = vec![None; func.params.len()]; @@ -2379,32 +2475,64 @@ impl<'a> Lowerer<'a> { for arg in args { let (index, value) = match arg { HirArg::Pos(value) => { - while next < bound.len() && bound[next].is_some() { next += 1; } - if next >= bound.len() { return Vec::new(); } - let index = next; next += 1; (index, value) + while next < bound.len() && bound[next].is_some() { + next += 1; + } + if next >= bound.len() { + return Vec::new(); + } + let index = next; + next += 1; + (index, value) } HirArg::Named { name, value } => { - let Some(index) = func.params.iter().position(|param| param.name == name) else { - self.unsupported(expr.span, format!("unknown subroutine parameter '{name}'")); + let Some(index) = func.params.iter().position(|param| param.name == name) + else { + self.unsupported( + expr.span, + format!("unknown subroutine parameter '{name}'"), + ); return Vec::new(); }; (index, value) } }; - if bound[index].replace(value).is_some() { return Vec::new(); } + if bound[index].replace(value).is_some() { + return Vec::new(); + } source_order.push((index, value)); } - if bound.iter().any(Option::is_none) { return Vec::new(); } + if bound.iter().any(Option::is_none) { + return Vec::new(); + } let mut actions = Vec::with_capacity(source_order.len() + 1); for (index, value) in source_order { if !self.expr_is_scalar_parameter(value) { - self.unsupported(expr.span, "parameter-runtime arguments must be scalar, side-effect-free values"); + self.unsupported( + expr.span, + "parameter-runtime arguments must be scalar, side-effect-free values", + ); return Vec::new(); } - let Some(var) = self.hir.param_vars.get(&(fid, func.params[index].name.clone())).copied() else { return Vec::new() }; - let Some(variable) = self.parameter_slots.get(&var).copied() else { return Vec::new() }; - let Ok(value_node) = self.lower_value(value) else { return Vec::new() }; - let value_span = self.hir.expr(value).map(|value| value.span).unwrap_or(expr.span); + let Some(var) = self + .hir + .param_vars + .get(&(fid, func.params[index].name.clone())) + .copied() + else { + return Vec::new(); + }; + let Some(variable) = self.parameter_slots.get(&var).copied() else { + return Vec::new(); + }; + let Ok(value_node) = self.lower_value(value) else { + return Vec::new(); + }; + let value_span = self + .hir + .expr(value) + .map(|value| value.span) + .unwrap_or(expr.span); actions.push(self.out.actions.push(wir::Action::SetGlobalVariable { variable, value: value_node, diff --git a/tests/corpus.rs b/tests/corpus.rs index c89d510..af708dc 100644 --- a/tests/corpus.rs +++ b/tests/corpus.rs @@ -328,7 +328,10 @@ fn compatibility_report_classifies_evidence_and_gaps() { })); for case in &report.cases { if case.fixture.expect == deltin_rs::compatibility::ExpectedOutcome::Unknown { - assert_ne!(case.status, deltin_rs::compatibility::FixtureStatus::Matched); + assert_ne!( + case.status, + deltin_rs::compatibility::FixtureStatus::Matched + ); } } } diff --git a/tests/project.rs b/tests/project.rs index a2a20c3..97a2266 100644 --- a/tests/project.rs +++ b/tests/project.rs @@ -82,7 +82,10 @@ fn caller_config_takes_precedence_without_discovering_root_config() { }), }); assert!(project.diagnostics.is_empty(), "{:?}", project.diagnostics); - assert_eq!(project.sources.get(project.entry).name, PathBuf::from("src/lib.del")); + assert_eq!( + project.sources.get(project.entry).name, + PathBuf::from("src/lib.del") + ); assert!(project.sources.by_name(&PathBuf::from("ds.toml")).is_none()); } @@ -116,8 +119,8 @@ fn invalid_ds_toml_is_a_project_diagnostic_with_config_provenance() { #[test] fn unreadable_ds_toml_uses_a_registered_config_source() { - let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/project-fixtures/non-file-ds-toml"); + let root = + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/project-fixtures/non-file-ds-toml"); let project = load_project(ProjectOptions { root, entry: None, diff --git a/tests/semantic.rs b/tests/semantic.rs index b6571dc..0b3446a 100644 --- a/tests/semantic.rs +++ b/tests/semantic.rs @@ -7,7 +7,12 @@ use deltin_rs::semantic::check_project; use deltin_rs::semantic::provider::NoopProvider; use std::path::PathBuf; -fn check(text: &str) -> (Vec, deltin_rs::semantic::SemanticProgram) { +fn check( + text: &str, +) -> ( + Vec, + deltin_rs::semantic::SemanticProgram, +) { static COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); let n = COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed); let dir = std::env::temp_dir().join(format!("deltin-rs-semantic-{}-{n}", std::process::id())); diff --git a/tests/workshop_lowering.rs b/tests/workshop_lowering.rs index 0f1af57..336be65 100644 --- a/tests/workshop_lowering.rs +++ b/tests/workshop_lowering.rs @@ -1,7 +1,7 @@ //! Core DEL HIR -> canonical Workshop WIR lowering evidence for #30. use deltin_rs::hir; -use deltin_rs::project::{ProjectOptions, load_project}; +use deltin_rs::project::{load_project, ProjectOptions}; use deltin_rs::semantic::check_project; use deltin_rs::semantic::provider::CatalogProvider; use deltin_rs::workshop::{lower_project_to_wir, lower_to_wir}; @@ -97,11 +97,9 @@ fn hir_is_backend_neutral_and_hir_only_external_lowering_fails_closed() { let (program, diagnostics) = lower_to_wir(&hir, &semantic.project.sources); assert!(program.rules.is_empty()); - assert!( - diagnostics - .iter() - .any(|diagnostic| diagnostic.code == "HI018" && diagnostic.primary == external.0) - ); + assert!(diagnostics + .iter() + .any(|diagnostic| diagnostic.code == "HI018" && diagnostic.primary == external.0)); } #[test] @@ -112,15 +110,43 @@ void First(Number amount, Number label) "First" { amount += 1; } rule: "params" Event.OngoingGlobal { First(label: 1, amount: 2); } "#, ); - assert!(diagnostics.iter().all(|diagnostic| !diagnostic.is_error()), "{diagnostics:?}"); + assert!( + diagnostics.iter().all(|diagnostic| !diagnostic.is_error()), + "{diagnostics:?}" + ); program.validate().expect("structurally valid WIR"); assert_eq!(program.global_variables.len(), 2); - assert_eq!(program.global_variables.get(workshop_rs::wir::GlobalVarId::from_index(0)).unwrap().name, "__del_param_f0_p0"); - assert_eq!(program.global_variables.get(workshop_rs::wir::GlobalVarId::from_index(1)).unwrap().name, "__del_param_f0_p1"); - let rule = program.rules.iter().find(|rule| rule.name == "params").unwrap(); - assert!(matches!(program.actions.get(rule.actions[0]), Some(workshop_rs::wir::Action::SetGlobalVariable { variable, .. }) if *variable == workshop_rs::wir::GlobalVarId::from_index(1))); - assert!(matches!(program.actions.get(rule.actions[1]), Some(workshop_rs::wir::Action::SetGlobalVariable { variable, .. }) if *variable == workshop_rs::wir::GlobalVarId::from_index(0))); - assert!(matches!(program.actions.get(rule.actions[2]), Some(workshop_rs::wir::Action::CallSubroutine { .. }))); + assert_eq!( + program + .global_variables + .get(workshop_rs::wir::GlobalVarId::from_index(0)) + .unwrap() + .name, + "__del_param_f0_p0" + ); + assert_eq!( + program + .global_variables + .get(workshop_rs::wir::GlobalVarId::from_index(1)) + .unwrap() + .name, + "__del_param_f0_p1" + ); + let rule = program + .rules + .iter() + .find(|rule| rule.name == "params") + .unwrap(); + assert!( + matches!(program.actions.get(rule.actions[0]), Some(workshop_rs::wir::Action::SetGlobalVariable { variable, .. }) if *variable == workshop_rs::wir::GlobalVarId::from_index(1)) + ); + assert!( + matches!(program.actions.get(rule.actions[1]), Some(workshop_rs::wir::Action::SetGlobalVariable { variable, .. }) if *variable == workshop_rs::wir::GlobalVarId::from_index(0)) + ); + assert!(matches!( + program.actions.get(rule.actions[2]), + Some(workshop_rs::wir::Action::CallSubroutine { .. }) + )); let catalog = workshop_rs::catalog::Catalog::builtin().unwrap(); let locale = workshop_rs::catalog::Locale::new("en-US"); let emitted = workshop_rs::emitter::emit(&program, &catalog, &locale).unwrap(); @@ -143,7 +169,12 @@ rule: "any" Event.OngoingGlobal { Target(1); }"#, ] { let (program, diagnostics) = lower(source); assert!(program.rules.is_empty(), "{source}"); - assert!(diagnostics.iter().any(|diagnostic| diagnostic.code == "HI018"), "{source}\n{diagnostics:?}"); + assert!( + diagnostics + .iter() + .any(|diagnostic| diagnostic.code == "HI018"), + "{source}\n{diagnostics:?}" + ); } } @@ -155,8 +186,18 @@ void Target(Number amount) "Target" { } rule: "nested" Event.OngoingGlobal { if (true) { Target(1); } } "#, ); - assert!(program.rules.is_empty(), "{}\n{diagnostics:?}", program.dump()); - assert!(diagnostics.iter().any(|diagnostic| diagnostic.code == "HI018" && diagnostic.message.contains("direct actions")), "{diagnostics:?}"); + assert!( + program.rules.is_empty(), + "{}\n{diagnostics:?}", + program.dump() + ); + assert!( + diagnostics + .iter() + .any(|diagnostic| diagnostic.code == "HI018" + && diagnostic.message.contains("direct actions")), + "{diagnostics:?}" + ); } #[test] @@ -184,13 +225,11 @@ rule: "damage" Event.OnDamageDealt if (score > 0) { 0 ); assert_eq!(program.rules.len(), 2); - assert!( - program - .rules - .get(workshop_rs::wir::RuleId::from_index(0)) - .and_then(|rule| rule.span) - .is_some() - ); + assert!(program + .rules + .get(workshop_rs::wir::RuleId::from_index(0)) + .and_then(|rule| rule.span) + .is_some()); let rule = program .rules .get(workshop_rs::wir::RuleId::from_index(1)) @@ -352,11 +391,20 @@ rule: "colliding-local" Event.OngoingGlobal { } "#, ); - assert!(program.rules.is_empty(), "{}\n{diagnostics:?}", program.dump()); - assert!(diagnostics.iter().any(|diagnostic| { - diagnostic.code == "HI018" - && diagnostic.message.contains("synthetic rule-local global name") - }), "{diagnostics:?}"); + assert!( + program.rules.is_empty(), + "{}\n{diagnostics:?}", + program.dump() + ); + assert!( + diagnostics.iter().any(|diagnostic| { + diagnostic.code == "HI018" + && diagnostic + .message + .contains("synthetic rule-local global name") + }), + "{diagnostics:?}" + ); } #[test] @@ -582,18 +630,16 @@ rule: "foreach-wait" Event.OngoingGlobal { } "#, ); + assert!(program.rules.iter().all(|rule| rule.name != "foreach-wait")); assert!( - program - .rules - .iter() - .all(|rule| rule.name != "foreach-wait") + diagnostics.iter().any(|diagnostic| { + diagnostic.code == "HI018" + && diagnostic + .message + .contains("non-reentrant global rule context") + }), + "{diagnostics:?}" ); - assert!(diagnostics.iter().any(|diagnostic| { - diagnostic.code == "HI018" - && diagnostic - .message - .contains("non-reentrant global rule context") - }), "{diagnostics:?}"); } #[test] @@ -615,12 +661,15 @@ rule: "foreach-collision" Event.OngoingGlobal { "{}\n{diagnostics:?}", program.dump() ); - assert!(diagnostics.iter().any(|diagnostic| { - diagnostic.code == "HI018" - && diagnostic - .message - .contains("foreach synthetic global name collides") - }), "{diagnostics:?}"); + assert!( + diagnostics.iter().any(|diagnostic| { + diagnostic.code == "HI018" + && diagnostic + .message + .contains("foreach synthetic global name collides") + }), + "{diagnostics:?}" + ); } #[test] @@ -715,9 +764,7 @@ rule: "dynamic-switch" Event.OngoingGlobal { assert_eq!(helper.span, helper.name_span); assert_eq!(helper.span.unwrap().file.index(), 0); let Some(workshop_rs::wir::Action::SetGlobalVariable { - span, - target_span, - .. + span, target_span, .. }) = program.actions.get(rule.actions[0]) else { panic!("dynamic switch must initialize a synthetic global temp") @@ -822,9 +869,9 @@ rule: "calls-dynamic" Event.OngoingGlobal { assert!( diagnostics.iter().any(|diagnostic| { diagnostic.code == "HI018" - && diagnostic - .message - .contains("subroutine switch materialization requires a bounded invocation context") + && diagnostic.message.contains( + "subroutine switch materialization requires a bounded invocation context", + ) }), "{diagnostics:?}" ); @@ -919,15 +966,39 @@ rule: "player-target" Event.OngoingPlayer { } "#, ); - assert!(diagnostics.iter().all(|diagnostic| !diagnostic.is_error()), "{diagnostics:?}"); + assert!( + diagnostics.iter().all(|diagnostic| !diagnostic.is_error()), + "{diagnostics:?}" + ); program.validate().expect("structurally valid WIR"); - let global = program.rules.iter().find(|rule| rule.name == "global-target").unwrap(); - let player = program.rules.iter().find(|rule| rule.name == "player-target").unwrap(); - for (rule, expected) in [(global, ["chaseAtRate", "stopChasingVariable"]), (player, ["chaseAtRate", ""])] { - assert_eq!(rule.actions.len(), if expected[1].is_empty() { 1 } else { 2 }); + let global = program + .rules + .iter() + .find(|rule| rule.name == "global-target") + .unwrap(); + let player = program + .rules + .iter() + .find(|rule| rule.name == "player-target") + .unwrap(); + for (rule, expected) in [ + (global, ["chaseAtRate", "stopChasingVariable"]), + (player, ["chaseAtRate", ""]), + ] { + assert_eq!( + rule.actions.len(), + if expected[1].is_empty() { 1 } else { 2 } + ); for (action, name) in rule.actions.iter().zip(expected) { - if name.is_empty() { break; } - let workshop_rs::wir::Action::Call { name: actual, args, .. } = program.actions.get(*action).unwrap() else { panic!("expected canonical action") }; + if name.is_empty() { + break; + } + let workshop_rs::wir::Action::Call { + name: actual, args, .. + } = program.actions.get(*action).unwrap() + else { + panic!("expected canonical action") + }; assert_eq!(actual, name); assert!(!args.is_empty()); } @@ -949,10 +1020,15 @@ rule: "dynamic-target" Event.OngoingGlobal { "#, ); assert!(program.rules.is_empty()); - assert!(diagnostics.iter().any(|diagnostic| { - diagnostic.code == "HI018" - && diagnostic.message.contains("resolved global or player variable") - }), "{diagnostics:?}"); + assert!( + diagnostics.iter().any(|diagnostic| { + diagnostic.code == "HI018" + && diagnostic + .message + .contains("resolved global or player variable") + }), + "{diagnostics:?}" + ); } #[test] @@ -964,8 +1040,13 @@ rule: "player-stop" Event.OngoingPlayer { StopChasingVariable(target); } "#, ); assert!(program.rules.is_empty()); - assert!(diagnostics.iter().any(|diagnostic| { - diagnostic.code == "HI018" - && diagnostic.message.contains("player stop-chase action is unavailable") - }), "{diagnostics:?}"); + assert!( + diagnostics.iter().any(|diagnostic| { + diagnostic.code == "HI018" + && diagnostic + .message + .contains("player stop-chase action is unavailable") + }), + "{diagnostics:?}" + ); }