Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect unused typevars #220

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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
152 changes: 90 additions & 62 deletions src/dreamchecker/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@

extern crate dreammaker as dm;
use dm::{Context, DMError, Location, Severity};
use dm::objtree::{ObjectTree, TypeRef, ProcRef, Code};
use dm::objtree::{ObjectTree, TypeRef, ProcRef, Code, VarDeclaration};
use dm::constants::{Constant, ConstFn};
use dm::ast::*;

use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use std::borrow::Borrow;

mod type_expr;
use type_expr::TypeExpr;
Expand Down Expand Up @@ -322,10 +323,10 @@ fn run_inner(context: &Context, objtree: &ObjectTree, cli: bool) {
cli_println!("============================================================");
cli_println!("Analyzing variables...\n");

check_var_defs(&objtree, &context);

let mut analyzer = AnalyzeObjectTree::new(context, objtree);

analyzer.check_var_defs();

let mut present = 0;
let mut invalid = 0;
let mut builtin = 0;
Expand Down Expand Up @@ -372,6 +373,11 @@ fn run_inner(context: &Context, objtree: &ObjectTree, cli: bool) {
cli_println!("============================================================");
cli_println!("Analyzing proc call tree...\n");
analyzer.check_proc_call_tree();

cli_println!("============================================================");
cli_println!("Listing unused var declarations...\n");

analyzer.check_unused_typevars();
}

// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -553,6 +559,8 @@ pub struct AnalyzeObjectTree<'o> {
context: &'o Context,
objtree: &'o ObjectTree,

unused_typevars: HashSet<&'o VarDeclaration>,

return_type: HashMap<ProcRef<'o>, TypeExpr<'o>>,
must_call_parent: ProcDirective<'o>,
must_not_override: ProcDirective<'o>,
Expand Down Expand Up @@ -584,6 +592,7 @@ impl<'o> AnalyzeObjectTree<'o> {
context,
objtree,
return_type,
unused_typevars: Default::default(),
must_call_parent: ProcDirective::new("SpacemanDMM_should_call_parent", true, false, false),
must_not_override: ProcDirective::new("SpacemanDMM_should_not_override", false, false, false),
private: ProcDirective::new("SpacemanDMM_private_proc", false, true, false),
Expand All @@ -602,6 +611,70 @@ impl<'o> AnalyzeObjectTree<'o> {
}
}

// ----------------------------------------------------------------------------
// Variable analyzer

/// Examines an ObjectTree for var definitions that are invalid
pub fn check_var_defs(&mut self) {
for typeref in self.objtree.iter_types() {
let path = &typeref.path;

for (varname, typevar) in typeref.vars.iter() {
if let Some(decl) = typeref.get_var_declaration(varname) {
self.unused_typevars.insert(decl.borrow());
}
}

for parent in typeref.iter_parent_types() {
if parent.is_root() {
break;
}

if &parent.path == path {
continue;
}

for (varname, typevar) in typeref.vars.iter() {
if varname == "vars" {
continue;
}
if path == "/client" && varname == "parent_type" {
continue;
}

guard!(let Some(parentvar) = parent.vars.get(varname)
else { continue });

guard!(let Some(decl) = &parentvar.declaration
else { continue });

if let Some(mydecl) = &typevar.declaration {
if typevar.value.location.is_builtins() {
continue;
}
DMError::new(mydecl.location, format!("{} redeclares var {:?}", path, varname))
.with_note(decl.location, format!("declared on {} here", parent.path))
.register(self.context);
}

if decl.var_type.flags.is_final() {
DMError::new(typevar.value.location, format!("{} overrides final var {:?}", path, varname))
.with_errortype("final_var")
.with_note(decl.location, format!("declared final on {} here", parent.path))
.register(self.context);
}

if decl.var_type.flags.is_private() {
DMError::new(typevar.value.location, format!("{} overrides private var {:?}", path, varname))
.with_errortype("private_var")
.with_note(decl.location, format!("declared private on {} here", parent.path))
.register(self.context);
}
}
}
}
}

/// Analyze a specific proc
pub fn check_proc(&mut self, proc: ProcRef<'o>, code: &'o [Spanned<Statement>]) {
self.must_not_sleep.try_copy_from_parent(proc);
Expand Down Expand Up @@ -651,6 +724,18 @@ impl<'o> AnalyzeObjectTree<'o> {
}
}

pub fn check_unused_typevars(&mut self) {
if !self.context.config().code_standards.disallow_unused_var_definitions { return }
for decl in self.unused_typevars.iter() {
if decl.location.is_builtins() {
continue
}
error(decl.location, "Unused var declaration")
.with_errortype("unused_var_declaration")
.register(self.context);
}
}

pub fn check_proc_call_tree(&mut self) {
for (procref, &(_, location)) in self.must_not_sleep.directive.iter() {
if let Some(sleepvec) = self.sleeping_procs.get_violators(*procref) {
Expand Down Expand Up @@ -955,64 +1040,6 @@ fn error<S: Into<String>>(location: Location, desc: S) -> DMError {
DMError::new(location, desc).with_component(dm::Component::DreamChecker)
}

// ----------------------------------------------------------------------------
// Variable analyzer

/// Examines an ObjectTree for var definitions that are invalid
pub fn check_var_defs(objtree: &ObjectTree, context: &Context) {
for typeref in objtree.iter_types() {
let path = &typeref.path;

for parent in typeref.iter_parent_types() {
if parent.is_root() {
break;
}

if &parent.path == path {
continue;
}

for (varname, typevar) in typeref.vars.iter() {
if varname == "vars" {
continue;
}
if path == "/client" && varname == "parent_type" {
continue;
}

guard!(let Some(parentvar) = parent.vars.get(varname)
else { continue });

guard!(let Some(decl) = &parentvar.declaration
else { continue });

if let Some(mydecl) = &typevar.declaration {
if typevar.value.location.is_builtins() {
continue;
}
DMError::new(mydecl.location, format!("{} redeclares var {:?}", path, varname))
.with_note(decl.location, format!("declared on {} here", parent.path))
.register(context);
}

if decl.var_type.flags.is_final() {
DMError::new(typevar.value.location, format!("{} overrides final var {:?}", path, varname))
.with_errortype("final_var")
.with_note(decl.location, format!("declared final on {} here", parent.path))
.register(context);
}

if decl.var_type.flags.is_private() {
DMError::new(typevar.value.location, format!("{} overrides private var {:?}", path, varname))
.with_errortype("private_var")
.with_note(decl.location, format!("declared private on {} here", parent.path))
.register(context);
}
}
}
}
}

// ----------------------------------------------------------------------------
// Procedure analyzer
#[derive(Debug)]
Expand Down Expand Up @@ -1662,7 +1689,7 @@ impl<'o, 's> AnalyzeProc<'o, 's> {
.with_fix_hint(var.location, "add additional type info here")
}
if let Some(decl) = self.ty.get_var_declaration(unscoped_name) {
//println!("found type var");
self.env.unused_typevars.remove(decl);
let mut ana = self.static_type(location, &decl.var_type.type_path)
.with_fix_hint(decl.location, "add additional type info here");
ana.is_impure = Some(true);
Expand Down Expand Up @@ -1906,6 +1933,7 @@ impl<'o, 's> AnalyzeProc<'o, 's> {
Follow::Field(kind, name) => {
if let Some(ty) = lhs.static_ty.basic_type() {
if let Some(decl) = ty.get_var_declaration(name) {
self.env.unused_typevars.remove(decl);
if ty != self.ty && decl.var_type.flags.is_private() {
error(location, format!("field {:?} on {} is declared as private", name, ty))
.with_errortype("private_var")
Expand Down
2 changes: 1 addition & 1 deletion src/dreammaker/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -922,7 +922,7 @@ impl fmt::Display for VarTypeFlags {
}

/// A type which may be ascribed to a `var`.
#[derive(Debug, Clone, PartialEq, Default)]
#[derive(Debug, Clone, PartialEq, Default, Eq, Hash)]
pub struct VarType {
pub flags: VarTypeFlags,
pub type_path: TreePath,
Expand Down
1 change: 1 addition & 0 deletions src/dreammaker/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub struct Langserver {
pub struct CodeStandards {
pub disallow_relative_proc_definitions: bool,
pub disallow_relative_type_definitions: bool,
pub disallow_unused_var_definitions: bool,
}

/// DMDoc config options
Expand Down
2 changes: 1 addition & 1 deletion src/dreammaker/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ impl Context {
// Location handling

/// File, line, and column information for an error.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
pub struct Location {
/// The index into the file table.
pub file: FileId,
Expand Down
2 changes: 1 addition & 1 deletion src/dreammaker/objtree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl SymbolIdSource {

pub type Vars = LinkedHashMap<String, Constant>;

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub struct VarDeclaration {
pub var_type: VarType,
pub location: Location,
Expand Down