mirror of https://github.com/tbklang/tlang.git
⚡ Feature: Variable usage checker (#32)
* Dependency - Added `varRefCounts` which maps a given `Variable` to its reference count. This includes the declaration thereof. - Added `touch(Variable variable)` which Increments the given variable's reference count * Dependency - When handling a standalone assignment to a variable increment it's reference count - This was updated in the handling of `VariableAssignmentStdAlone` * Dependency - Make variable declaration count as the first reference * Dependency - VAriables that appear in expressions must increase the ref count * Dependency - Added `getUnusedVariables()` which returns all variables which were declared but not used * TypeChecker - Moved the reference counting mechanism into the `TypeChecker` to avoid problem of seperate `DNodeGenerator` instances having their own seperate counts - We now check the reference count values after function definition processing so as to get a proper count Dependency - Use the reference counting from `TypeChecker` - Removed reference counting code Test cases - Added a positive case in the form of `unused_vars.t` - Added a negative case in the form of `unused_vars_none.t` * Commands - Added commands to `TypeCheckerBase!()` mixin template - Added `TypeCheckerInit(Compiler compiler)` - Use the template in both `compile` command and the`typecheck` command * TypeChecker - We now respect the `typecheck:warnUnusedVars` option * TypeChecker - Added `doPostChecks()` - Moved the reference counting code to `doPostChecks()` * Configuration - `defaultConfig()` now adds an entry for `typecheck:warnUnusedVars` set to `true` * TypeChecker - Use `getName()` on the `Variable` when printing out the names of unused variables * Compiler - Made `gibFileData(string sourceFile)` public - Added `getTypeChecker()` to the `Compiler` class * TypeChecker (unittests) - Added a positive test case unittest for the unused variables detection mechanism * TypeChecker (unittests) - Check the variable is equal to the one we are looking for (`j`) * TypeChecker (unittests) - Added a negative test case for the unused variables detection mechanism * Pipelines - Added tests for `unused_vars.t` and `unused_vars_none.t` * TypeChecker - Fixed message which prints out when an unused variable is detected * Merge branch 'vardec_varass_dependency' into feature/unused_vars_detection
This commit is contained in:
parent
7a2ea96140
commit
800f06da5f
|
@ -382,6 +382,11 @@ jobs:
|
|||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Unused variables check (positive case)
|
||||
run: ./tlang typecheck source/tlang/testing/unused_vars.t --unusedVars true
|
||||
- name: Unused variables check (negative case)
|
||||
run: ./tlang typecheck source/tlang/testing/unused_vars_none.t --unusedVars true
|
||||
|
||||
emit:
|
||||
needs: [build, unittests]
|
||||
|
|
|
@ -116,7 +116,15 @@ mixin template EmitBase()
|
|||
*/
|
||||
mixin template TypeCheckerBase()
|
||||
{
|
||||
@ArgNamed("unusedVars|uvars", "Warn about any unused variables")
|
||||
@(ArgConfig.optional)
|
||||
bool warnUnusedVariables = true;
|
||||
|
||||
void TypeCheckerInit(Compiler compiler)
|
||||
{
|
||||
// Set whether to warn about unused variables
|
||||
compiler.getConfig().addConfig(ConfigEntry("typecheck:warnUnusedVars", warnUnusedVariables));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -127,7 +135,7 @@ struct compileCommand
|
|||
{
|
||||
mixin BaseCommand!();
|
||||
|
||||
|
||||
mixin TypeCheckerBase!();
|
||||
|
||||
mixin EmitBase!();
|
||||
|
||||
|
@ -154,6 +162,9 @@ struct compileCommand
|
|||
/* Setup general configuration parameters */
|
||||
BaseCommandInit(compiler);
|
||||
|
||||
/* Setup all type checker related parameters */
|
||||
TypeCheckerInit(compiler);
|
||||
|
||||
/* Perform tokenization */
|
||||
compiler.doLex();
|
||||
writeln("=== Tokens ===\n");
|
||||
|
@ -295,7 +306,7 @@ struct parseCommand
|
|||
struct typecheckCommand
|
||||
{
|
||||
mixin BaseCommand!();
|
||||
|
||||
mixin TypeCheckerBase!();
|
||||
|
||||
void onExecute()
|
||||
{
|
||||
|
@ -317,6 +328,9 @@ struct typecheckCommand
|
|||
/* Setup general configuration parameters */
|
||||
BaseCommandInit(compiler);
|
||||
|
||||
/* Setup all type checker related parameters */
|
||||
TypeCheckerInit(compiler);
|
||||
|
||||
compiler.doLex();
|
||||
writeln("=== Tokens ===\n");
|
||||
writeln(compiler.getTokens());
|
||||
|
|
|
@ -229,6 +229,9 @@ public final class CompilerConfiguration
|
|||
config.addConfig(ConfigEntry("types:max_width", 8));
|
||||
}
|
||||
|
||||
/* Always warn about unused variables */
|
||||
config.addConfig(ConfigEntry("typecheck:warnUnusedVars", true));
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
|
@ -196,6 +196,25 @@ public class Compiler
|
|||
this.typeChecker.beginCheck();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type checker instance of
|
||||
* this compiler
|
||||
*
|
||||
* Returns: the type checker
|
||||
* Throws:
|
||||
* CompilerException if you have not
|
||||
* called `doTypeCheck()` yet
|
||||
*/
|
||||
public TypeChecker getTypeChecker()
|
||||
{
|
||||
if(typeChecker is null)
|
||||
{
|
||||
throw new CompilerException(CompilerError.TYPECHECK_NOT_YET_PERFORMED);
|
||||
}
|
||||
|
||||
return this.typeChecker;
|
||||
}
|
||||
|
||||
/* Perform code emitting */
|
||||
public void doEmit()
|
||||
{
|
||||
|
@ -255,7 +274,7 @@ public class Compiler
|
|||
* sourceFile = the path to the file to open
|
||||
* Returns: the source data
|
||||
*/
|
||||
private string gibFileData(string sourceFile)
|
||||
public string gibFileData(string sourceFile)
|
||||
{
|
||||
File sourceFileFile;
|
||||
sourceFileFile.open(sourceFile); /* TODO: Error handling with ANY file I/O */
|
||||
|
|
|
@ -209,6 +209,34 @@ public final class TypeChecker
|
|||
|
||||
gprintln("FUNCDEF DONE: "~to!(string)(functionBodyCodeQueues[funcData.name]));
|
||||
}
|
||||
|
||||
/* Collect statistics */
|
||||
doPostChecks();
|
||||
}
|
||||
|
||||
/**
|
||||
* These are just checks we run for the convenience
|
||||
* of the user. They do not manipulate anything but
|
||||
* rather provide statistics
|
||||
*/
|
||||
private void doPostChecks()
|
||||
{
|
||||
/**
|
||||
* Find the variables which were declared but never used
|
||||
*/
|
||||
if(this.config.hasConfig("typecheck:warnUnusedVars") & this.config.getConfig("typecheck:warnUnusedVars").getBoolean())
|
||||
{
|
||||
Variable[] unusedVariables = getUnusedVariables();
|
||||
gprintln("There are "~to!(string)(unusedVariables.length)~" unused variables");
|
||||
if(unusedVariables.length)
|
||||
{
|
||||
foreach(Variable unusedVariable; unusedVariables)
|
||||
{
|
||||
// TODO: Get a nicer name, full path-based
|
||||
gprintln("Variable '"~to!(string)(unusedVariable.getName())~"' is declared but never used");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -3257,6 +3285,51 @@ public final class TypeChecker
|
|||
//assert()
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a given `Variable` to its reference
|
||||
* count. This includes the declaration
|
||||
* thereof.
|
||||
*/
|
||||
private uint[Variable] varRefCounts;
|
||||
|
||||
/**
|
||||
* Increments the given variable's reference
|
||||
* count
|
||||
*
|
||||
* Params:
|
||||
* variable = the variable
|
||||
*/
|
||||
void touch(Variable variable)
|
||||
{
|
||||
// Create entry if not existing yet
|
||||
if(variable !in this.varRefCounts)
|
||||
{
|
||||
this.varRefCounts[variable] = 0;
|
||||
}
|
||||
|
||||
// Increment count
|
||||
this.varRefCounts[variable]++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all variables which were declared
|
||||
* but not used
|
||||
*
|
||||
* Returns: the array of variables
|
||||
*/
|
||||
public Variable[] getUnusedVariables()
|
||||
{
|
||||
Variable[] unused;
|
||||
foreach(Variable variable; this.varRefCounts.keys())
|
||||
{
|
||||
if(!(this.varRefCounts[variable] > 1))
|
||||
{
|
||||
unused ~= variable;
|
||||
}
|
||||
}
|
||||
|
||||
return unused;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -3580,4 +3653,64 @@ unittest
|
|||
typeChecker.beginCheck();
|
||||
|
||||
/* TODO: Actually test generated code queue */
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the unused variable detection mechanism
|
||||
*
|
||||
* Case: Positive (unused variables exist)
|
||||
* Source file: source/tlang/testing/unused_vars.t
|
||||
*/
|
||||
unittest
|
||||
{
|
||||
// Dummy field out
|
||||
File fileOutDummy;
|
||||
import tlang.compiler.core;
|
||||
|
||||
string sourceFile = "source/tlang/testing/unused_vars.t";
|
||||
|
||||
|
||||
Compiler compiler = new Compiler(gibFileData(sourceFile), fileOutDummy);
|
||||
compiler.doLex();
|
||||
compiler.doParse();
|
||||
compiler.doTypeCheck();
|
||||
TypeChecker tc = compiler.getTypeChecker();
|
||||
|
||||
/**
|
||||
* There should be 1 unused variable and then
|
||||
* it should be named `j`
|
||||
*/
|
||||
Variable[] unusedVars = tc.getUnusedVariables();
|
||||
assert(unusedVars.length == 1);
|
||||
Variable unusedVarActual = unusedVars[0];
|
||||
Variable unusedVarExpected = cast(Variable)tc.getResolver().resolveBest(tc.getModule(), "j");
|
||||
assert(unusedVarActual is unusedVarExpected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the unused variable detection mechanism
|
||||
*
|
||||
* Case: Negative (unused variables do NOT exist)
|
||||
* Source file: source/tlang/testing/unused_vars_none.t
|
||||
*/
|
||||
unittest
|
||||
{
|
||||
// Dummy field out
|
||||
File fileOutDummy;
|
||||
import tlang.compiler.core;
|
||||
|
||||
string sourceFile = "source/tlang/testing/unused_vars_none.t";
|
||||
|
||||
|
||||
Compiler compiler = new Compiler(gibFileData(sourceFile), fileOutDummy);
|
||||
compiler.doLex();
|
||||
compiler.doParse();
|
||||
compiler.doTypeCheck();
|
||||
TypeChecker tc = compiler.getTypeChecker();
|
||||
|
||||
/**
|
||||
* There should be 0 unused variables
|
||||
*/
|
||||
Variable[] unusedVars = tc.getUnusedVariables();
|
||||
assert(unusedVars.length == 0);
|
||||
}
|
|
@ -673,6 +673,9 @@ public class DNodeGenerator
|
|||
/* Get the entity as a Variable */
|
||||
Variable variable = cast(Variable)namedEntity;
|
||||
|
||||
/* Variable reference count must increase */
|
||||
tc.touch(variable);
|
||||
|
||||
/* Pool the node */
|
||||
VariableNode varDecNode = poolT!(VariableNode, Variable)(variable);
|
||||
|
||||
|
@ -980,6 +983,9 @@ public class DNodeGenerator
|
|||
writeln("Hello");
|
||||
writeln("VarType: "~to!(string)(variableType));
|
||||
|
||||
/* Add an entry to the reference counting map */
|
||||
tc.touch(variable);
|
||||
|
||||
/* Basic type */
|
||||
if(cast(Primitive)variableType)
|
||||
{
|
||||
|
@ -1082,6 +1088,9 @@ public class DNodeGenerator
|
|||
Variable variable = cast(Variable)tc.getResolver().resolveBest(c, vAsStdAl.getVariableName());
|
||||
assert(variable);
|
||||
|
||||
/* Assinging to a variable is usage, therefore increment the reference count */
|
||||
tc.touch(variable);
|
||||
|
||||
|
||||
/* Pool the variable */
|
||||
DNode varDecDNode = pool(variable);
|
||||
|
@ -1558,5 +1567,4 @@ public class DNodeGenerator
|
|||
|
||||
return classDNode;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
module unused_vars;
|
||||
|
||||
int j;
|
|
@ -0,0 +1,8 @@
|
|||
module unused_vars;
|
||||
|
||||
int j;
|
||||
|
||||
void thing()
|
||||
{
|
||||
j = 1;
|
||||
}
|
Loading…
Reference in New Issue