From 8419317ddc62171f8c8a721524d1dcdca2760354 Mon Sep 17 00:00:00 2001 From: Lucy Date: Mon, 30 Mar 2026 20:27:46 -0400 Subject: [PATCH] Adds `SHOULD_NOT_CALL_PARENT` --- CONFIGURING.md | 1 + crates/dreamchecker/README.md | 28 +++++++++ crates/dreamchecker/src/lib.rs | 22 ++++++- crates/dreamchecker/tests/directive_tests.rs | 62 ++++++++++++++++++++ 4 files changed, 112 insertions(+), 1 deletion(-) diff --git a/CONFIGURING.md b/CONFIGURING.md index a868fafa0..9b97d1783 100644 --- a/CONFIGURING.md +++ b/CONFIGURING.md @@ -31,6 +31,7 @@ Raised by DreamChecker: * `override_missing_keyword_arg` - Raised when proc overrides are missing keyword arguments * `must_not_override` - `SpacemanDMM_should_not_override` directive * `must_call_parent` - `SpacemanDMM_should_call_parent` directive +* `must_not_call_parent` - `SpacemanDMM_should_not_call_parent` directive * `final_var` - `SpacemanDMM_final` var type * `private_proc` - `SpacemanDMM_private_proc` directive * `protected_proc` - `SpacemanDMM_protected_proc` directive diff --git a/crates/dreamchecker/README.md b/crates/dreamchecker/README.md index 0e9451422..1ba978604 100644 --- a/crates/dreamchecker/README.md +++ b/crates/dreamchecker/README.md @@ -59,6 +59,7 @@ be enabled: #ifdef SPACEMAN_DMM #define RETURN_TYPE(X) set SpacemanDMM_return_type = X #define SHOULD_CALL_PARENT(X) set SpacemanDMM_should_call_parent = X + #define SHOULD_NOT_CALL_PARENT(X) set SpacemanDMM_should_not_call_parent = X #define UNLINT(X) SpacemanDMM_unlint(X) #define SHOULD_NOT_OVERRIDE(X) set SpacemanDMM_should_not_override = X #define SHOULD_NOT_SLEEP(X) set SpacemanDMM_should_not_sleep = X @@ -72,6 +73,7 @@ be enabled: #else #define RETURN_TYPE(X) #define SHOULD_CALL_PARENT(X) + #define SHOULD_NOT_CALL_PARENT(X) #define UNLINT(X) X #define SHOULD_NOT_OVERRIDE(X) #define SHOULD_NOT_SLEEP(X) @@ -107,6 +109,32 @@ help with finding situations where a signal or other important handling in the parent proc is being skipped. Child procs may set this setting to `0` instead to override the check. +### Should not call parent + +Use `set SpacemanDMM_should_not_call_parent = 1` to raise a warning for a direct child that calls the parent. +Grandchildren can still call their parent, though. + +Child procs may set this setting to `0` instead to override the check. + +For example: +```dm +/datum/proc/thing() + SHOULD_NOT_CALL_PARENT(TRUE) + CRASH("This should not be called!") + +/datum/a/thing() + // This will throw an error! + return ..() + +/datum/b/thing() + // This is fine. + return TRUE + +/datum/b/c/thing() + // This is ALSO fine, as it is a grandchild + return ..() +``` + ### Should not override Use `set SpacemanDMM_should_not_override = 1` to raise a warning for any child diff --git a/crates/dreamchecker/src/lib.rs b/crates/dreamchecker/src/lib.rs index 681d071df..3d2431760 100644 --- a/crates/dreamchecker/src/lib.rs +++ b/crates/dreamchecker/src/lib.rs @@ -600,6 +600,7 @@ pub struct AnalyzeObjectTree<'o> { return_type: HashMap, TypeExpr<'o>>, must_call_parent: ProcDirective<'o>, + must_not_call_parent: ProcDirective<'o>, must_not_override: ProcDirective<'o>, private: ProcDirective<'o>, protected: ProcDirective<'o>, @@ -639,6 +640,12 @@ impl<'o> AnalyzeObjectTree<'o> { false, false, ), + must_not_call_parent: ProcDirective::new( + "SpacemanDMM_should_not_call_parent", + true, + false, + false, + ), must_not_override: ProcDirective::new( "SpacemanDMM_should_not_override", false, @@ -685,6 +692,7 @@ impl<'o> AnalyzeObjectTree<'o> { let procdirective = match directive { "SpacemanDMM_should_not_override" => &mut self.must_not_override, "SpacemanDMM_should_call_parent" => &mut self.must_call_parent, + "SpacemanDMM_should_not_call_parent" => &mut self.must_not_call_parent, "SpacemanDMM_private_proc" => &mut self.private, "SpacemanDMM_protected_proc" => &mut self.protected, "SpacemanDMM_should_not_sleep" => &mut self.must_not_sleep, @@ -1414,7 +1422,19 @@ impl<'o, 's> AnalyzeProc<'o, 's> { .register(self.context); } } - if !self.calls_parent { + if self.calls_parent { + if !matches!(self.env.must_not_call_parent.get(self.proc_ref), Some((false, _))) { + if let Some((true, location)) = self.env.must_not_call_parent.get(parent) { + error( + self.proc_ref.location, + format!("proc calls parent, prohibited by {parent}"), + ) + .with_note(*location, "required by this must_not_call_parent annotation") + .with_errortype("must_not_call_parent") + .register(self.context); + } + } + } else { if let Some((proc, true, location)) = self.env.must_call_parent.get_self_or_parent(self.proc_ref) { diff --git a/crates/dreamchecker/tests/directive_tests.rs b/crates/dreamchecker/tests/directive_tests.rs index 09b3fccfc..e2339992c 100644 --- a/crates/dreamchecker/tests/directive_tests.rs +++ b/crates/dreamchecker/tests/directive_tests.rs @@ -137,3 +137,65 @@ fn no_can_be_redefined() { .trim(); check_errors_match(code, NO_CAN_BE_REDEFINED_ERRORS); } + +pub const NO_CALL_PARENT_ERRORS: &[(u32, u16, &str)] = + &[(4, 18, "proc calls parent, prohibited by /mob/proc/test")]; + +#[test] +fn should_not_call_parent() { + let code = r##" +/mob/proc/test() + set SpacemanDMM_should_not_call_parent = 1 + +/mob/subtype/test() + return ..() +"## + .trim(); + check_errors_match(code, NO_CALL_PARENT_ERRORS); +} + +#[test] +fn should_not_call_parent_override() { + let code = r##" +/mob/proc/test() + set SpacemanDMM_should_not_call_parent = 1 + +/mob/subtype/test() + set SpacemanDMM_should_not_call_parent = 0 + return ..() +"## + .trim(); + check_errors_match(code, NO_ERRORS); +} + +#[test] +fn should_not_call_parent_grandchild() { + let code = r##" +/mob/proc/test() + set SpacemanDMM_should_not_call_parent = 1 + +/mob/subtype/test() + return 1 + +/mob/subtype/grandchild/test() + return ..() +"## + .trim(); + check_errors_match(code, NO_ERRORS); +} + +pub const NO_CALL_PARENT_GAP_ERRORS: &[(u32, u16, &str)] = + &[(4, 29, "proc calls parent, prohibited by /mob/proc/test")]; + +#[test] +fn should_not_call_parent_grandchild_gap() { + let code = r##" +/mob/proc/test() + set SpacemanDMM_should_not_call_parent = 1 + +/mob/subtype/grandchild/test() + return ..() +"## + .trim(); + check_errors_match(code, NO_CALL_PARENT_GAP_ERRORS); +}