@@ -11,25 +11,30 @@ import { isEmpty } from '../../helpers/collectionUtils.js';
11
11
import type { SafeDsCallGraphComputer } from '../flow/safe-ds-call-graph-computer.js' ;
12
12
import type { SafeDsServices } from '../safe-ds-module.js' ;
13
13
import {
14
+ EndlessRecursion ,
14
15
FileRead ,
15
16
FileWrite ,
16
17
type ImpurityReason ,
17
18
OtherImpurityReason ,
18
19
PotentiallyImpureParameterCall ,
19
20
} from './model.js' ;
20
21
import {
22
+ isSdsAssignment ,
23
+ isSdsExpressionStatement ,
21
24
isSdsFunction ,
22
25
isSdsLambda ,
26
+ isSdsWildcard ,
23
27
SdsCall ,
24
28
SdsCallable ,
25
29
SdsExpression ,
26
30
SdsFunction ,
27
31
SdsParameter ,
32
+ SdsStatement ,
28
33
} from '../generated/ast.js' ;
29
34
import { EvaluatedEnumVariant , ParameterSubstitutions , StringConstant } from '../partialEvaluation/model.js' ;
30
35
import { SafeDsAnnotations } from '../builtins/safe-ds-annotations.js' ;
31
36
import { SafeDsImpurityReasons } from '../builtins/safe-ds-enums.js' ;
32
- import { getParameters } from '../helpers/nodeProperties.js' ;
37
+ import { getAssignees , getParameters } from '../helpers/nodeProperties.js' ;
33
38
import { isContainedInOrEqual } from '../helpers/astUtils.js' ;
34
39
35
40
export class SafeDsPurityComputer {
@@ -63,7 +68,7 @@ export class SafeDsPurityComputer {
63
68
* The parameter substitutions to use. These are **not** the argument of a call, but the values of the parameters
64
69
* of any containing callables, i.e. the context of the node.
65
70
*/
66
- isPureCallable ( node : SdsCallable , substitutions = NO_SUBSTITUTIONS ) : boolean {
71
+ isPureCallable ( node : SdsCallable | undefined , substitutions = NO_SUBSTITUTIONS ) : boolean {
67
72
return isEmpty ( this . getImpurityReasonsForCallable ( node , substitutions ) ) ;
68
73
}
69
74
@@ -77,7 +82,7 @@ export class SafeDsPurityComputer {
77
82
* The parameter substitutions to use. These are **not** the argument of a call, but the values of the parameters
78
83
* of any containing callables, i.e. the context of the node.
79
84
*/
80
- isPureExpression ( node : SdsExpression , substitutions = NO_SUBSTITUTIONS ) : boolean {
85
+ isPureExpression ( node : SdsExpression | undefined , substitutions = NO_SUBSTITUTIONS ) : boolean {
81
86
return isEmpty ( this . getImpurityReasonsForExpression ( node , substitutions ) ) ;
82
87
}
83
88
@@ -91,7 +96,7 @@ export class SafeDsPurityComputer {
91
96
* The parameter substitutions to use. These are **not** the argument of a call, but the values of the parameters
92
97
* of any containing callables, i.e. the context of the node.
93
98
*/
94
- callableHasSideEffects ( node : SdsCallable , substitutions = NO_SUBSTITUTIONS ) : boolean {
99
+ callableHasSideEffects ( node : SdsCallable | undefined , substitutions = NO_SUBSTITUTIONS ) : boolean {
95
100
return this . getImpurityReasonsForCallable ( node , substitutions ) . some ( ( it ) => it . isSideEffect ) ;
96
101
}
97
102
@@ -105,10 +110,37 @@ export class SafeDsPurityComputer {
105
110
* The parameter substitutions to use. These are **not** the argument of a call, but the values of the parameters
106
111
* of any containing callables, i.e. the context of the node.
107
112
*/
108
- expressionHasSideEffects ( node : SdsExpression , substitutions = NO_SUBSTITUTIONS ) : boolean {
113
+ expressionHasSideEffects ( node : SdsExpression | undefined , substitutions = NO_SUBSTITUTIONS ) : boolean {
109
114
return this . getImpurityReasonsForExpression ( node , substitutions ) . some ( ( it ) => it . isSideEffect ) ;
110
115
}
111
116
117
+ /**
118
+ * Returns whether the given statement does something. It must either
119
+ * - create a placeholder,
120
+ * - assign to a result, or
121
+ * - call a function that has side effects.
122
+ *
123
+ * @param node
124
+ * The statement to check.
125
+ *
126
+ * @param substitutions
127
+ * The parameter substitutions to use. These are **not** the argument of a call, but the values of the parameters
128
+ * of any containing callables, i.e. the context of the node.
129
+ */
130
+ statementDoesSomething ( node : SdsStatement , substitutions = NO_SUBSTITUTIONS ) : boolean {
131
+ if ( isSdsAssignment ( node ) ) {
132
+ return (
133
+ ! getAssignees ( node ) . every ( isSdsWildcard ) ||
134
+ this . expressionHasSideEffects ( node . expression , substitutions )
135
+ ) ;
136
+ } else if ( isSdsExpressionStatement ( node ) ) {
137
+ return this . expressionHasSideEffects ( node . expression , substitutions ) ;
138
+ } else {
139
+ /* c8 ignore next 2 */
140
+ return false ;
141
+ }
142
+ }
143
+
112
144
/**
113
145
* Returns the reasons why the given callable is impure.
114
146
*
@@ -119,7 +151,7 @@ export class SafeDsPurityComputer {
119
151
* The parameter substitutions to use. These are **not** the argument of a call, but the values of the parameters
120
152
* of any containing callables, i.e. the context of the node.
121
153
*/
122
- getImpurityReasonsForCallable ( node : SdsCallable , substitutions = NO_SUBSTITUTIONS ) : ImpurityReason [ ] {
154
+ getImpurityReasonsForCallable ( node : SdsCallable | undefined , substitutions = NO_SUBSTITUTIONS ) : ImpurityReason [ ] {
123
155
return this . getImpurityReasons ( node , substitutions ) ;
124
156
}
125
157
@@ -133,28 +165,54 @@ export class SafeDsPurityComputer {
133
165
* The parameter substitutions to use. These are **not** the argument of a call, but the values of the parameters
134
166
* of any containing callables, i.e. the context of the node.
135
167
*/
136
- getImpurityReasonsForExpression ( node : SdsExpression , substitutions = NO_SUBSTITUTIONS ) : ImpurityReason [ ] {
168
+ getImpurityReasonsForExpression (
169
+ node : SdsExpression | undefined ,
170
+ substitutions = NO_SUBSTITUTIONS ,
171
+ ) : ImpurityReason [ ] {
137
172
return this . getExecutedCallsInExpression ( node ) . flatMap ( ( it ) => this . getImpurityReasons ( it , substitutions ) ) ;
138
173
}
139
174
140
- private getImpurityReasons ( node : SdsCall | SdsCallable , substitutions = NO_SUBSTITUTIONS ) : ImpurityReason [ ] {
141
- const key = this . getNodeId ( node ) ;
142
- return this . reasonsCache . get ( key , ( ) => {
143
- return this . callGraphComputer
144
- . getCallGraph ( node , substitutions )
145
- . streamCalledCallables ( )
146
- . flatMap ( ( it ) => {
147
- if ( isSdsFunction ( it ) ) {
148
- return this . getImpurityReasonsForFunction ( it ) ;
149
- } else {
150
- return EMPTY_STREAM ;
151
- }
152
- } )
153
- . toArray ( ) ;
175
+ private getImpurityReasons (
176
+ node : SdsCall | SdsCallable | undefined ,
177
+ substitutions = NO_SUBSTITUTIONS ,
178
+ ) : ImpurityReason [ ] {
179
+ if ( ! node ) {
180
+ /* c8 ignore next 2 */
181
+ return [ ] ;
182
+ }
183
+
184
+ // Cache the result if no substitutions are given
185
+ if ( isEmpty ( substitutions ) ) {
186
+ const key = this . getNodeId ( node ) ;
187
+ return this . reasonsCache . get ( key , ( ) => {
188
+ return this . doGetImpurityReasons ( node , substitutions ) ;
189
+ } ) ;
190
+ } else {
191
+ /* c8 ignore next 2 */
192
+ return this . doGetImpurityReasons ( node , substitutions ) ;
193
+ }
194
+ }
195
+
196
+ private doGetImpurityReasons ( node : SdsCall | SdsCallable , substitutions = NO_SUBSTITUTIONS ) : ImpurityReason [ ] {
197
+ const callGraph = this . callGraphComputer . getCallGraph ( node , substitutions ) ;
198
+
199
+ const recursionImpurityReason : ImpurityReason [ ] = [ ] ;
200
+ if ( callGraph . isRecursive ) {
201
+ recursionImpurityReason . push ( EndlessRecursion ) ;
202
+ }
203
+
204
+ const otherImpurityReasons = callGraph . streamCalledCallables ( ) . flatMap ( ( it ) => {
205
+ if ( isSdsFunction ( it ) ) {
206
+ return this . getImpurityReasonsForFunction ( it ) ;
207
+ } else {
208
+ return EMPTY_STREAM ;
209
+ }
154
210
} ) ;
211
+
212
+ return [ ...recursionImpurityReason , ...otherImpurityReasons ] ;
155
213
}
156
214
157
- private getExecutedCallsInExpression ( expression : SdsExpression ) : SdsCall [ ] {
215
+ private getExecutedCallsInExpression ( expression : SdsExpression | undefined ) : SdsCall [ ] {
158
216
return this . callGraphComputer . getAllContainedCalls ( expression ) . filter ( ( it ) => {
159
217
// Keep only calls that are not contained in a lambda inside the expression
160
218
const containingLambda = getContainerOfType ( it , isSdsLambda ) ;
0 commit comments