Skip to content

Commit 0e845d1

Browse files
SONARPY-1807 SymbolTableBuilderV2 cleanup
1 parent 000c244 commit 0e845d1

File tree

5 files changed

+534
-499
lines changed

5 files changed

+534
-499
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2024 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.python.semantic.v2;
21+
22+
import java.util.Map;
23+
import org.sonar.plugins.python.api.tree.ClassDef;
24+
import org.sonar.plugins.python.api.tree.ComprehensionExpression;
25+
import org.sonar.plugins.python.api.tree.ComprehensionFor;
26+
import org.sonar.plugins.python.api.tree.FileInput;
27+
import org.sonar.plugins.python.api.tree.FunctionDef;
28+
import org.sonar.plugins.python.api.tree.LambdaExpression;
29+
import org.sonar.plugins.python.api.tree.Name;
30+
import org.sonar.plugins.python.api.tree.Parameter;
31+
import org.sonar.plugins.python.api.tree.Tree;
32+
import org.sonar.plugins.python.api.tree.TypeAnnotation;
33+
import org.sonar.python.tree.DictCompExpressionImpl;
34+
35+
/**
36+
* Read (i.e. non-binding) usages have to be visited in a second phase.
37+
* They can't be visited in the same phase as write (i.e. binding) usages,
38+
* since a read usage may appear in the syntax tree "before" it's declared (written).
39+
*/
40+
public class ReadUsagesVisitor extends ScopeVisitor {
41+
public ReadUsagesVisitor(Map<Tree, ScopeV2> scopesByRootTree) {
42+
super(scopesByRootTree);
43+
}
44+
45+
@Override
46+
public void visitFileInput(FileInput tree) {
47+
enterScope(tree);
48+
super.visitFileInput(tree);
49+
}
50+
51+
@Override
52+
public void visitFunctionDef(FunctionDef pyFunctionDefTree) {
53+
scan(pyFunctionDefTree.decorators());
54+
enterScope(pyFunctionDefTree);
55+
scan(pyFunctionDefTree.name());
56+
scan(pyFunctionDefTree.typeParams());
57+
scan(pyFunctionDefTree.parameters());
58+
scan(pyFunctionDefTree.returnTypeAnnotation());
59+
scan(pyFunctionDefTree.body());
60+
leaveScope();
61+
}
62+
63+
@Override
64+
public void visitParameter(Parameter parameter) {
65+
// parameter default value should not be in the function scope.
66+
Tree currentScopeTree = leaveScope();
67+
scan(parameter.defaultValue());
68+
enterScope(currentScopeTree);
69+
scan(parameter.name());
70+
scan(parameter.typeAnnotation());
71+
}
72+
73+
@Override
74+
public void visitLambda(LambdaExpression pyLambdaExpressionTree) {
75+
enterScope(pyLambdaExpressionTree);
76+
super.visitLambda(pyLambdaExpressionTree);
77+
leaveScope();
78+
}
79+
80+
@Override
81+
public void visitPyListOrSetCompExpression(ComprehensionExpression tree) {
82+
enterScope(tree);
83+
scan(tree.resultExpression());
84+
ComprehensionFor comprehensionFor = tree.comprehensionFor();
85+
scan(comprehensionFor.loopExpression());
86+
leaveScope();
87+
scan(comprehensionFor.iterable());
88+
enterScope(tree);
89+
scan(comprehensionFor.nestedClause());
90+
leaveScope();
91+
}
92+
93+
@Override
94+
public void visitDictCompExpression(DictCompExpressionImpl tree) {
95+
enterScope(tree);
96+
scan(tree.keyExpression());
97+
scan(tree.valueExpression());
98+
ComprehensionFor comprehensionFor = tree.comprehensionFor();
99+
scan(comprehensionFor.loopExpression());
100+
leaveScope();
101+
scan(comprehensionFor.iterable());
102+
enterScope(tree);
103+
scan(comprehensionFor.nestedClause());
104+
leaveScope();
105+
}
106+
107+
@Override
108+
public void visitTypeAnnotation(TypeAnnotation tree) {
109+
if (tree.is(Tree.Kind.PARAMETER_TYPE_ANNOTATION) || tree.is(Tree.Kind.RETURN_TYPE_ANNOTATION)) {
110+
// The scope of the type annotations on a function declaration should be the scope enclosing the function, and not the scope of
111+
// the function body itself. Note that this code assumes that we already entered the function body scope by visiting the type
112+
// annotations, so there should always be a scope to pop out and return to here.
113+
Tree currentScopeTree = leaveScope();
114+
super.visitTypeAnnotation(tree);
115+
enterScope(currentScopeTree);
116+
super.visitTypeAnnotation(tree);
117+
} else {
118+
super.visitTypeAnnotation(tree);
119+
}
120+
}
121+
122+
@Override
123+
public void visitClassDef(ClassDef classDef) {
124+
scan(classDef.args());
125+
scan(classDef.decorators());
126+
enterScope(classDef);
127+
scan(classDef.name());
128+
scan(classDef.body());
129+
leaveScope();
130+
}
131+
132+
@Override
133+
public void visitName(Name name) {
134+
if (!name.isVariable()) {
135+
return;
136+
}
137+
addSymbolUsage(name);
138+
super.visitName(name);
139+
}
140+
141+
private void addSymbolUsage(Name name) {
142+
var scope = currentScope();
143+
var symbol = scope.resolve(name.name());
144+
if (symbol != null && symbol.usages().stream().noneMatch(usage -> usage.tree().equals(name))) {
145+
symbol.addUsage(name, UsageV2.Kind.OTHER);
146+
}
147+
}
148+
}

