From 87b1325e779e9b23f0aa72dcf2deeea257f4dd7b Mon Sep 17 00:00:00 2001 From: spookydonut Date: Fri, 4 Mar 2022 19:55:48 +0800 Subject: [PATCH 1/3] Lint initial(thing.var), where var is a list --- crates/dreamchecker/src/lib.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/dreamchecker/src/lib.rs b/crates/dreamchecker/src/lib.rs index d187a411..002fe39f 100644 --- a/crates/dreamchecker/src/lib.rs +++ b/crates/dreamchecker/src/lib.rs @@ -2218,6 +2218,17 @@ 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.is_list() { + if let Expression::Base{term: _, follow} = arg { + for each in follow.iter() { + if let Spanned{location, elem: Follow::Field(_, ident)} = each { + error(*location, format!("built-in proc initial() called on list type var `{}`", ident)).register(self.context); + } + } + } + } + if let Some(kw) = this_kwarg { param_name_map.insert(kw.as_str(), analysis); param_expr_map.insert(kw.as_str(), argument_value); From c351e4ef7d6e2980a21cf91023e5eea99362c0bb Mon Sep 17 00:00:00 2001 From: spookydonut Date: Sun, 10 Jul 2022 15:10:22 +0800 Subject: [PATCH 2/3] Make it better --- crates/dreamchecker/src/lib.rs | 33 ++++++++++++++++++---- crates/dreamchecker/tests/initial_tests.rs | 30 ++++++++++++++++++++ 2 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 crates/dreamchecker/tests/initial_tests.rs diff --git a/crates/dreamchecker/src/lib.rs b/crates/dreamchecker/src/lib.rs index 6fbc969d..72d58ddc 100644 --- a/crates/dreamchecker/src/lib.rs +++ b/crates/dreamchecker/src/lib.rs @@ -2220,11 +2220,34 @@ 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.is_list() { - if let Expression::Base{term: _, follow} = arg { - for each in follow.iter() { - if let Spanned{location, elem: Follow::Field(_, ident)} = each { - error(*location, format!("built-in proc initial() called on list type var `{}`", ident)).register(self.context); + 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() { + match analysis.static_ty { + StaticType::List { list: _, keys: _ } => { + error(*location, format!("initial() called on var/list/{}, but it always returns null for lists", ident)) + .register(self.context); + } + StaticType::Type(ty) => { + error(*location, format!("initial() called on var{}/{}, but it always returns null for types", ty.pretty_path() , ident)) + .register(self.context); + } + _ => {} + } + } else { + let term_analysis = self.visit_term(term.location, &term.elem, None, local_vars); + if let Term::Ident(name) = &term.elem { + match term_analysis.static_ty { + StaticType::List { list: _, keys: _ } => { + error(term.location, format!("initial() called on var/list/{}, but it always returns null for lists", name)) + .register(self.context); + } + StaticType::Type(ty) => { + error(term.location, format!("initial() called on var{}/{}, but it always returns null for types", ty.pretty_path() , name)) + .register(self.context); + } + _ => {} + } } } } 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); +} From 48ca3003c1fbb5295569106ae291dac947b500d1 Mon Sep 17 00:00:00 2001 From: spookydonut Date: Sun, 10 Jul 2022 16:47:18 +0800 Subject: [PATCH 3/3] Improved --- crates/dreamchecker/src/lib.rs | 47 +++++++++++++++------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/crates/dreamchecker/src/lib.rs b/crates/dreamchecker/src/lib.rs index 72d58ddc..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) { @@ -2223,31 +2238,11 @@ impl<'o, 's> AnalyzeProc<'o, 's> { 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() { - match analysis.static_ty { - StaticType::List { list: _, keys: _ } => { - error(*location, format!("initial() called on var/list/{}, but it always returns null for lists", ident)) - .register(self.context); - } - StaticType::Type(ty) => { - error(*location, format!("initial() called on var{}/{}, but it always returns null for types", ty.pretty_path() , ident)) - .register(self.context); - } - _ => {} - } + self.initial_null_error(analysis.static_ty.clone(), ident.to_string(), *location); } else { - let term_analysis = self.visit_term(term.location, &term.elem, None, local_vars); - if let Term::Ident(name) = &term.elem { - match term_analysis.static_ty { - StaticType::List { list: _, keys: _ } => { - error(term.location, format!("initial() called on var/list/{}, but it always returns null for lists", name)) - .register(self.context); - } - StaticType::Type(ty) => { - error(term.location, format!("initial() called on var{}/{}, but it always returns null for types", ty.pretty_path() , name)) - .register(self.context); - } - _ => {} - } + 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); } } }