mirror of
https://github.com/dlang-community/D-Scanner.git
synced 2025-04-26 05:10:03 +03:00
fix case/default scopes, fix #913
This commit is contained in:
parent
fed654441f
commit
d275361153
3 changed files with 328 additions and 43 deletions
|
@ -5,6 +5,7 @@ import dparse.lexer : IdType, str, Token;
|
||||||
import dsymbol.scope_ : Scope;
|
import dsymbol.scope_ : Scope;
|
||||||
import std.array;
|
import std.array;
|
||||||
import std.container;
|
import std.container;
|
||||||
|
import std.meta : AliasSeq;
|
||||||
import std.string;
|
import std.string;
|
||||||
import std.sumtype;
|
import std.sumtype;
|
||||||
|
|
||||||
|
@ -498,3 +499,307 @@ const(Token)[] findTokenForDisplay(const Token[] tokens, IdType type, const(Toke
|
||||||
return tokens[i .. i + 1];
|
return tokens[i .. i + 1];
|
||||||
return fallback is null ? tokens : fallback;
|
return fallback is null ? tokens : fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class ScopedBaseAnalyzer : BaseAnalyzer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
this(string fileName, const Scope* sc, bool skipTests = false)
|
||||||
|
{
|
||||||
|
super(fileName, sc, skipTests);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template ScopedVisit(NodeType)
|
||||||
|
{
|
||||||
|
override void visit(const NodeType n)
|
||||||
|
{
|
||||||
|
pushScopeImpl();
|
||||||
|
scope (exit)
|
||||||
|
popScopeImpl();
|
||||||
|
n.accept(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
alias visit = BaseAnalyzer.visit;
|
||||||
|
|
||||||
|
mixin ScopedVisit!BlockStatement;
|
||||||
|
mixin ScopedVisit!ForeachStatement;
|
||||||
|
mixin ScopedVisit!ForStatement;
|
||||||
|
mixin ScopedVisit!IfStatement;
|
||||||
|
mixin ScopedVisit!Module;
|
||||||
|
mixin ScopedVisit!StructBody;
|
||||||
|
mixin ScopedVisit!TemplateDeclaration;
|
||||||
|
mixin ScopedVisit!WithStatement;
|
||||||
|
mixin ScopedVisit!WhileStatement;
|
||||||
|
|
||||||
|
override void visit(const SwitchStatement switchStatement)
|
||||||
|
{
|
||||||
|
switchStack.length++;
|
||||||
|
scope (exit)
|
||||||
|
switchStack.length--;
|
||||||
|
switchStatement.accept(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
static foreach (T; AliasSeq!(CaseStatement, DefaultStatement, CaseRangeStatement))
|
||||||
|
override void visit(const T stmt)
|
||||||
|
{
|
||||||
|
// case and default statements always open new scopes and close
|
||||||
|
// previous case scopes
|
||||||
|
bool close = switchStack.length && switchStack[$ - 1].inCase;
|
||||||
|
bool b = switchStack[$ - 1].inCase;
|
||||||
|
switchStack[$ - 1].inCase = true;
|
||||||
|
scope (exit)
|
||||||
|
switchStack[$ - 1].inCase = b;
|
||||||
|
if (close)
|
||||||
|
{
|
||||||
|
popScope();
|
||||||
|
pushScope();
|
||||||
|
stmt.accept(this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pushScope();
|
||||||
|
stmt.accept(this);
|
||||||
|
popScope();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Called on new scopes, which includes for example:
|
||||||
|
///
|
||||||
|
/// - `module m; /* here, entire file */`
|
||||||
|
/// - `{ /* here */ }`
|
||||||
|
/// - `if () { /* here */ } else { /* here */ }`
|
||||||
|
/// - `foreach (...) { /* here */ }`
|
||||||
|
/// - `case 1: /* here */ break;`
|
||||||
|
/// - `case 1: /* here, up to next case */ goto case; case 2: /* here 2 */ break;`
|
||||||
|
/// - `default: /* here */ break;`
|
||||||
|
/// - `struct S { /* here */ }`
|
||||||
|
///
|
||||||
|
/// But doesn't include:
|
||||||
|
///
|
||||||
|
/// - `static if (x) { /* not a separate scope */ }` (use `mixin ScopedVisit!ConditionalDeclaration;`)
|
||||||
|
///
|
||||||
|
/// You can `mixin ScopedVisit!NodeType` to automatically call push/popScope
|
||||||
|
/// on occurences of that NodeType.
|
||||||
|
abstract void pushScope();
|
||||||
|
/// ditto
|
||||||
|
abstract void popScope();
|
||||||
|
|
||||||
|
void pushScopeImpl()
|
||||||
|
{
|
||||||
|
if (switchStack.length)
|
||||||
|
switchStack[$ - 1].scopeDepth++;
|
||||||
|
pushScope();
|
||||||
|
}
|
||||||
|
|
||||||
|
void popScopeImpl()
|
||||||
|
{
|
||||||
|
if (switchStack.length)
|
||||||
|
switchStack[$ - 1].scopeDepth--;
|
||||||
|
popScope();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SwitchStack
|
||||||
|
{
|
||||||
|
int scopeDepth;
|
||||||
|
bool inCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitchStack[] switchStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
import core.exception : AssertError;
|
||||||
|
import dparse.lexer : getTokensForParser, LexerConfig, StringCache;
|
||||||
|
import dparse.parser : parseModule;
|
||||||
|
import dparse.rollback_allocator : RollbackAllocator;
|
||||||
|
import std.conv : to;
|
||||||
|
import std.exception : assertThrown;
|
||||||
|
|
||||||
|
// test where we can:
|
||||||
|
// call `depth(1);` to check that the scope depth is at 1
|
||||||
|
// if calls are syntactically not valid, define `auto depth = 1;`
|
||||||
|
//
|
||||||
|
// call `isNewScope();` to check that the scope hasn't been checked with isNewScope before
|
||||||
|
// if calls are syntactically not valid, define `auto isNewScope = void;`
|
||||||
|
//
|
||||||
|
// call `isOldScope();` to check that the scope has already been checked with isNewScope
|
||||||
|
// if calls are syntactically not valid, define `auto isOldScope = void;`
|
||||||
|
|
||||||
|
class TestScopedAnalyzer : ScopedBaseAnalyzer
|
||||||
|
{
|
||||||
|
this(size_t codeLine)
|
||||||
|
{
|
||||||
|
super("stdin", null, false);
|
||||||
|
|
||||||
|
this.codeLine = codeLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
override void visit(const FunctionCallExpression f)
|
||||||
|
{
|
||||||
|
int depth = cast(int) stack.length;
|
||||||
|
if (f.unaryExpression && f.unaryExpression.primaryExpression
|
||||||
|
&& f.unaryExpression.primaryExpression.identifierOrTemplateInstance)
|
||||||
|
{
|
||||||
|
auto fname = f.unaryExpression.primaryExpression.identifierOrTemplateInstance.identifier.text;
|
||||||
|
if (fname == "depth")
|
||||||
|
{
|
||||||
|
assert(f.arguments.tokens.length == 3);
|
||||||
|
auto expected = f.arguments.tokens[1].text.to!int;
|
||||||
|
assert(expected == depth, "Expected depth="
|
||||||
|
~ expected.to!string ~ " in line " ~ (codeLine + f.tokens[0].line).to!string
|
||||||
|
~ ", but got depth=" ~ depth.to!string);
|
||||||
|
}
|
||||||
|
else if (fname == "isNewScope")
|
||||||
|
{
|
||||||
|
assert(!stack[$ - 1]);
|
||||||
|
stack[$ - 1] = true;
|
||||||
|
}
|
||||||
|
else if (fname == "isOldScope")
|
||||||
|
{
|
||||||
|
assert(stack[$ - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override void visit(const AutoDeclarationPart p)
|
||||||
|
{
|
||||||
|
int depth = cast(int) stack.length;
|
||||||
|
|
||||||
|
if (p.identifier.text == "depth")
|
||||||
|
{
|
||||||
|
assert(p.initializer.tokens.length == 1);
|
||||||
|
auto expected = p.initializer.tokens[0].text.to!int;
|
||||||
|
assert(expected == depth, "Expected depth="
|
||||||
|
~ expected.to!string ~ " in line " ~ (codeLine + p.tokens[0].line).to!string
|
||||||
|
~ ", but got depth=" ~ depth.to!string);
|
||||||
|
}
|
||||||
|
else if (p.identifier.text == "isNewScope")
|
||||||
|
{
|
||||||
|
assert(!stack[$ - 1]);
|
||||||
|
stack[$ - 1] = true;
|
||||||
|
}
|
||||||
|
else if (p.identifier.text == "isOldScope")
|
||||||
|
{
|
||||||
|
assert(stack[$ - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override void pushScope()
|
||||||
|
{
|
||||||
|
stack.length++;
|
||||||
|
}
|
||||||
|
|
||||||
|
override void popScope()
|
||||||
|
{
|
||||||
|
stack.length--;
|
||||||
|
}
|
||||||
|
|
||||||
|
alias visit = ScopedBaseAnalyzer.visit;
|
||||||
|
|
||||||
|
bool[] stack;
|
||||||
|
size_t codeLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
void testScopes(string code, size_t codeLine = __LINE__ - 1)
|
||||||
|
{
|
||||||
|
StringCache cache = StringCache(4096);
|
||||||
|
LexerConfig config;
|
||||||
|
RollbackAllocator rba;
|
||||||
|
auto tokens = getTokensForParser(code, config, &cache);
|
||||||
|
Module m = parseModule(tokens, "stdin", &rba);
|
||||||
|
|
||||||
|
auto analyzer = new TestScopedAnalyzer(codeLine);
|
||||||
|
analyzer.visit(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
testScopes(q{
|
||||||
|
auto isNewScope = void;
|
||||||
|
auto depth = 1;
|
||||||
|
auto isOldScope = void;
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrown!AssertError(testScopes(q{
|
||||||
|
auto isNewScope = void;
|
||||||
|
auto isNewScope = void;
|
||||||
|
}));
|
||||||
|
|
||||||
|
assertThrown!AssertError(testScopes(q{
|
||||||
|
auto isOldScope = void;
|
||||||
|
}));
|
||||||
|
|
||||||
|
assertThrown!AssertError(testScopes(q{
|
||||||
|
auto depth = 2;
|
||||||
|
}));
|
||||||
|
|
||||||
|
testScopes(q{
|
||||||
|
auto isNewScope = void;
|
||||||
|
auto depth = 1;
|
||||||
|
|
||||||
|
void foo() {
|
||||||
|
isNewScope();
|
||||||
|
isOldScope();
|
||||||
|
depth(2);
|
||||||
|
switch (a)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
isNewScope();
|
||||||
|
depth(4);
|
||||||
|
break;
|
||||||
|
depth(4);
|
||||||
|
isOldScope();
|
||||||
|
case 2:
|
||||||
|
isNewScope();
|
||||||
|
depth(4);
|
||||||
|
if (a)
|
||||||
|
{
|
||||||
|
isNewScope();
|
||||||
|
depth(6);
|
||||||
|
default:
|
||||||
|
isNewScope();
|
||||||
|
depth(6); // since cases/default opens new scope
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
isNewScope();
|
||||||
|
depth(6); // since cases/default opens new scope
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
isNewScope();
|
||||||
|
depth(6); // since cases/default opens new scope
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
depth(4);
|
||||||
|
default:
|
||||||
|
isNewScope();
|
||||||
|
depth(4);
|
||||||
|
break;
|
||||||
|
depth(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
isOldScope();
|
||||||
|
depth(2);
|
||||||
|
|
||||||
|
switch (a)
|
||||||
|
{
|
||||||
|
isNewScope();
|
||||||
|
depth(3);
|
||||||
|
isOldScope();
|
||||||
|
default:
|
||||||
|
isNewScope();
|
||||||
|
depth(4);
|
||||||
|
break;
|
||||||
|
isOldScope();
|
||||||
|
case 1:
|
||||||
|
isNewScope();
|
||||||
|
depth(4);
|
||||||
|
break;
|
||||||
|
isOldScope();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto isOldScope = void;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import dscanner.analysis.helpers;
|
||||||
/**
|
/**
|
||||||
* Checks for labels and variables that have the same name.
|
* Checks for labels and variables that have the same name.
|
||||||
*/
|
*/
|
||||||
final class LabelVarNameCheck : BaseAnalyzer
|
final class LabelVarNameCheck : ScopedBaseAnalyzer
|
||||||
{
|
{
|
||||||
mixin AnalyzerInfo!"label_var_same_name_check";
|
mixin AnalyzerInfo!"label_var_same_name_check";
|
||||||
|
|
||||||
|
@ -22,14 +22,6 @@ final class LabelVarNameCheck : BaseAnalyzer
|
||||||
super(fileName, sc, skipTests);
|
super(fileName, sc, skipTests);
|
||||||
}
|
}
|
||||||
|
|
||||||
mixin ScopedVisit!Module;
|
|
||||||
mixin ScopedVisit!BlockStatement;
|
|
||||||
mixin ScopedVisit!StructBody;
|
|
||||||
mixin ScopedVisit!CaseStatement;
|
|
||||||
mixin ScopedVisit!ForStatement;
|
|
||||||
mixin ScopedVisit!IfStatement;
|
|
||||||
mixin ScopedVisit!TemplateDeclaration;
|
|
||||||
|
|
||||||
mixin AggregateVisit!ClassDeclaration;
|
mixin AggregateVisit!ClassDeclaration;
|
||||||
mixin AggregateVisit!StructDeclaration;
|
mixin AggregateVisit!StructDeclaration;
|
||||||
mixin AggregateVisit!InterfaceDeclaration;
|
mixin AggregateVisit!InterfaceDeclaration;
|
||||||
|
@ -64,7 +56,7 @@ final class LabelVarNameCheck : BaseAnalyzer
|
||||||
--conditionalDepth;
|
--conditionalDepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
alias visit = BaseAnalyzer.visit;
|
alias visit = ScopedBaseAnalyzer.visit;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
@ -80,16 +72,6 @@ private:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template ScopedVisit(NodeType)
|
|
||||||
{
|
|
||||||
override void visit(const NodeType n)
|
|
||||||
{
|
|
||||||
pushScope();
|
|
||||||
n.accept(this);
|
|
||||||
popScope();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void duplicateCheck(const Token name, bool fromLabel, bool isConditional)
|
void duplicateCheck(const Token name, bool fromLabel, bool isConditional)
|
||||||
{
|
{
|
||||||
import std.conv : to;
|
import std.conv : to;
|
||||||
|
@ -128,12 +110,12 @@ private:
|
||||||
return stack[$ - 1];
|
return stack[$ - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
void pushScope()
|
protected override void pushScope()
|
||||||
{
|
{
|
||||||
stack.length++;
|
stack.length++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void popScope()
|
protected override void popScope()
|
||||||
{
|
{
|
||||||
stack.length--;
|
stack.length--;
|
||||||
}
|
}
|
||||||
|
@ -278,6 +260,21 @@ unittest
|
||||||
struct a { int a; }
|
struct a { int a; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
switch (1) {
|
||||||
|
case 1:
|
||||||
|
int x, c1;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
int x, c2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
int x, def;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}c, sac);
|
}c, sac);
|
||||||
stderr.writeln("Unittest for LabelVarNameCheck passed.");
|
stderr.writeln("Unittest for LabelVarNameCheck passed.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import std.range : empty, front, walkLength;
|
||||||
/**
|
/**
|
||||||
* Checks for redundant attributes. At the moment only visibility attributes.
|
* Checks for redundant attributes. At the moment only visibility attributes.
|
||||||
*/
|
*/
|
||||||
final class RedundantAttributesCheck : BaseAnalyzer
|
final class RedundantAttributesCheck : ScopedBaseAnalyzer
|
||||||
{
|
{
|
||||||
mixin AnalyzerInfo!"redundant_attributes_check";
|
mixin AnalyzerInfo!"redundant_attributes_check";
|
||||||
|
|
||||||
|
@ -67,15 +67,8 @@ final class RedundantAttributesCheck : BaseAnalyzer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
alias visit = BaseAnalyzer.visit;
|
alias visit = ScopedBaseAnalyzer.visit;
|
||||||
|
|
||||||
mixin ScopedVisit!Module;
|
|
||||||
mixin ScopedVisit!BlockStatement;
|
|
||||||
mixin ScopedVisit!StructBody;
|
|
||||||
mixin ScopedVisit!CaseStatement;
|
|
||||||
mixin ScopedVisit!ForStatement;
|
|
||||||
mixin ScopedVisit!IfStatement;
|
|
||||||
mixin ScopedVisit!TemplateDeclaration;
|
|
||||||
mixin ScopedVisit!ConditionalDeclaration;
|
mixin ScopedVisit!ConditionalDeclaration;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -153,22 +146,12 @@ private:
|
||||||
return currentAttributes.map!(a => a.attribute.type.str).joiner(",").to!string;
|
return currentAttributes.map!(a => a.attribute.type.str).joiner(",").to!string;
|
||||||
}
|
}
|
||||||
|
|
||||||
template ScopedVisit(NodeType)
|
protected override void pushScope()
|
||||||
{
|
|
||||||
override void visit(const NodeType n)
|
|
||||||
{
|
|
||||||
pushScope();
|
|
||||||
n.accept(this);
|
|
||||||
popScope();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void pushScope()
|
|
||||||
{
|
{
|
||||||
stack.length++;
|
stack.length++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void popScope()
|
protected override void popScope()
|
||||||
{
|
{
|
||||||
stack.length--;
|
stack.length--;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue