Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONFIGURING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 28 additions & 0 deletions crates/dreamchecker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
22 changes: 21 additions & 1 deletion crates/dreamchecker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,7 @@ pub struct AnalyzeObjectTree<'o> {

return_type: HashMap<ProcRef<'o>, TypeExpr<'o>>,
must_call_parent: ProcDirective<'o>,
must_not_call_parent: ProcDirective<'o>,
must_not_override: ProcDirective<'o>,
private: ProcDirective<'o>,
protected: ProcDirective<'o>,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
{
Expand Down
62 changes: 62 additions & 0 deletions crates/dreamchecker/tests/directive_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}