diff --git a/crates/dreamchecker/src/lib.rs b/crates/dreamchecker/src/lib.rs index 6173615c..e1163010 100644 --- a/crates/dreamchecker/src/lib.rs +++ b/crates/dreamchecker/src/lib.rs @@ -692,7 +692,7 @@ impl<'o> AnalyzeObjectTree<'o> { .with_blocking_builtins(self.sleeping_procs.get_violators(*child_violator).unwrap()) .register(self.context) } - } + } if let Some(calledvec) = self.call_tree.get(&nextproc) { for (proccalled, location, new_context) in calledvec.iter() { let mut newstack = callstack.clone(); @@ -743,7 +743,7 @@ impl<'o> AnalyzeObjectTree<'o> { .with_blocking_builtins(self.impure_procs.get_violators(*child_violator).unwrap()) .register(self.context) } - } + } if let Some(calledvec) = self.call_tree.get(&nextproc) { for (proccalled, location, new_context) in calledvec.iter() { let mut newstack = callstack.clone(); @@ -2133,6 +2133,21 @@ impl<'o, 's> AnalyzeProc<'o, 's> { } } + fn initial_null_error(&mut self, static_ty: StaticType, name: String, location: Location) { + match static_ty { + StaticType::List { list: _, keys: _ } => { + error(location, format!("initial() called on var/list/{}, but it always returns null for lists", name)) + .register(self.context); + } + StaticType::Type(ty) if ty == self.objtree.expect("/icon") => {} + StaticType::Type(ty) => { + error(location, format!("initial() called on var{}/{}, but it always returns null for types", ty.pretty_path() , name)) + .register(self.context); + } + _ => {} + } + } + fn visit_call(&mut self, location: Location, src: TypeRef<'o>, proc: ProcRef<'o>, args: &'o [Expression], is_exact: bool, local_vars: &mut HashMap, RandomState>) -> Analysis<'o> { self.env.call_tree.entry(self.proc_ref).or_default().push((proc, location, self.inside_newcontext != 0)); if let Some((privateproc, true, decllocation)) = self.env.private.get_self_or_parent(proc) { @@ -2219,6 +2234,20 @@ impl<'o, 's> AnalyzeProc<'o, 's> { } let analysis = self.visit_expression(location, argument_value, None, local_vars); + + if proc.is_builtin() && proc.name() == "initial" && analysis.static_ty != StaticType::None { + if let Expression::Base{term, follow} = arg { + if let Some(Spanned{location, elem: Follow::Field(_, ident)}) = follow.last() { + self.initial_null_error(analysis.static_ty.clone(), ident.to_string(), *location); + } else { + let argument_analysis = self.visit_term(term.location, &term.elem, None, local_vars); + if let Term::Ident(ident_name) = &term.elem { + self.initial_null_error(argument_analysis.static_ty, ident_name.to_string(), term.location); + } + } + } + } + if let Some(kw) = this_kwarg { param_name_map.insert(kw.as_str(), analysis); param_expr_map.insert(kw.as_str(), argument_value); diff --git a/crates/dreamchecker/tests/initial_tests.rs b/crates/dreamchecker/tests/initial_tests.rs new file mode 100644 index 00000000..0fea1576 --- /dev/null +++ b/crates/dreamchecker/tests/initial_tests.rs @@ -0,0 +1,30 @@ + +extern crate dreamchecker as dc; + +use dc::test_helpers::*; + +pub const INITIAL_FOLLOW_ERRORS: &[(u32, u16, &str)] = &[ + (7, 17, "initial() called on var/list/bar, but it always returns null for lists"), + (8, 17, "initial() called on var/turf/T, but it always returns null for types"), + (11, 13, "initial() called on var/list/bar, but it always returns null for lists"), + (12, 13, "initial() called on var/turf/T, but it always returns null for types"), +]; + +#[test] +fn initial_follow() { + let code = r##" +/datum/foo + var/list/bar + var/turf/T + +/proc/test() + var/datum/foo/test = new() + initial(test.bar) + initial(test.T) + +/datum/foo/proc/test() + initial(bar) + initial(T) +"##.trim(); + check_errors_match(code, INITIAL_FOLLOW_ERRORS); +}