Skip to content

Commit 01bc466

Browse files
cirrasfourls
authored andcommitted
Improve usage of local scopes in the symbol table
1 parent ca8bc71 commit 01bc466

File tree

8 files changed

+234
-1
lines changed

8 files changed

+234
-1
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2222
- "Separate grouped parameters" quick fix for `GroupedParameterDeclaration`.
2323
- "Remove (n) unused formatting arguments" quick fix for `FormatArgumentCount`.
2424
- "Use string value directly" quick fix for `FormatArgumentCount`.
25+
- **API:** `TryStatementNode::getExceptBlock` method.
26+
- **API:** `WhileStatementNode::getGuardExpression` method.
27+
- **API:** `WhileStatementNode::getStatement` method.
2528
- **API:** `DelphiTokenType.WINAPI` token type.
2629
- **API:** `DelphiIssueBuilder` type, which is returned by `DelphiCheckContext::newIssue`.
2730
- **API:** `QuickFix` type, which is accepted by `DelphiIssueBuilder::withQuickFixes`.
2831
- **API:** `QuickFixEdit` type, which is accepted by `QuickFix::addEdits`.
2932

33+
### Deprecated
34+
35+
- **API:** `TryStatementNode::getExpectBlock` method, use `getExceptBlock` instead.
36+
3037
### Fixed
3138

3239
- Exception when parsing fully qualified attribute references.
40+
- `DuplicatedDeclarationException` errors caused by some local scopes being modeled incorrectly.
3341

3442
## [1.4.0] - 2024-04-02
3543

delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/TryStatementNodeImpl.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,14 @@ public boolean hasFinallyBlock() {
5353
return getChild(1) instanceof FinallyBlockNode;
5454
}
5555