python-frontend/src/main/java/org/sonar/python/semantic/v2/ScopeV2.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,11 @@
3434
public class ScopeV2 {
3535
private final ScopeV2 parent;
3636
private final Tree rootTree;
37-
3837
private final List<ScopeV2> childrenScopes;
39-
40-
final Map<String, SymbolV2> symbolsByName = new HashMap<>();
38+
private final Map<String, SymbolV2> symbolsByName = new HashMap<>();
4139
private final Set<SymbolV2> symbols = new HashSet<>();
4240

43-
public ScopeV2(ScopeV2 parent, Tree rootTree) {
41+
public ScopeV2(@Nullable ScopeV2 parent, Tree rootTree) {
4442
this.parent = parent;
4543
this.rootTree = rootTree;
4644
this.childrenScopes = new ArrayList<>();
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2024 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.python.semantic.v2;
21+
22+
import java.util.Deque;
23+
import java.util.LinkedList;
24+
import java.util.Map;
25+
import javax.annotation.Nullable;
26+
import org.sonar.plugins.python.api.tree.BaseTreeVisitor;
27+
import org.sonar.plugins.python.api.tree.Tree;
28+
29+
public class ScopeVisitor extends BaseTreeVisitor {
30+
31+
private final Map<Tree, ScopeV2> scopesByRootTree;
32+
private final Deque<Tree> scopeRootTrees;
33+
34+
public ScopeVisitor(Map<Tree, ScopeV2> scopesByRootTree) {
35+
this.scopesByRootTree = scopesByRootTree;
36+
this.scopeRootTrees = new LinkedList<>();
37+
}
38+
39+
Tree currentScopeRootTree() {
40+
return scopeRootTrees.peek();
41+
}
42+
43+
void enterScope(Tree tree) {
44+
scopeRootTrees.push(tree);
45+
}
46+
47+
Tree leaveScope() {
48+
return scopeRootTrees.pop();
49+
}
50+
51+
ScopeV2 currentScope() {
52+
return scopesByRootTree.get(currentScopeRootTree());
53+
}
54+
55+
void createScope(Tree tree, @Nullable ScopeV2 parent) {
56+
scopesByRootTree.put(tree, new ScopeV2(parent, tree));
57+
}
58+
59+
void createAndEnterScope(Tree tree, @Nullable ScopeV2 parent) {
60+
createScope(tree, parent);
61+
enterScope(tree);
62+
}
63+
64+
}

0 commit comments

Comments
 (0)