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
;
16 import dmd
.declaration
;
18 import dmd
.expression
;
19 import dmd
.expressionsem
;
22 import dmd
.identifier
;
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
;
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.
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.
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
;
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.
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())
107 assert(f
.type
.ty
== Tfunction
);
108 TypeFunction tf
= cast(TypeFunction
)f
.type
;
111 PURE purity
= f
.isPure();
112 if (purity
== PURE
.strong
)
114 if (purity
== PURE
.const_
)
120 int callSideEffectLevel(Type t
)
124 if (t
.ty
== Tdelegate
)
125 tf
= cast(TypeFunction
)(cast(TypeDelegate
)t
).next
;
128 assert(t
.ty
== Tfunction
);
129 tf
= cast(TypeFunction
)t
;
131 if (!tf
.isnothrow
) // function can throw
135 PURE purity
= tf
.purity
;
136 if (t
.ty
== Tdelegate
&& purity
> PURE
.weak
)
140 else if (!tf
.isImmutable())
141 purity
= PURE
.const_
;
144 if (purity
== PURE
.strong
)
146 if (purity
== PURE
.const_
)
151 private bool lambdaHasSideEffect(Expression e
, bool assumeImpureCalls
= false)
155 // Sort the cases by most frequently used first
159 case EXP
.declaration
:
164 case EXP
.concatenateAssign
:
165 case EXP
.concatenateElemAssign
:
166 case EXP
.concatenateDcharAssign
:
170 case EXP
.leftShiftAssign
:
171 case EXP
.rightShiftAssign
:
172 case EXP
.unsignedRightShiftAssign
:
183 case EXP
.newAnonymousClass
:
187 if (assumeImpureCalls
)
190 if (e
.type
&& e
.type
.ty
== Tnoreturn
)
193 CallExp ce
= cast(CallExp
)e
;
194 /* Calling a function or delegate that is pure nothrow
195 * has no side effects.
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)
212 CastExp ce
= cast(CastExp
)e
;
214 * cast(classtype)func() // because it may throw
216 if (ce
.to
.ty
== Tclass
&& ce
.e1
.op
== EXP
.call && ce
.e1
.type
.ty
== Tclass
)
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).
230 * true if expression has no side effects
232 bool discardValue(Expression e
)
234 if (lambdaHasSideEffect(e
)) // check side-effect shallowly
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
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.
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)
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)
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();
295 e
.warning("calling `%s` without side effects discards return value of type `%s`; prepend a `cast(void)` if intentional", s
, e
.type
.toChars());
303 LogicalExp aae
= cast(LogicalExp
)e
;
304 return discardValue(aae
.e2
);
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; }
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
) |
338 CommaExp ce
= cast(CommaExp
)e
;
339 // Don't complain about compiler-generated comma expressions
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
);
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
))
358 e
.error("`%s` has no effect", e
.toChars());
362 /**************************************************
363 * Build a temporary variable to copy the value of e into.
365 * stc = storage classes will be added to the made temporary variable
366 * name = name for temporary variable
367 * e = original expression
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
381 /**************************************************
382 * Build a temporary variable to extract e's evaluation, if e is not trivial.
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.
390 * When e is trivial and alwaysCopy == false, e itself is returned.
391 * Otherwise, a new VarExp is returned.
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
407 ((sc
.flags
& SCOPE
.ctfe
) ?
!hasSideEffect(e
) : isTrivialExp(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
);