56+
@SuppressWarnings("removal")
5657
@Override
5758
public ExceptBlockNode getExpectBlock() {
59+
return getExceptBlock();
60+
}
61+
62+
@Override
63+
public ExceptBlockNode getExceptBlock() {
5864
if (exceptBlock == null && hasExceptBlock()) {
5965
exceptBlock = (ExceptBlockNode) getChild(1);
6066
}

delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/WhileStatementNodeImpl.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
package au.com.integradev.delphi.antlr.ast.node;
2020

2121
import au.com.integradev.delphi.antlr.ast.visitors.DelphiParserVisitor;
22+
import javax.annotation.Nullable;
2223
import org.antlr.runtime.Token;
24+
import org.sonar.plugins.communitydelphi.api.ast.ExpressionNode;
25+
import org.sonar.plugins.communitydelphi.api.ast.StatementNode;
2326
import org.sonar.plugins.communitydelphi.api.ast.WhileStatementNode;
2427

2528
public final class WhileStatementNodeImpl extends DelphiNodeImpl implements WhileStatementNode {
@@ -31,4 +34,15 @@ public WhileStatementNodeImpl(Token token) {
3134
public <T> T accept(DelphiParserVisitor<T> visitor, T data) {
3235
return visitor.visit(this, data);
3336
}
37+
38+
@Override
39+
public ExpressionNode getGuardExpression() {
40+
return (ExpressionNode) getChild(0);
41+
}
42+
43+
@Override
44+
@Nullable
45+
public StatementNode getStatement() {
46+
return (StatementNode) getChild(2);
47+
}
3448
}

delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/SymbolTableVisitor.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,18 @@
6161
import org.sonar.plugins.communitydelphi.api.ast.AnonymousMethodNode;
6262
import org.sonar.plugins.communitydelphi.api.ast.ArrayConstructorNode;
6363
import org.sonar.plugins.communitydelphi.api.ast.AttributeNode;
64+
import org.sonar.plugins.communitydelphi.api.ast.CaseItemStatementNode;
6465
import org.sonar.plugins.communitydelphi.api.ast.CaseStatementNode;
6566
import org.sonar.plugins.communitydelphi.api.ast.ClassReferenceTypeNode;
6667
import org.sonar.plugins.communitydelphi.api.ast.CompoundStatementNode;
6768
import org.sonar.plugins.communitydelphi.api.ast.ConstDeclarationNode;
6869
import org.sonar.plugins.communitydelphi.api.ast.ConstStatementNode;
6970
import org.sonar.plugins.communitydelphi.api.ast.DelphiAst;
7071
import org.sonar.plugins.communitydelphi.api.ast.DelphiNode;
72+
import org.sonar.plugins.communitydelphi.api.ast.ElseBlockNode;
7173
import org.sonar.plugins.communitydelphi.api.ast.EnumElementNode;
7274
import org.sonar.plugins.communitydelphi.api.ast.EnumTypeNode;
75+
import org.sonar.plugins.communitydelphi.api.ast.ExceptBlockNode;
7376
import org.sonar.plugins.communitydelphi.api.ast.ExceptItemNode;
7477
import org.sonar.plugins.communitydelphi.api.ast.ExpressionNode;
7578
import org.sonar.plugins.communitydelphi.api.ast.FieldDeclarationNode;
@@ -83,9 +86,11 @@
8386
import org.sonar.plugins.communitydelphi.api.ast.FormalParameterNode.FormalParameterData;
8487
import org.sonar.plugins.communitydelphi.api.ast.GenericDefinitionNode;
8588
import org.sonar.plugins.communitydelphi.api.ast.GenericDefinitionNode.TypeParameter;
89+
import org.sonar.plugins.communitydelphi.api.ast.IfStatementNode;
8690
import org.sonar.plugins.communitydelphi.api.ast.ImplementationSectionNode;
8791
import org.sonar.plugins.communitydelphi.api.ast.InitializationSectionNode;
8892
import org.sonar.plugins.communitydelphi.api.ast.InterfaceSectionNode;
93+
import org.sonar.plugins.communitydelphi.api.ast.LabelStatementNode;
8994
import org.sonar.plugins.communitydelphi.api.ast.MethodResolutionClauseNode;
9095
import org.sonar.plugins.communitydelphi.api.ast.NameDeclarationNode;
9196
import org.sonar.plugins.communitydelphi.api.ast.NameReferenceNode;
@@ -102,6 +107,7 @@
102107
import org.sonar.plugins.communitydelphi.api.ast.RoutineNameNode;
103108
import org.sonar.plugins.communitydelphi.api.ast.RoutineNode;
104109
import org.sonar.plugins.communitydelphi.api.ast.RoutineParametersNode;
110+
import org.sonar.plugins.communitydelphi.api.ast.StatementNode;
105111
import org.sonar.plugins.communitydelphi.api.ast.TryStatementNode;
106112
import org.sonar.plugins.communitydelphi.api.ast.TypeDeclarationNode;
107113
import org.sonar.plugins.communitydelphi.api.ast.TypeNode;
@@ -111,6 +117,7 @@
111117
import org.sonar.plugins.communitydelphi.api.ast.UnitImportNode;
112118
import org.sonar.plugins.communitydelphi.api.ast.VarDeclarationNode;
113119
import org.sonar.plugins.communitydelphi.api.ast.VarStatementNode;
120+
import org.sonar.plugins.communitydelphi.api.ast.WhileStatementNode;
114121
import org.sonar.plugins.communitydelphi.api.ast.WithStatementNode;
115122
import org.sonar.plugins.communitydelphi.api.directive.SwitchDirective.SwitchKind;
116123
import org.sonar.plugins.communitydelphi.api.symbol.declaration.EnumElementNameDeclaration;
@@ -534,6 +541,43 @@ public Data visit(AnonymousMethodNode node, Data data) {
534541

535542
@Override
536543
public Data visit(TryStatementNode node, Data data) {
544+
createLocalScope(node.getStatementList(), data);
545+
if (node.hasExceptBlock()) {
546+
ExceptBlockNode exceptBlock = node.getExceptBlock();
547+
if (exceptBlock.isBareExcept()) {
548+
createLocalScope(exceptBlock.getStatementList(), data);
549+
} else {
550+
DelphiParserVisitor.super.visit(exceptBlock, data);
551+
}
552+
} else if (node.hasFinallyBlock()) {
553+
return createLocalScope(node.getFinallyBlock().getStatementList(), data);
554+
}
555+
return data;
556+
}
557+
558+
@Override
559+
public Data visit(IfStatementNode node, Data data) {
560+
DelphiParserVisitor.super.visit(node.getGuardExpression(), data);
561+
562+
StatementNode thenStatement = node.getThenStatement();
563+
if (thenStatement instanceof CompoundStatementNode) {
564+
DelphiParserVisitor.super.visit(thenStatement, data);
565+
} else if (thenStatement != null) {
566+
createLocalScope(thenStatement, data);
567+
}
568+
569+
StatementNode elseStatement = node.getElseStatement();
570+
if (elseStatement instanceof CompoundStatementNode) {
571+
DelphiParserVisitor.super.visit(elseStatement, data);
572+
} else if (elseStatement != null) {
573+
createLocalScope(elseStatement, data);
574+
}
575+
576+
return data;
577+
}
578+
579+
@Override
580+
public Data visit(ElseBlockNode node, Data data) {
537581
return createLocalScope(node, data);
538582
}
539583

@@ -566,6 +610,11 @@ public Data visit(ForLoopVarReferenceNode node, Data data) {
566610
return DelphiParserVisitor.super.visit(node, data);
567611
}
568612

613+
@Override
614+
public Data visit(WhileStatementNode node, Data data) {
615+
return createLocalScope(node, data);
616+
}
617+
569618
@Override
570619
public Data visit(RepeatStatementNode node, Data data) {
571620
return createLocalScope(node, data);
@@ -612,6 +661,23 @@ public Data visit(CaseStatementNode node, Data data) {
612661
return createLocalScope(node, data);
613662
}
614663

664+
@Override
665+
public Data visit(CaseItemStatementNode node, Data data) {
666+
return createLocalScope(node, data);
667+
}
668+
669+
@Override
670+
public Data visit(LabelStatementNode node, Data data) {
671+
// Bizarre behavior:
672+
// If a label is the first statement in a given scope (routine, local, etc.), then it
673+
// doesn't get its own local scope!
674+
// This is probably a poorly thought-out compiler optimization.
675+
if (node.getChildIndex() > 0) {
676+
return createLocalScope(node, data);
677+
}
678+
return DelphiParserVisitor.super.visit(node, data);
679+
}
680+
615681
@Override
616682
public Data visit(ConstStatementNode node, Data data) {
617683
return handleVarDeclaration(node, node.getTypeNode(), data);

delphi-frontend/src/main/java/org/sonar/plugins/communitydelphi/api/ast/TryStatementNode.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,49 @@
1818
*/
1919
package org.sonar.plugins.communitydelphi.api.ast;
2020

21+
/** {@code try} statement. */
2122
public interface TryStatementNode extends StatementNode {
23+
/**
24+
* Returns the list of statements from within this {@code try} statement.
25+
*
26+
* @return statement list from within this {@code try} statement
27+
*/
2228
StatementListNode getStatementList();
2329

30+
/**
31+
* Returns whether this {@code try} statement has an {@code except} block.
32+
*
33+
* @return true if this {@code try} statement has an {@code except} block
34+
*/
2435
boolean hasExceptBlock();
2536

37+
/**
38+
* Returns whether this {@code try} statement has a {@code finally} block.
39+
*
40+
* @return true if this {@code try} statement has a {@code finally} block
41+
*/
2642
boolean hasFinallyBlock();
2743

44+
/**
45+
* Returns the {@code except} block for this {@code try} statement.
46+
*
47+
* @return the {@code except} block for this {@code try} statement
48+
* @deprecated Use {@link TryStatementNode#getExceptBlock()} instead
49+
*/
50+
@Deprecated(forRemoval = true)
2851
ExceptBlockNode getExpectBlock();
2952

53+
/**
54+
* Returns the {@code except} block for this {@code try} statement.
55+
*
56+
* @return the {@code except} block for this {@code try} statement
57+
*/
58+
ExceptBlockNode getExceptBlock();
59+
60+
/**
61+
* Returns the {@code finally} block for this {@code try} statement.
62+
*
63+
* @return the {@code finally} block for this {@code try} statement
64+
*/
3065
FinallyBlockNode getFinallyBlock();
3166
}

delphi-frontend/src/main/java/org/sonar/plugins/communitydelphi/api/ast/WhileStatementNode.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,23 @@
1818
*/
1919
package org.sonar.plugins.communitydelphi.api.ast;
2020

21-
public interface WhileStatementNode extends StatementNode {}
21+
import javax.annotation.Nullable;
22+
23+
/** {@code while} loop statement. */
24+
public interface WhileStatementNode extends StatementNode {
25+
/**
26+
* Returns the guard expression for this {@code while} loop.
27+
*
28+
* @return the guard expression for this {@code while} loop
29+
*/
30+
ExpressionNode getGuardExpression();
31+
32+
/**
33+
* Returns the statement that will be executed in this {@code while} loop, also known as the loop
34+
* body.
35+
*
36+
* @return the statement that will be executed in this {@code while} loop
37+
*/
38+
@Nullable
39+
StatementNode getStatement();
40+
}

delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,13 @@ void testBareInterfaceMethodReference() {
380380
verifyUsages(10, 10, reference(17, 2));
381381
}
382382

383+
@Test
384+
void testLocalScopes() {
385+
execute("LocalScopes.pas");
386+
verifyUsages(68, 8, reference(69, 10));
387+
verifyUsages(73, 10, reference(74, 12));
388+
}
389+
383390
@Test
384391
void testClassReferenceMethodResolution() {
385392
execute("classReferences/MethodResolution.pas");
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
unit LocalScopes;
2+
3+
interface
4+
5+
implementation
6+
7+
procedure TestNoDuplicatedDeclarationException;
8+
label Baz;
9+
begin
10+
begin
11+
var Foo := 'bar';
12+
end;
13+
14+
if True then
15+
var Foo := 'bar'
16+
else
17+
var Foo := 'bar';
18+
19+
case True of
20+
True: var Foo := 'bar';
21+
False: var Foo := 'bar';
22+
else var Foo := 'bar';
23+
end;
24+
25+
repeat
26+
var Foo := 'bar';
27+
until False;
28+
29+
while False do begin
30+
var Foo := 'bar';
31+
end;
32+
33+
try
34+
var Foo := 'bar';
35+
finally
36+
var Foo := 'bar';
37+
end;
38+
39+
try
40+
var Foo := 'bar';
41+
except
42+
var Foo := 'bar';
43+
end;
44+
45+
try
46+
var Foo := 'bar';
47+
except
48+
on TObject do begin
49+
var Foo := 'bar';
50+
end
51+
else
52+
var Foo := 'bar';
53+
end;
54+
55+
while True do
56+
var Foo := 'bar';
57+
58+
Baz:
59+
var Foo := 'bar';
60+
61+
var Foo := 'bar';
62+
end;
63+
64+
procedure TestLabelScopeFlatteningBehavior;
65+
label Baz, Flarp;
66+
begin
67+
Baz:
68+
var Foo := 'bar';
69+
WriteLn(Foo);
70+
71+
begin
72+
Flarp:
73+
var Boop := 'beep';
74+
WriteLn(Boop);
75+
end;
76+
end;
77+
78+
end.

0 commit comments

Comments
 (0)