d: Merge upstream dmd 3982604c5, druntime bc58b1e9, phobos 12329adb6.
[official-gcc.git] / gcc / d / dmd / sideeffect.d
blob99833b55a329b3ecee443ef6e8f13ba3235cd165
1 /**
2 * Find side-effects of expressions.
4 * Copyright: Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved
5 * Authors: $(LINK2 http://www.digitalmars.com, Walter Bright)
6 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/sideeffect.d, _sideeffect.d)
8 * Documentation: https://dlang.org/phobos/dmd_sideeffect.html
9 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/sideeffect.d
12 module dmd.sideeffect;
14 import dmd.apply;
15 import dmd.astenums;
16 import dmd.declaration;
17 import dmd.dscope;
18 import dmd.expression;
19 import dmd.expressionsem;
20 import dmd.func;
21 import dmd.globals;
22 import dmd.identifier;
23 import dmd.init;
24 import dmd.mtype;
25 import dmd.tokens;
26 import dmd.visitor;
28 /**************************************************
29 * Front-end expression rewriting should create temporary variables for
30 * non trivial sub-expressions in order to:
31 * 1. save evaluation order
32 * 2. prevent sharing of sub-expression in AST
34 extern (C++) bool isTrivialExp(Expression e)
36 extern (C++) final class IsTrivialExp : StoppableVisitor
38 alias visit = typeof(super).visit;
39 public:
40 extern (D) this()
44 override void visit(Expression e)
46 /* https://issues.dlang.org/show_bug.cgi?id=11201
47 * CallExp is always non trivial expression,
48 * especially for inlining.
50 if (e.op == EXP.call)
52 stop = true;
53 return;
55 // stop walking if we determine this expression has side effects
56 stop = lambdaHasSideEffect(e);
60 scope IsTrivialExp v = new IsTrivialExp();
61 return walkPostorder(e, v) == false;
64 /********************************************
65 * Determine if Expression has any side effects.
67 * Params:
68 * e = the expression
69 * assumeImpureCalls = whether function calls should always be assumed to
70 * be impure (e.g. debug is allowed to violate purity)
72 extern (C++) bool hasSideEffect(Expression e, bool assumeImpureCalls = false)
74 extern (C++) final class LambdaHasSideEffect : StoppableVisitor
76 alias visit = typeof(super).visit;
77 public:
78 extern (D) this()
82 override void visit(Expression e)
84 // stop walking if we determine this expression has side effects
85 stop = lambdaHasSideEffect(e, assumeImpureCalls);
89 scope LambdaHasSideEffect v = new LambdaHasSideEffect();
90 return walkPostorder(e, v);
93 /********************************************
94 * Determine if the call of f, or function type or delegate type t1, has any side effects.
95 * Returns:
96 * 0 has any side effects
97 * 1 nothrow + constant purity
98 * 2 nothrow + strong purity
100 int callSideEffectLevel(FuncDeclaration f)
102 /* https://issues.dlang.org/show_bug.cgi?id=12760
103 * ctor call always has side effects.
105 if (f.isCtorDeclaration())
106 return 0;
107 assert(f.type.ty == Tfunction);
108 TypeFunction tf = cast(TypeFunction)f.type;
109 if (tf.isnothrow)
111 PURE purity = f.isPure();
112 if (purity == PURE.strong)
113 return 2;
114 if (purity == PURE.const_)
115 return 1;
117 return 0;
120 int callSideEffectLevel(Type t)
122 t = t.toBasetype();
123 TypeFunction tf;
124 if (t.ty == Tdelegate)
125 tf = cast(TypeFunction)(cast(TypeDelegate)t).next;
126 else
128 assert(t.ty == Tfunction);
129 tf = cast(TypeFunction)t;
131 if (!tf.isnothrow) // function can throw
132 return 0;
134 tf.purityLevel();
135 PURE purity = tf.purity;
136 if (t.ty == Tdelegate && purity > PURE.weak)
138 if (tf.isMutable())
139 purity = PURE.weak;
140 else if (!tf.isImmutable())
141 purity = PURE.const_;
144 if (purity == PURE.strong)
145 return 2;
146 if (purity == PURE.const_)
147 return 1;
148 return 0;
151 private bool lambdaHasSideEffect(Expression e, bool assumeImpureCalls = false)
153 switch (e.op)
155 // Sort the cases by most frequently used first
156 case EXP.assign:
157 case EXP.plusPlus:
158 case EXP.minusMinus:
159 case EXP.declaration:
160 case EXP.construct:
161 case EXP.blit:
162 case EXP.addAssign:
163 case EXP.minAssign:
164 case EXP.concatenateAssign:
165 case EXP.concatenateElemAssign:
166 case EXP.concatenateDcharAssign:
167 case EXP.mulAssign:
168 case EXP.divAssign:
169 case EXP.modAssign:
170 case EXP.leftShiftAssign:
171 case EXP.rightShiftAssign:
172 case EXP.unsignedRightShiftAssign:
173 case EXP.andAssign:
174 case EXP.orAssign:
175 case EXP.xorAssign:
176 case EXP.powAssign:
177 case EXP.in_:
178 case EXP.remove:
179 case EXP.assert_:
180 case EXP.halt:
181 case EXP.delete_:
182 case EXP.new_:
183 case EXP.newAnonymousClass:
184 return true;
185 case EXP.call:
187 if (assumeImpureCalls)
188 return true;
190 if (e.type && e.type.ty == Tnoreturn)
191 return true;
193 CallExp ce = cast(CallExp)e;
194 /* Calling a function or delegate that is pure nothrow
195 * has no side effects.
197 if (ce.e1.type)
199 Type t = ce.e1.type.toBasetype();
200 if (t.ty == Tdelegate)
201 t = (cast(TypeDelegate)t).next;
202 if (t.ty == Tfunction && (ce.f ? callSideEffectLevel(ce.f) : callSideEffectLevel(ce.e1.type)) > 0)
205 else
206 return true;
208 break;
210 case EXP.cast_:
212 CastExp ce = cast(CastExp)e;
213 /* if:
214 * cast(classtype)func() // because it may throw
216 if (ce.to.ty == Tclass && ce.e1.op == EXP.call && ce.e1.type.ty == Tclass)
217 return true;
218 break;
220 default:
221 break;
223 return false;
226 /***********************************
227 * The result of this expression will be discarded.
228 * Print error messages if the operation has no side effects (and hence is meaningless).
229 * Returns:
230 * true if expression has no side effects
232 bool discardValue(Expression e)
234 if (lambdaHasSideEffect(e)) // check side-effect shallowly
235 return false;
236 switch (e.op)
238 case EXP.cast_:
240 CastExp ce = cast(CastExp)e;
241 if (ce.to.equals(Type.tvoid))
244 * Don't complain about an expression with no effect if it was cast to void
246 return false;
248 break; // complain
250 case EXP.error:
251 return false;
252 case EXP.variable:
254 VarDeclaration v = (cast(VarExp)e).var.isVarDeclaration();
255 if (v && (v.storage_class & STC.temp))
257 // https://issues.dlang.org/show_bug.cgi?id=5810
258 // Don't complain about an internal generated variable.
259 return false;
261 break;
263 case EXP.call:
264 /* Issue 3882: */
265 if (global.params.warnings != DiagnosticReporting.off && !global.gag)
267 CallExp ce = cast(CallExp)e;
268 if (e.type.ty == Tvoid)
270 /* Don't complain about calling void-returning functions with no side-effect,
271 * because purity and nothrow are inferred, and because some of the
272 * runtime library depends on it. Needs more investigation.
274 * One possible solution is to restrict this message to only be called in hierarchies that
275 * never call assert (and or not called from inside unittest blocks)
278 else if (ce.e1.type)
280 Type t = ce.e1.type.toBasetype();
281 if (t.ty == Tdelegate)
282 t = (cast(TypeDelegate)t).next;
283 if (t.ty == Tfunction && (ce.f ? callSideEffectLevel(ce.f) : callSideEffectLevel(ce.e1.type)) > 0)
285 const(char)* s;
286 if (ce.f)
287 s = ce.f.toPrettyChars();
288 else if (ce.e1.op == EXP.star)
290 // print 'fp' if ce.e1 is (*fp)
291 s = (cast(PtrExp)ce.e1).e1.toChars();
293 else
294 s = ce.e1.toChars();
295 e.warning("calling `%s` without side effects discards return value of type `%s`; prepend a `cast(void)` if intentional", s, e.type.toChars());
299 return false;
300 case EXP.andAnd:
301 case EXP.orOr:
303 LogicalExp aae = cast(LogicalExp)e;
304 return discardValue(aae.e2);
306 case EXP.question:
308 CondExp ce = cast(CondExp)e;
309 /* https://issues.dlang.org/show_bug.cgi?id=6178
310 * https://issues.dlang.org/show_bug.cgi?id=14089
311 * Either CondExp::e1 or e2 may have
312 * redundant expression to make those types common. For example:
314 * struct S { this(int n); int v; alias v this; }
315 * S[int] aa;
316 * aa[1] = 0;
318 * The last assignment statement will be rewitten to:
320 * 1 in aa ? aa[1].value = 0 : (aa[1] = 0, aa[1].this(0)).value;
322 * The last DotVarExp is necessary to take assigned value.
324 * int value = (aa[1] = 0); // value = aa[1].value
326 * To avoid false error, discardValue() should be called only when
327 * the both tops of e1 and e2 have actually no side effects.
329 if (!lambdaHasSideEffect(ce.e1) && !lambdaHasSideEffect(ce.e2))
331 return discardValue(ce.e1) |
332 discardValue(ce.e2);
334 return false;
336 case EXP.comma:
338 CommaExp ce = cast(CommaExp)e;
339 // Don't complain about compiler-generated comma expressions
340 if (ce.isGenerated)
341 return false;
343 // Don't check e1 until we cast(void) the a,b code generation.
344 // This is concretely done in expressionSemantic, if a CommaExp has Tvoid as type
345 return discardValue(ce.e2);
347 case EXP.tuple:
348 /* Pass without complaint if any of the tuple elements have side effects.
349 * Ideally any tuple elements with no side effects should raise an error,
350 * this needs more investigation as to what is the right thing to do.
352 if (!hasSideEffect(e))
353 break;
354 return false;
355 default:
356 break;
358 e.error("`%s` has no effect", e.toChars());
359 return true;
362 /**************************************************
363 * Build a temporary variable to copy the value of e into.
364 * Params:
365 * stc = storage classes will be added to the made temporary variable
366 * name = name for temporary variable
367 * e = original expression
368 * Returns:
369 * Newly created temporary variable.
371 VarDeclaration copyToTemp(StorageClass stc, const char[] name, Expression e)
373 assert(name[0] == '_' && name[1] == '_');
374 auto vd = new VarDeclaration(e.loc, e.type,
375 Identifier.generateId(name),
376 new ExpInitializer(e.loc, e));
377 vd.storage_class = stc | STC.temp | STC.ctfe; // temporary is always CTFEable
378 return vd;
381 /**************************************************
382 * Build a temporary variable to extract e's evaluation, if e is not trivial.
383 * Params:
384 * sc = scope
385 * name = name for temporary variable
386 * e0 = a new side effect part will be appended to it.
387 * e = original expression
388 * alwaysCopy = if true, build new temporary variable even if e is trivial.
389 * Returns:
390 * When e is trivial and alwaysCopy == false, e itself is returned.
391 * Otherwise, a new VarExp is returned.
392 * Note:
393 * e's lvalue-ness will be handled well by STC.ref_ or STC.rvalue.
395 Expression extractSideEffect(Scope* sc, const char[] name,
396 ref Expression e0, Expression e, bool alwaysCopy = false)
398 //printf("extractSideEffect(e: %s)\n", e.toChars());
400 /* The trouble here is that if CTFE is running, extracting the side effect
401 * results in an assignment, and then the interpreter says it cannot evaluate the
402 * side effect assignment variable. But we don't have to worry about side
403 * effects in function calls anyway, because then they won't CTFE.
404 * https://issues.dlang.org/show_bug.cgi?id=17145
406 if (!alwaysCopy &&
407 ((sc.flags & SCOPE.ctfe) ? !hasSideEffect(e) : isTrivialExp(e)))
408 return e;
410 auto vd = copyToTemp(0, name, e);
411 vd.storage_class |= e.isLvalue() ? STC.ref_ : STC.rvalue;
413 e0 = Expression.combine(e0, new DeclarationExp(vd.loc, vd)
414 .expressionSemantic(sc));
416 return new VarExp(vd.loc, vd)
417 .expressionSemantic(sc);