22
22
import java .util .HashSet ;
23
23
import java .util .Iterator ;
24
24
import java .util .Map ;
25
+ import java .util .Optional ;
25
26
import java .util .Set ;
26
27
import java .util .stream .Collectors ;
28
+ import java .util .stream .Stream ;
29
+ import javax .annotation .CheckForNull ;
30
+ import org .sonar .plugins .python .api .tree .Expression ;
31
+ import org .sonar .plugins .python .api .tree .Name ;
32
+ import org .sonar .plugins .python .api .tree .Tree ;
27
33
import org .sonar .python .semantic .v2 .SymbolV2 ;
34
+ import org .sonar .python .semantic .v2 .SymbolV2Utils ;
28
35
import org .sonar .python .semantic .v2 .TypeTable ;
36
+ import org .sonar .python .semantic .v2 .UsageV2 ;
37
+ import org .sonar .python .tree .NameImpl ;
38
+ import org .sonar .python .tree .TreeUtils ;
29
39
import org .sonar .python .types .v2 .PythonType ;
40
+ import org .sonar .python .types .v2 .UnionType ;
30
41
31
42
public class AstBasedPropagation {
32
43
private final Map <SymbolV2 , Set <Propagation >> propagationsByLhs ;
33
- private final TypeTable typeTable ;
44
+ private final Propagator propagator ;
34
45
35
46
public AstBasedPropagation (Map <SymbolV2 , Set <Propagation >> propagationsByLhs , TypeTable typeTable ) {
36
47
this .propagationsByLhs = propagationsByLhs ;
37
- this .typeTable = typeTable ;
48
+ this .propagator = new Propagator ( typeTable ) ;
38
49
}
39
50
40
51
public Map <SymbolV2 , Set <PythonType >> processPropagations (Set <SymbolV2 > trackedVars ) {
@@ -56,18 +67,76 @@ public Map<SymbolV2, Set<PythonType>> processPropagations(Set<SymbolV2> trackedV
56
67
return propagations .stream ().collect (Collectors .groupingBy (Propagation ::lhsSymbol , Collectors .mapping (Propagation ::rhsType , Collectors .toSet ())));
57
68
}
58
69
59
- private static void applyPropagations (Set <Propagation > propagations , Set <SymbolV2 > initializedVars , boolean checkDependenciesReadiness ) {
70
+ private void applyPropagations (Set <Propagation > propagations , Set <SymbolV2 > initializedVars , boolean checkDependenciesReadiness ) {
60
71
Set <Propagation > workSet = new HashSet <>(propagations );
61
72
while (!workSet .isEmpty ()) {
62
73
Iterator <Propagation > iterator = workSet .iterator ();
63
74
Propagation propagation = iterator .next ();
64
75
iterator .remove ();
65
76
if (!checkDependenciesReadiness || propagation .areDependenciesReady (initializedVars )) {
66
- boolean learnt = propagation .propagate (initializedVars );
77
+ boolean learnt = propagator .propagate (propagation , initializedVars );
67
78
if (learnt ) {
68
79
workSet .addAll (propagation .dependents ());
69
80
}
70
81
}
71
82
}
72
83
}
84
+
85
+ private record Propagator (TypeTable typeTable ) {
86
+ /**
87
+ * @return true if the propagation effectively changed the inferred type of assignment LHS
88
+ */
89
+ public boolean propagate (Propagation propagation , Set <SymbolV2 > initializedVars ) {
90
+ PythonType rhsType = propagation .rhsType ();
91
+ Name lhsName = propagation .lhsName ();
92
+ SymbolV2 lhsSymbol = propagation .lhsSymbol ();
93
+ if (initializedVars .add (lhsSymbol )) {
94
+ propagateTypeToUsages (propagation , rhsType );
95
+ return true ;
96
+ } else {
97
+ PythonType currentType = currentType (lhsName );
98
+ if (currentType == null ) {
99
+ return false ;
100
+ }
101
+ PythonType newType = UnionType .or (rhsType , currentType );
102
+ propagateTypeToUsages (propagation , newType );
103
+ return !newType .equals (currentType );
104
+ }
105
+ }
106
+
107
+ private void propagateTypeToUsages (Propagation propagation , PythonType newType ) {
108
+ Tree scopeTree = propagation .scopeTree (propagation .lhsName ());
109
+ getSymbolNonDeclarationUsageTrees (propagation .lhsSymbol )
110
+ .filter (NameImpl .class ::isInstance )
111
+ .map (NameImpl .class ::cast )
112
+ // Avoid propagation to usages in nested scopes, as this may lead to FPs
113
+ .filter (n -> isInSameScope (propagation , n , scopeTree ))
114
+ .forEach (n -> n .typeV2 (newType ));
115
+ }
116
+
117
+ @ CheckForNull
118
+ private static PythonType currentType (Name lhsName ) {
119
+ return Optional .ofNullable (lhsName .symbolV2 ())
120
+ .stream ()
121
+ .flatMap (Propagator ::getSymbolNonDeclarationUsageTrees )
122
+ .flatMap (TreeUtils .toStreamInstanceOfMapper (Expression .class ))
123
+ .findFirst ()
124
+ .map (Expression ::typeV2 )
125
+ .orElse (null );
126
+ }
127
+
128
+ public static Stream <Tree > getSymbolNonDeclarationUsageTrees (SymbolV2 symbol ) {
129
+ return symbol .usages ()
130
+ .stream ()
131
+ // Function and class definition names will always have FunctionType and ClassType respectively
132
+ // so they are filtered out of type propagation
133
+ .filter (u -> !SymbolV2Utils .isDeclaration (u ))
134
+ .map (UsageV2 ::tree );
135
+ }
136
+
137
+ private boolean isInSameScope (Propagation propagation , Name n , Tree scopeTree ) {
138
+ return Optional .ofNullable (propagation .scopeTree (n )).filter (scopeTree ::equals ).isPresent ();
139
+ }
140
+ }
141
+
73
142
}
0 commit comments