1 /**
2  * Perform constant folding.
3  *
4  * Copyright:   Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
5  * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
6  * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/optimize.d, _optimize.d)
8  * Documentation:  https://dlang.org/phobos/dmd_optimize.html
9  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/optimize.d
10  */
11 
12 module dmd.optimize;
13 
14 import core.stdc.stdio;
15 
16 import dmd.astenums;
17 import dmd.constfold;
18 import dmd.ctfeexpr;
19 import dmd.dcast;
20 import dmd.dclass;
21 import dmd.declaration;
22 import dmd.dsymbol;
23 import dmd.dsymbolsem;
24 import dmd.errors;
25 import dmd.expression;
26 import dmd.expressionsem;
27 import dmd.globals;
28 import dmd.hdrgen;
29 import dmd.init;
30 import dmd.location;
31 import dmd.mtype;
32 import dmd.printast;
33 import dmd.root.ctfloat;
34 import dmd.sideeffect;
35 import dmd.tokens;
36 import dmd.visitor;
37 
38 /*************************************
39  * If variable has a const initializer,
40  * return that initializer.
41  * Returns:
42  *      initializer if there is one,
43  *      null if not,
44  *      ErrorExp if error
45  */
46 Expression expandVar(int result, VarDeclaration v)
47 {
48     //printf("expandVar(result = %d, v = %p, %s)\n", result, v, v ? v.toChars() : "null");
49 
50     /********
51      * Params:
52      *  e = initializer expression
53      */
54     Expression initializerReturn(Expression e)
55     {
56         if (e.type != v.type)
57         {
58             e = e.castTo(null, v.type);
59         }
60         v.inuse++;
61         e = e.optimize(result);
62         v.inuse--;
63         //if (e) printf("\te = %p, %s, e.type = %d, %s\n", e, e.toChars(), e.type.ty, e.type.toChars());
64         return e;
65     }
66 
67     static Expression nullReturn()
68     {
69         return null;
70     }
71 
72     static Expression errorReturn()
73     {
74         return ErrorExp.get();
75     }
76 
77     if (!v)
78         return nullReturn();
79     if (!v.originalType && v.semanticRun < PASS.semanticdone) // semantic() not yet run
80         v.dsymbolSemantic(null);
81     if (v.type &&
82         (v.isConst() || v.isImmutable() || v.storage_class & STC.manifest))
83     {
84         Type tb = v.type.toBasetype();
85         if (v.storage_class & STC.manifest ||
86             tb.isscalar() ||
87             ((result & WANTexpand) && (tb.ty != Tsarray && tb.ty != Tstruct)))
88         {
89             if (v._init)
90             {
91                 if (v.inuse)
92                 {
93                     if (v.storage_class & STC.manifest)
94                     {
95                         .error(v.loc, "%s `%s` recursive initialization of constant", v.kind, v.toPrettyChars);
96                         return errorReturn();
97                     }
98                     return nullReturn();
99                 }
100                 Expression ei = v.getConstInitializer();
101                 if (!ei)
102                 {
103                     if (v.storage_class & STC.manifest)
104                     {
105                         .error(v.loc, "%s `%s` enum cannot be initialized with `%s`", v.kind, v.toPrettyChars, dmd.hdrgen.toChars(v._init));
106                         return errorReturn();
107                     }
108                     return nullReturn();
109                 }
110                 if (ei.op == EXP.construct || ei.op == EXP.blit)
111                 {
112                     AssignExp ae = cast(AssignExp)ei;
113                     ei = ae.e2;
114                     if (ei.isConst() == 1)
115                     {
116                     }
117                     else if (ei.op == EXP.string_)
118                     {
119                         // https://issues.dlang.org/show_bug.cgi?id=14459
120                         // Do not constfold the string literal
121                         // if it's typed as a C string, because the value expansion
122                         // will drop the pointer identity.
123                         if (!(result & WANTexpand) && ei.type.toBasetype().ty == Tpointer)
124                             return nullReturn();
125                     }
126                     else
127                         return nullReturn();
128                     if (ei.type == v.type)
129                     {
130                         // const variable initialized with const expression
131                     }
132                     else if (ei.implicitConvTo(v.type) >= MATCH.constant)
133                     {
134                         // const var initialized with non-const expression
135                         ei = ei.implicitCastTo(null, v.type);
136                         ei = ei.expressionSemantic(null);
137                     }
138                     else
139                         return nullReturn();
140                 }
141                 else if (!(v.storage_class & STC.manifest) &&
142                          ei.isConst() != 1 &&
143                          ei.op != EXP.string_ &&
144                          ei.op != EXP.address)
145                 {
146                     return nullReturn();
147                 }
148 
149                 if (!ei.type)
150                 {
151                     return nullReturn();
152                 }
153                 else
154                 {
155                     // Should remove the copy() operation by
156                     // making all mods to expressions copy-on-write
157                     return initializerReturn(ei.copy());
158                 }
159             }
160             else
161             {
162                 // v does not have an initializer
163                 version (all)
164                 {
165                     return nullReturn();
166                 }
167                 else
168                 {
169                     // BUG: what if const is initialized in constructor?
170                     auto e = v.type.defaultInit();
171                     e.loc = e1.loc;
172                     return initializerReturn(e);
173                 }
174             }
175             assert(0);
176         }
177     }
178     return nullReturn();
179 }
180 
181 private Expression fromConstInitializer(int result, Expression e1)
182 {
183     //printf("fromConstInitializer(result = %x, %s)\n", result, e1.toChars());
184     //static int xx; if (xx++ == 10) assert(0);
185     Expression e = e1;
186     if (auto ve = e1.isVarExp())
187     {
188         VarDeclaration v = ve.var.isVarDeclaration();
189         e = expandVar(result, v);
190         if (e)
191         {
192             // If it is a comma expression involving a declaration, we mustn't
193             // perform a copy -- we'd get two declarations of the same variable.
194             // See https://issues.dlang.org/show_bug.cgi?id=4465.
195             if (e.op == EXP.comma && e.isCommaExp().e1.isDeclarationExp())
196                 e = e1;
197             else if (e.type != e1.type && e1.type && e1.type.ty != Tident)
198             {
199                 // Type 'paint' operation
200                 e = e.copy();
201                 e.type = e1.type;
202             }
203             e.loc = e1.loc;
204         }
205         else
206         {
207             e = e1;
208         }
209     }
210     return e;
211 }
212 
213 /***
214  * It is possible for constant folding to change an array expression of
215  * unknown length, into one where the length is known.
216  * If the expression 'arr' is a literal, set lengthVar to be its length.
217  * Params:
218  *    lengthVar = variable declaration for the `.length` property
219  *    arr = String, ArrayLiteral, or of TypeSArray
220  */
221 package void setLengthVarIfKnown(VarDeclaration lengthVar, Expression arr)
222 {
223     if (!lengthVar)
224         return;
225     if (lengthVar._init && !lengthVar._init.isVoidInitializer())
226         return; // we have previously calculated the length
227     dinteger_t len;
228     if (auto se = arr.isStringExp())
229         len = se.len;
230     else if (auto ale = arr.isArrayLiteralExp())
231         len = ale.elements.length;
232     else
233     {
234         auto tsa = arr.type.toBasetype().isTypeSArray();
235         if (!tsa)
236             return; // we don't know the length yet
237         len = tsa.dim.toInteger();
238     }
239     Expression dollar = new IntegerExp(Loc.initial, len, Type.tsize_t);
240     lengthVar._init = new ExpInitializer(Loc.initial, dollar);
241     lengthVar.storage_class |= STC.static_ | STC.const_;
242 }
243 
244 /***
245  * Same as above, but determines the length from 'type'.
246  * Params:
247  *    lengthVar = variable declaration for the `.length` property
248  *    type = TypeSArray
249  */
250 package void setLengthVarIfKnown(VarDeclaration lengthVar, Type type)
251 {
252     if (!lengthVar)
253         return;
254     if (lengthVar._init && !lengthVar._init.isVoidInitializer())
255         return; // we have previously calculated the length
256     auto tsa = type.toBasetype().isTypeSArray();
257     if (!tsa)
258         return; // we don't know the length yet
259     const len = tsa.dim.toInteger();
260     Expression dollar = new IntegerExp(Loc.initial, len, Type.tsize_t);
261     lengthVar._init = new ExpInitializer(Loc.initial, dollar);
262     lengthVar.storage_class |= STC.static_ | STC.const_;
263 }
264 
265 /*********************************
266  * Constant fold an Expression.
267  * Params:
268  *      e = expression to const fold; this may get modified in-place
269  *      result = WANTvalue, WANTexpand, or both
270  *      keepLvalue = `e` is an lvalue, and keep it as an lvalue since it is
271  *                   an argument to a `ref` or `out` parameter, or the operand of `&` operator
272  * Returns:
273  *      Constant folded version of `e`
274  */
275 Expression Expression_optimize(Expression e, int result, bool keepLvalue)
276 {
277     //printf("Expression_optimize() e: %s result: %d keepLvalue %d\n", e.toChars(), result, keepLvalue);
278     Expression ret = e;
279 
280     void errorReturn()
281     {
282         ret = ErrorExp.get();
283     }
284 
285     /* Returns: true if error
286      */
287     bool expOptimize(ref Expression e, int flags, bool keepLvalue = false)
288     {
289         if (!e)
290             return false;
291         Expression ex = Expression_optimize(e, flags, keepLvalue);
292         if (ex.op == EXP.error)
293         {
294             ret = ex; // store error result
295             return true;
296         }
297         else
298         {
299             e = ex; // modify original
300             return false;
301         }
302     }
303 
304     bool unaOptimize(UnaExp e, int flags)
305     {
306         return expOptimize(e.e1, flags);
307     }
308 
309     bool binOptimize(BinExp e, int flags, bool keepLhsLvalue = false)
310     {
311         return expOptimize(e.e1, flags, keepLhsLvalue) |
312                expOptimize(e.e2, flags);
313     }
314 
315     void visitExp(Expression e)
316     {
317         //printf("Expression::optimize(result = x%x) %s\n", result, e.toChars());
318     }
319 
320     void visitVar(VarExp e)
321     {
322         VarDeclaration v = e.var.isVarDeclaration();
323 
324         if (!(keepLvalue && v && !(v.storage_class & STC.manifest)))
325             ret = fromConstInitializer(result, e);
326 
327         // if unoptimized, try to optimize the dtor expression
328         // (e.g., might be a LogicalExp with constant lhs)
329         if (ret == e && v && v.edtor)
330         {
331             // prevent infinite recursion (`<var>.~this()`)
332             if (!v.inuse)
333             {
334                 v.inuse++;
335                 expOptimize(v.edtor, WANTvalue);
336                 v.inuse--;
337             }
338         }
339     }
340 
341     void visitTuple(TupleExp e)
342     {
343         expOptimize(e.e0, WANTvalue);
344         foreach (ref ex; (*e.exps)[])
345         {
346             expOptimize(ex, WANTvalue);
347         }
348     }
349 
350     void visitArrayLiteral(ArrayLiteralExp e)
351     {
352         if (e.elements)
353         {
354             expOptimize(e.basis, result & WANTexpand);
355             foreach (ref ex; (*e.elements)[])
356             {
357                 expOptimize(ex, result & WANTexpand);
358             }
359         }
360     }
361 
362     void visitAssocArrayLiteral(AssocArrayLiteralExp e)
363     {
364         assert(e.keys.length == e.values.length);
365         foreach (i, ref ekey; (*e.keys)[])
366         {
367             expOptimize(ekey, result & WANTexpand);
368             expOptimize((*e.values)[i], result & WANTexpand);
369         }
370     }
371 
372     void visitStructLiteral(StructLiteralExp e)
373     {
374         if (e.stageflags & stageOptimize)
375             return;
376         const old = e.stageflags;
377         e.stageflags |= stageOptimize;
378         if (e.elements)
379         {
380             foreach (ref ex; (*e.elements)[])
381             {
382                 expOptimize(ex, result & WANTexpand);
383             }
384         }
385         e.stageflags = old;
386     }
387 
388     void visitUna(UnaExp e)
389     {
390         //printf("UnaExp::optimize() %s\n", e.toChars());
391         if (unaOptimize(e, result))
392             return;
393     }
394 
395     void visitNeg(NegExp e)
396     {
397         if (unaOptimize(e, result))
398             return;
399         if (e.e1.isConst() == 1)
400         {
401             ret = Neg(e.type, e.e1).copy();
402         }
403     }
404 
405     void visitCom(ComExp e)
406     {
407         if (unaOptimize(e, result))
408             return;
409         if (e.e1.isConst() == 1)
410         {
411             ret = Com(e.type, e.e1).copy();
412         }
413     }
414 
415     void visitNop(NotExp e)
416     {
417         if (unaOptimize(e, result))
418             return;
419         if (e.e1.isConst() == 1)
420         {
421             ret = Not(e.type, e.e1).copy();
422         }
423     }
424 
425     void visitSymOff(SymOffExp e)
426     {
427         assert(e.var);
428     }
429 
430     void visitAddr(AddrExp e)
431     {
432         //printf("AddrExp::optimize(result = %d, keepLvalue = %d) %s\n", result, keepLvalue, e.toChars());
433         /* Rewrite &(a,b) as (a,&b)
434          */
435         if (auto ce = e.e1.isCommaExp())
436         {
437             auto ae = new AddrExp(e.loc, ce.e2, e.type);
438             ret = new CommaExp(ce.loc, ce.e1, ae);
439             ret.type = e.type;
440             return;
441         }
442         // Keep lvalue-ness
443         if (expOptimize(e.e1, result, true))
444             return;                     // error return
445 
446         // Convert &*ex to ex
447         if (auto pe = e.e1.isPtrExp())
448         {
449             Expression ex = pe.e1;
450             if (e.type.equals(ex.type))
451                 ret = ex;
452             else if (e.type.toBasetype().equivalent(ex.type.toBasetype()))
453             {
454                 ret = ex.copy();
455                 ret.type = e.type;
456             }
457             return;
458         }
459         if (auto ve = e.e1.isVarExp())
460         {
461             if (!ve.var.isReference() && !ve.var.isImportedSymbol())
462             {
463                 ret = new SymOffExp(e.loc, ve.var, 0, ve.hasOverloads);
464                 ret.type = e.type;
465                 return;
466             }
467         }
468         if (e.e1.isDotVarExp())
469         {
470             /******************************
471              * Run down the left side of the a.b.c expression to determine the
472              * leftmost variable being addressed (`a`), and accumulate the offsets of the `.b` and `.c`.
473              * Params:
474              *      e = the DotVarExp or VarExp
475              *      var = set to the VarExp at the end, or null if doesn't end in VarExp
476              *      eint = set to the IntegerExp at the end, or null if doesn't end in IntegerExp
477              *      offset = accumulation of all the .var offsets encountered
478              * Returns: true on error
479              */
480             static bool getVarAndOffset(Expression e, out VarDeclaration var, out IntegerExp eint, ref uint offset)
481             {
482                 if (e.type.size() == SIZE_INVALID)  // trigger computation of v.offset
483                     return true;
484 
485                 if (auto dve = e.isDotVarExp())
486                 {
487                     auto v = dve.var.isVarDeclaration();
488                     if (!v || !v.isField() || v.isBitFieldDeclaration())
489                         return false;
490 
491                     if (getVarAndOffset(dve.e1, var, eint, offset))
492                         return true;
493                     offset += v.offset;
494                 }
495                 else if (auto ve = e.isVarExp())
496                 {
497                     if (!ve.var.isReference() &&
498                         !ve.var.isImportedSymbol() &&
499                         ve.var.isDataseg() &&
500                         ve.var.isCsymbol())
501                     {
502                         var = ve.var.isVarDeclaration();
503                     }
504                 }
505                 else if (auto ep = e.isPtrExp())
506                 {
507                     if (auto ei = ep.e1.isIntegerExp())
508                     {
509                         eint = ei;
510                     }
511                     else if (auto se = ep.e1.isSymOffExp())
512                     {
513                         if (!se.var.isReference() &&
514                             !se.var.isImportedSymbol() &&
515                             se.var.isDataseg())
516                         {
517                             var = se.var.isVarDeclaration();
518                             offset += se.offset;
519                         }
520                     }
521                 }
522                 else if (auto ei = e.isIndexExp())
523                 {
524                     if (auto ve = ei.e1.isVarExp())
525                     {
526                         if (!ve.var.isReference() &&
527                             !ve.var.isImportedSymbol() &&
528                             ve.var.isDataseg() &&
529                             ve.var.isCsymbol())
530                         {
531                             if (auto ie = ei.e2.isIntegerExp())
532                             {
533                                 var = ve.var.isVarDeclaration();
534                                 offset += ie.toInteger() * ve.type.toBasetype().nextOf().size();
535                             }
536                         }
537                     }
538                 }
539                 return false;
540             }
541 
542             uint offset;
543             VarDeclaration var;
544             IntegerExp eint;
545             if (getVarAndOffset(e.e1, var, eint, offset))
546             {
547                 ret = ErrorExp.get();
548                 return;
549             }
550             if (var)
551             {
552                 ret = new SymOffExp(e.loc, var, offset, false);
553                 ret.type = e.type;
554                 return;
555             }
556             if (eint)
557             {
558                 ret = new IntegerExp(e.loc, eint.toInteger() + offset, e.type);
559                 return;
560             }
561         }
562         else if (auto ae = e.e1.isIndexExp())
563         {
564             if (ae.e2.isIntegerExp() && ae.e1.isIndexExp())
565             {
566                 /* Rewrite `(a[i])[index]` to `(&a[i]) + index*size`
567                  */
568                 sinteger_t index = ae.e2.toInteger();
569                 auto ae1 = ae.e1.isIndexExp();          // ae1 is a[i]
570                 if (auto ts = ae1.type.isTypeSArray())
571                 {
572                     sinteger_t dim = ts.dim.toInteger();
573 
574                     if (index < 0 || index > dim)
575                     {
576                         error(e.loc, "array index %lld is out of bounds `[0..%lld]`", index, dim);
577                         return errorReturn();
578                     }
579 
580                     import core.checkedint : mulu;
581                     bool overflow;
582                     const offset = mulu(index, ts.nextOf().size(e.loc), overflow); // offset = index*size
583                     if (overflow)
584                     {
585                         error(e.loc, "array offset overflow");
586                         return errorReturn();
587                     }
588 
589                     Expression ex = new AddrExp(ae1.loc, ae1);  // &a[i]
590                     ex.type = ae1.type.pointerTo();
591 
592                     Expression add = new AddExp(ae.loc, ex, new IntegerExp(ae.e2.loc, offset, ae.e2.type));
593                     add.type = e.type;
594                     ret = Expression_optimize(add, result, keepLvalue);
595                     return;
596                 }
597             }
598 
599             // Convert &array[n] to &array+n
600             if (ae.e2.isIntegerExp() && ae.e1.isVarExp())
601             {
602                 sinteger_t index = ae.e2.toInteger();
603                 VarExp ve = ae.e1.isVarExp();
604                 if (ve.type.isTypeSArray() && !ve.var.isImportedSymbol())
605                 {
606                     TypeSArray ts = ve.type.isTypeSArray();
607                     sinteger_t dim = ts.dim.toInteger();
608                     if (index < 0 || index >= dim)
609                     {
610                         /* 0 for C static arrays means size is unknown, no need to check,
611                          * and address one past the end is OK, too
612                          */
613                         if (!((dim == 0 || dim == index) && ve.var.isCsymbol()))
614                         {
615                             error(e.loc, "array index %lld is out of bounds `[0..%lld]`", index, dim);
616                             return errorReturn();
617                         }
618                     }
619 
620                     import core.checkedint : mulu;
621                     bool overflow;
622                     const offset = mulu(index, ts.nextOf().size(e.loc), overflow);
623                     if (overflow)
624                     {
625                         error(e.loc, "array offset overflow");
626                         return errorReturn();
627                     }
628 
629                     ret = new SymOffExp(e.loc, ve.var, offset);
630                     ret.type = e.type;
631                     return;
632                 }
633             }
634             // Convert &((a.b)[index]) to (&a.b)+index*elementsize
635             else if (ae.e2.isIntegerExp() && ae.e1.isDotVarExp())
636             {
637                 sinteger_t index = ae.e2.toInteger();
638                 DotVarExp ve = ae.e1.isDotVarExp();
639                 if (ve.type.isTypeSArray() && ve.var.isField() && ve.e1.isPtrExp())
640                 {
641                     TypeSArray ts = ve.type.isTypeSArray();
642                     sinteger_t dim = ts.dim.toInteger();
643                     if (index < 0 || index >= dim)
644                     {
645                         /* 0 for C static arrays means size is unknown, no need to check,
646                          * and address one past the end is OK, too
647                          */
648                         if (!((dim == 0 || dim == index) && ve.var.isCsymbol()))
649                         {
650                             error(e.loc, "array index %lld is out of bounds `[0..%lld]`", index, dim);
651                             return errorReturn();
652                         }
653                     }
654 
655                     import core.checkedint : mulu;
656                     bool overflow;
657                     const offset = mulu(index, ts.nextOf().size(e.loc), overflow); // index*elementsize
658                     if (overflow)
659                     {
660                         error(e.loc, "array offset overflow");
661                         return errorReturn();
662                     }
663 
664                     auto pe = new AddrExp(e.loc, ve);
665                     pe.type = e.type;
666                     ret = new AddExp(e.loc, pe, new IntegerExp(e.loc, offset, Type.tsize_t));
667                     ret.type = e.type;
668                     return;
669                 }
670             }
671         }
672     }
673 
674     void visitPtr(PtrExp e)
675     {
676         //printf("PtrExp::optimize(result = x%x) %s\n", result, e.toChars());
677         if (expOptimize(e.e1, result))
678             return;
679         // Convert *&ex to ex
680         // But only if there is no type punning involved
681         if (auto ey = e.e1.isAddrExp())
682         {
683             Expression ex = ey.e1;
684             if (e.type.equals(ex.type))
685                 ret = ex;
686             else if (e.type.toBasetype().equivalent(ex.type.toBasetype()))
687             {
688                 ret = ex.copy();
689                 ret.type = e.type;
690             }
691         }
692         if (keepLvalue)
693             return;
694         // Constant fold *(&structliteral + offset)
695         if (e.e1.op == EXP.add)
696         {
697             Expression ex = Ptr(e.type, e.e1).copy();
698             if (!CTFEExp.isCantExp(ex))
699             {
700                 ret = ex;
701                 return;
702             }
703         }
704         if (auto se = e.e1.isSymOffExp())
705         {
706             VarDeclaration v = se.var.isVarDeclaration();
707             Expression ex = expandVar(result, v);
708             if (ex && ex.isStructLiteralExp())
709             {
710                 StructLiteralExp sle = ex.isStructLiteralExp();
711                 ex = sle.getField(e.type, cast(uint)se.offset);
712                 if (ex && !CTFEExp.isCantExp(ex))
713                 {
714                     ret = ex;
715                     return;
716                 }
717             }
718         }
719     }
720 
721     void visitDotVar(DotVarExp e)
722     {
723         //printf("DotVarExp::optimize(result = x%x) %s\n", result, e.toChars());
724         if (expOptimize(e.e1, result))
725             return;
726         if (keepLvalue)
727             return;
728         Expression ex = e.e1;
729         if (auto ve = ex.isVarExp())
730         {
731             VarDeclaration v = ve.var.isVarDeclaration();
732             ex = expandVar(result, v);
733         }
734         if (ex && ex.isStructLiteralExp())
735         {
736             StructLiteralExp sle = ex.isStructLiteralExp();
737             VarDeclaration vf = e.var.isVarDeclaration();
738             if (vf && !vf.overlapped)
739             {
740                 /* https://issues.dlang.org/show_bug.cgi?id=13021
741                  * Prevent optimization if vf has overlapped fields.
742                  */
743                 ex = sle.getField(e.type, vf.offset);
744                 if (ex && !CTFEExp.isCantExp(ex))
745                 {
746                     ret = ex;
747                     return;
748                 }
749             }
750         }
751     }
752 
753     void visitNew(NewExp e)
754     {
755         expOptimize(e.thisexp, WANTvalue);
756         // Optimize parameters
757         if (e.arguments)
758         {
759             foreach (ref arg; (*e.arguments)[])
760             {
761                 expOptimize(arg, WANTvalue);
762             }
763         }
764     }
765 
766     void visitCall(CallExp e)
767     {
768         //printf("CallExp::optimize(result = %d) %s\n", result, e.toChars());
769         // Optimize parameters with keeping lvalue-ness
770         if (expOptimize(e.e1, result))
771             return;
772         if (e.arguments)
773         {
774             // t1 can apparently be void for __ArrayDtor(T) calls
775             if (auto tf = e.calledFunctionType())
776             {
777                 foreach (i, ref arg; (*e.arguments)[])
778                 {
779                     Parameter p = tf.parameterList[i];
780                     bool keep = p && p.isReference();
781                     expOptimize(arg, WANTvalue, keep);
782                 }
783             }
784         }
785     }
786 
787     void visitCast(CastExp e)
788     {
789         //printf("CastExp::optimize(result = %d) %s\n", result, e.toChars());
790         //printf("from %s to %s\n", e.type.toChars(), e.to.toChars());
791         //printf("from %s\n", e.type.toChars());
792         //printf("e1.type %s\n", e.e1.type.toChars());
793         //printf("type = %p\n", e.type);
794         assert(e.type);
795         const op1 = e.e1.op;
796         Expression e1old = e.e1;
797         if (expOptimize(e.e1, result, keepLvalue))
798             return;
799         if (!keepLvalue)
800             e.e1 = fromConstInitializer(result, e.e1);
801         if (e.e1 == e1old && e.e1.op == EXP.arrayLiteral && e.type.toBasetype().ty == Tpointer && e.e1.type.toBasetype().ty != Tsarray)
802         {
803             // Casting this will result in the same expression, and
804             // infinite loop because of Expression::implicitCastTo()
805             return; // no change
806         }
807         if ((e.e1.op == EXP.string_ || e.e1.op == EXP.arrayLiteral) &&
808             (e.type.ty == Tpointer || e.type.ty == Tarray))
809         {
810             const esz  = e.type.nextOf().size(e.loc);
811             const e1sz = e.e1.type.toBasetype().nextOf().size(e.e1.loc);
812             if (esz == SIZE_INVALID || e1sz == SIZE_INVALID)
813                 return errorReturn();
814 
815             if (e1sz == esz)
816             {
817                 // https://issues.dlang.org/show_bug.cgi?id=12937
818                 // If target type is void array, trying to paint
819                 // e.e1 with that type will cause infinite recursive optimization.
820                 if (e.type.nextOf().ty == Tvoid)
821                     return;
822                 ret = e.e1.castTo(null, e.type);
823                 //printf(" returning1 %s\n", ret.toChars());
824                 return;
825             }
826         }
827 
828         // Returning e.e1 with changing its type
829         void returnE_e1()
830         {
831             ret = (e1old == e.e1 ? e.e1.copy() : e.e1);
832             ret.type = e.type;
833         }
834 
835         if (e.e1.op == EXP.structLiteral && e.e1.type.implicitConvTo(e.type) >= MATCH.constant)
836         {
837             //printf(" returning2 %s\n", e.e1.toChars());
838             return returnE_e1();
839         }
840         /* The first test here is to prevent infinite loops
841          */
842         if (op1 != EXP.arrayLiteral && e.e1.op == EXP.arrayLiteral)
843         {
844             ret = e.e1.castTo(null, e.to);
845             return;
846         }
847         if (e.e1.op == EXP.null_ && (e.type.ty == Tpointer || e.type.ty == Tclass || e.type.ty == Tarray))
848         {
849             //printf(" returning3 %s\n", e.e1.toChars());
850             return returnE_e1();
851         }
852         if (e.type.ty == Tclass && e.e1.type.ty == Tclass)
853         {
854             import dmd.astenums : Sizeok;
855 
856             // See if we can remove an unnecessary cast
857             ClassDeclaration cdfrom = e.e1.type.isClassHandle();
858             ClassDeclaration cdto = e.type.isClassHandle();
859             if (cdfrom.errors || cdto.errors)
860                 return errorReturn();
861             if (cdto == ClassDeclaration.object && !cdfrom.isInterfaceDeclaration())
862                 return returnE_e1();    // can always convert a class to Object
863             // Need to determine correct offset before optimizing away the cast.
864             // https://issues.dlang.org/show_bug.cgi?id=16980
865             if (cdfrom.size(e.loc) == SIZE_INVALID)
866                 return errorReturn();
867             assert(cdfrom.sizeok == Sizeok.done);
868             assert(cdto.sizeok == Sizeok.done || !cdto.isBaseOf(cdfrom, null));
869             int offset;
870             if (cdto.isBaseOf(cdfrom, &offset) && offset == 0)
871             {
872                 //printf(" returning4 %s\n", e.e1.toChars());
873                 return returnE_e1();
874             }
875         }
876         if (e.e1.type.mutableOf().unSharedOf().equals(e.to.mutableOf().unSharedOf()))
877         {
878             //printf(" returning5 %s\n", e.e1.toChars());
879             return returnE_e1();
880         }
881         if (e.e1.isConst())
882         {
883             if (e.e1.op == EXP.symbolOffset)
884             {
885                 if (e.type.toBasetype().ty != Tsarray)
886                 {
887                     const esz = e.type.size(e.loc);
888                     const e1sz = e.e1.type.size(e.e1.loc);
889                     if (esz == SIZE_INVALID ||
890                         e1sz == SIZE_INVALID)
891                         return errorReturn();
892 
893                     if (esz == e1sz)
894                         return returnE_e1();
895                 }
896                 return;
897             }
898             if (e.to.toBasetype().ty != Tvoid)
899             {
900                 if (e.e1.type.equals(e.type) && e.type.equals(e.to))
901                     ret = e.e1;
902                 else
903                     ret = Cast(e.loc, e.type, e.to, e.e1).copy();
904             }
905         }
906         //printf(" returning6 %s\n", ret.toChars());
907     }
908 
909     void visitBinAssign(BinAssignExp e)
910     {
911         //printf("BinAssignExp::optimize(result = %d) %s\n", result, e.toChars());
912         if (binOptimize(e, result, /*keepLhsLvalue*/ true))
913             return;
914         if (e.op == EXP.leftShiftAssign || e.op == EXP.rightShiftAssign || e.op == EXP.unsignedRightShiftAssign)
915         {
916             if (e.e2.isConst() == 1)
917             {
918                 sinteger_t i2 = e.e2.toInteger();
919                 uinteger_t sz = e.e1.type.size(e.e1.loc);
920                 assert(sz != SIZE_INVALID);
921                 sz *= 8;
922                 if (i2 < 0 || i2 >= sz)
923                 {
924                     error(e.loc, "shift assign by %lld is outside the range `0..%llu`", i2, cast(ulong)sz - 1);
925                     return errorReturn();
926                 }
927             }
928         }
929     }
930 
931     void visitBin(BinExp e)
932     {
933         //printf("BinExp::optimize(result = %d) %s\n", result, e.toChars());
934         const keepLhsLvalue = e.op == EXP.construct || e.op == EXP.blit || e.op == EXP.assign
935             || e.op == EXP.plusPlus || e.op == EXP.minusMinus
936             || e.op == EXP.prePlusPlus || e.op == EXP.preMinusMinus;
937         binOptimize(e, result, keepLhsLvalue);
938     }
939 
940     void visitAdd(AddExp e)
941     {
942         //printf("AddExp::optimize(%s)\n", e.toChars());
943         if (binOptimize(e, result))
944             return;
945         if (e.e1.isConst() && e.e2.isConst())
946         {
947             if (e.e1.op == EXP.symbolOffset && e.e2.op == EXP.symbolOffset)
948                 return;
949             ret = Add(e.loc, e.type, e.e1, e.e2).copy();
950         }
951     }
952 
953     void visitMin(MinExp e)
954     {
955         //printf("MinExp::optimize(%s)\n", e.toChars());
956         if (binOptimize(e, result))
957             return;
958         if (e.e1.isConst() && e.e2.isConst())
959         {
960             if (e.e2.op == EXP.symbolOffset)
961                 return;
962             ret = Min(e.loc, e.type, e.e1, e.e2).copy();
963         }
964     }
965 
966     void visitMul(MulExp e)
967     {
968         //printf("MulExp::optimize(result = %d) %s\n", result, e.toChars());
969         if (binOptimize(e, result))
970             return;
971         if (e.e1.isConst() == 1 && e.e2.isConst() == 1)
972         {
973             ret = Mul(e.loc, e.type, e.e1, e.e2).copy();
974         }
975     }
976 
977     void visitDiv(DivExp e)
978     {
979         //printf("DivExp::optimize(%s)\n", e.toChars());
980         if (binOptimize(e, result))
981             return;
982         if (e.e1.isConst() == 1 && e.e2.isConst() == 1)
983         {
984             ret = Div(e.loc, e.type, e.e1, e.e2).copy();
985         }
986     }
987 
988     void visitMod(ModExp e)
989     {
990         if (binOptimize(e, result))
991             return;
992         if (e.e1.isConst() == 1 && e.e2.isConst() == 1)
993         {
994             ret = Mod(e.loc, e.type, e.e1, e.e2).copy();
995         }
996     }
997 
998     extern (D) void shift_optimize(BinExp e, UnionExp function(const ref Loc, Type, Expression, Expression) shift)
999     {
1000         if (binOptimize(e, result))
1001             return;
1002         if (e.e2.isConst() == 1)
1003         {
1004             sinteger_t i2 = e.e2.toInteger();
1005             uinteger_t sz = e.e1.type.size(e.e1.loc);
1006             assert(sz != SIZE_INVALID);
1007             sz *= 8;
1008             if (i2 < 0 || i2 >= sz)
1009             {
1010                 error(e.loc, "shift by %lld is outside the range `0..%llu`", i2, cast(ulong)sz - 1);
1011                 return errorReturn();
1012             }
1013             if (e.e1.isConst() == 1)
1014                 ret = (*shift)(e.loc, e.type, e.e1, e.e2).copy();
1015         }
1016     }
1017 
1018     void visitShl(ShlExp e)
1019     {
1020         //printf("ShlExp::optimize(result = %d) %s\n", result, e.toChars());
1021         shift_optimize(e, &Shl);
1022     }
1023 
1024     void visitShr(ShrExp e)
1025     {
1026         //printf("ShrExp::optimize(result = %d) %s\n", result, e.toChars());
1027         shift_optimize(e, &Shr);
1028     }
1029 
1030     void visitUshr(UshrExp e)
1031     {
1032         //printf("UshrExp::optimize(result = %d) %s\n", result, toChars());
1033         shift_optimize(e, &Ushr);
1034     }
1035 
1036     void visitAnd(AndExp e)
1037     {
1038         if (binOptimize(e, result))
1039             return;
1040         if (e.e1.isConst() == 1 && e.e2.isConst() == 1)
1041             ret = And(e.loc, e.type, e.e1, e.e2).copy();
1042     }
1043 
1044     void visitOr(OrExp e)
1045     {
1046         if (binOptimize(e, result))
1047             return;
1048         if (e.e1.isConst() == 1 && e.e2.isConst() == 1)
1049             ret = Or(e.loc, e.type, e.e1, e.e2).copy();
1050     }
1051 
1052     void visitXor(XorExp e)
1053     {
1054         if (binOptimize(e, result))
1055             return;
1056         if (e.e1.isConst() == 1 && e.e2.isConst() == 1)
1057             ret = Xor(e.loc, e.type, e.e1, e.e2).copy();
1058     }
1059 
1060     void visitPow(PowExp e)
1061     {
1062         if (binOptimize(e, result))
1063             return;
1064         // All negative integral powers are illegal.
1065         if (e.e1.type.isintegral() && (e.e2.op == EXP.int64) && cast(sinteger_t)e.e2.toInteger() < 0)
1066         {
1067             error(e.loc, "cannot raise `%s` to a negative integer power. Did you mean `(cast(real)%s)^^%s` ?", e.e1.type.toBasetype().toChars(), e.e1.toChars(), e.e2.toChars());
1068             return errorReturn();
1069         }
1070         // If e2 *could* have been an integer, make it one.
1071         if (e.e2.op == EXP.float64 && e.e2.toReal() == real_t(cast(sinteger_t)e.e2.toReal()))
1072         {
1073             // This only applies to floating point, or positive integral powers.
1074             if (e.e1.type.isfloating() || cast(sinteger_t)e.e2.toInteger() >= 0)
1075                 e.e2 = new IntegerExp(e.loc, e.e2.toInteger(), Type.tint64);
1076         }
1077         if (e.e1.isConst() == 1 && e.e2.isConst() == 1)
1078         {
1079             Expression ex = Pow(e.loc, e.type, e.e1, e.e2).copy();
1080             if (!CTFEExp.isCantExp(ex))
1081             {
1082                 ret = ex;
1083                 return;
1084             }
1085         }
1086     }
1087 
1088     void visitComma(CommaExp e)
1089     {
1090         //printf("CommaExp::optimize(result = %d) %s\n", result, e.toChars());
1091         // Comma needs special treatment, because it may
1092         // contain compiler-generated declarations. We can interpret them, but
1093         // otherwise we must NOT attempt to constant-fold them.
1094         // In particular, if the comma returns a temporary variable, it needs
1095         // to be an lvalue (this is particularly important for struct constructors)
1096         expOptimize(e.e1, WANTvalue);
1097         expOptimize(e.e2, result, keepLvalue);
1098         if (ret.op == EXP.error)
1099             return;
1100         if (!e.e1 || e.e1.op == EXP.int64 || e.e1.op == EXP.float64 || !hasSideEffect(e.e1))
1101         {
1102             ret = e.e2;
1103             if (ret)
1104                 ret.type = e.type;
1105         }
1106         //printf("-CommaExp::optimize(result = %d) %s\n", result, e.e.toChars());
1107     }
1108 
1109     void visitArrayLength(ArrayLengthExp e)
1110     {
1111         //printf("ArrayLengthExp::optimize(result = %d) %s\n", result, e.toChars());
1112         if (unaOptimize(e, WANTexpand))
1113             return;
1114         // CTFE interpret static immutable arrays (to get better diagnostics)
1115         if (auto ve = e.e1.isVarExp())
1116         {
1117             VarDeclaration v = ve.var.isVarDeclaration();
1118             if (v && (v.storage_class & STC.static_) && (v.storage_class & STC.immutable_) && v._init)
1119             {
1120                 if (Expression ci = v.getConstInitializer())
1121                     e.e1 = ci;
1122             }
1123         }
1124         if (e.e1.op == EXP.string_ || e.e1.op == EXP.arrayLiteral || e.e1.op == EXP.assocArrayLiteral || e.e1.type.toBasetype().ty == Tsarray || e.e1.op == EXP.null_)
1125         {
1126             ret = ArrayLength(e.type, e.e1).copy();
1127         }
1128     }
1129 
1130     void visitEqual(EqualExp e)
1131     {
1132         //printf("EqualExp::optimize(result = %x) %s\n", result, e.toChars());
1133         if (binOptimize(e, WANTvalue))
1134             return;
1135         Expression e1 = fromConstInitializer(result, e.e1);
1136         Expression e2 = fromConstInitializer(result, e.e2);
1137         if (e1.op == EXP.error)
1138         {
1139             ret = e1;
1140             return;
1141         }
1142         if (e2.op == EXP.error)
1143         {
1144             ret = e2;
1145             return;
1146         }
1147         ret = Equal(e.op, e.loc, e.type, e1, e2).copy();
1148         if (CTFEExp.isCantExp(ret))
1149             ret = e;
1150     }
1151 
1152     void visitIdentity(IdentityExp e)
1153     {
1154         //printf("IdentityExp::optimize(result = %d) %s\n", result, e.toChars());
1155         if (binOptimize(e, WANTvalue))
1156             return;
1157         if ((e.e1.isConst() && e.e2.isConst()) || (e.e1.op == EXP.null_ && e.e2.op == EXP.null_))
1158         {
1159             ret = Identity(e.op, e.loc, e.type, e.e1, e.e2).copy();
1160             if (CTFEExp.isCantExp(ret))
1161                 ret = e;
1162         }
1163     }
1164 
1165     void visitIndex(IndexExp e)
1166     {
1167         //printf("IndexExp::optimize(result = %d) %s\n", result, e.toChars());
1168         if (expOptimize(e.e1, result & WANTexpand))
1169             return;
1170         Expression ex = fromConstInitializer(result, e.e1);
1171         // We might know $ now
1172         setLengthVarIfKnown(e.lengthVar, ex);
1173         if (expOptimize(e.e2, WANTvalue))
1174             return;
1175         // Don't optimize to an array literal element directly in case an lvalue is requested
1176         if (keepLvalue && ex.op == EXP.arrayLiteral)
1177             return;
1178         ret = Index(e.type, ex, e.e2, e.indexIsInBounds).copy();
1179         if (CTFEExp.isCantExp(ret) || (!ret.isErrorExp() && keepLvalue && !ret.isLvalue()))
1180             ret = e;
1181     }
1182 
1183     void visitSlice(SliceExp e)
1184     {
1185         //printf("SliceExp::optimize(result = %d) %s\n", result, e.toChars());
1186         if (expOptimize(e.e1, result & WANTexpand))
1187             return;
1188         if (!e.lwr)
1189         {
1190             if (e.e1.op == EXP.string_)
1191             {
1192                 // Convert slice of string literal into dynamic array
1193                 Type t = e.e1.type.toBasetype();
1194                 if (Type tn = t.nextOf())
1195                     ret = e.e1.castTo(null, tn.arrayOf());
1196             }
1197         }
1198         else
1199         {
1200             e.e1 = fromConstInitializer(result, e.e1);
1201             // We might know $ now
1202             setLengthVarIfKnown(e.lengthVar, e.e1);
1203             expOptimize(e.lwr, WANTvalue);
1204             expOptimize(e.upr, WANTvalue);
1205             if (ret.op == EXP.error)
1206                 return;
1207             ret = Slice(e.type, e.e1, e.lwr, e.upr).copy();
1208             if (CTFEExp.isCantExp(ret))
1209                 ret = e;
1210         }
1211         // https://issues.dlang.org/show_bug.cgi?id=14649
1212         // Leave the slice form so it might be
1213         // a part of array operation.
1214         // Assume that the backend codegen will handle the form `e[]`
1215         // as an equal to `e` itself.
1216         if (ret.op == EXP.string_)
1217         {
1218             e.e1 = ret;
1219             e.lwr = null;
1220             e.upr = null;
1221             ret = e;
1222         }
1223         //printf("-SliceExp::optimize() %s\n", ret.toChars());
1224     }
1225 
1226     void visitLogical(LogicalExp e)
1227     {
1228         //printf("LogicalExp::optimize(%d) %s\n", result, e.toChars());
1229         if (expOptimize(e.e1, WANTvalue))
1230             return;
1231         const oror = e.op == EXP.orOr;
1232         if (e.e1.toBool().hasValue(oror))
1233         {
1234             // Replace with (e1, oror)
1235             ret = IntegerExp.createBool(oror);
1236             ret = Expression.combine(e.e1, ret);
1237             if (e.type.toBasetype().ty == Tvoid)
1238             {
1239                 ret = new CastExp(e.loc, ret, Type.tvoid);
1240                 ret.type = e.type;
1241             }
1242             ret = Expression_optimize(ret, result, false);
1243             return;
1244         }
1245         expOptimize(e.e2, WANTvalue);
1246         if (e.e1.isConst())
1247         {
1248             const e1Opt = e.e1.toBool();
1249             if (e.e2.isConst())
1250             {
1251                 bool n1 = e1Opt.get();
1252                 bool n2 = e.e2.toBool().get();
1253                 ret = new IntegerExp(e.loc, oror ? (n1 || n2) : (n1 && n2), e.type);
1254             }
1255             else if (e1Opt.hasValue(!oror))
1256             {
1257                 if (e.type.toBasetype().ty == Tvoid)
1258                     ret = e.e2;
1259                 else
1260                 {
1261                     ret = new CastExp(e.loc, e.e2, e.type);
1262                     ret.type = e.type;
1263                 }
1264             }
1265         }
1266     }
1267 
1268     void visitCmp(CmpExp e)
1269     {
1270         //printf("CmpExp::optimize() %s\n", e.toChars());
1271         if (binOptimize(e, WANTvalue))
1272             return;
1273         Expression e1 = fromConstInitializer(result, e.e1);
1274         Expression e2 = fromConstInitializer(result, e.e2);
1275         ret = Cmp(e.op, e.loc, e.type, e1, e2).copy();
1276         if (CTFEExp.isCantExp(ret))
1277             ret = e;
1278     }
1279 
1280     void visitCat(CatExp e)
1281     {
1282         //printf("CatExp::optimize(%d) %s\n", result, e.toChars());
1283         if (binOptimize(e, result))
1284             return;
1285 
1286         if (e.type == Type.tstring)
1287             if (auto ce1 = e.e1.isCatExp())
1288             {
1289                 // https://issues.dlang.org/show_bug.cgi?id=12798
1290                 // optimize ((expr ~ str1) ~ str2)
1291                 // https://issues.dlang.org/show_bug.cgi?id=24078
1292                 // This optimization is only valid if `expr` is a string.
1293                 // Otherwise it leads to:
1294                 // `["c"] ~ "a" ~ "b"` becoming `["c"] ~ "ab"`
1295                 scope CatExp cex = new CatExp(e.loc, ce1.e2, e.e2);
1296                 cex.type = e.type;
1297                 Expression ex = Expression_optimize(cex, result, false);
1298                 if (ex != cex)
1299                 {
1300                     e.e1 = ce1.e1;
1301                     e.e2 = ex;
1302                 }
1303             }
1304         // optimize "str"[] -> "str"
1305         if (auto se1 = e.e1.isSliceExp())
1306         {
1307             if (se1.e1.op == EXP.string_ && !se1.lwr)
1308                 e.e1 = se1.e1;
1309         }
1310         if (auto se2 = e.e2.isSliceExp())
1311         {
1312             if (se2.e1.op == EXP.string_ && !se2.lwr)
1313                 e.e2 = se2.e1;
1314         }
1315         ret = Cat(e.loc, e.type, e.e1, e.e2).copy();
1316         if (CTFEExp.isCantExp(ret))
1317             ret = e;
1318     }
1319 
1320     void visitCond(CondExp e)
1321     {
1322         if (expOptimize(e.econd, WANTvalue))
1323             return;
1324         const opt = e.econd.toBool();
1325         if (opt.hasValue(true))
1326             ret = Expression_optimize(e.e1, result, keepLvalue);
1327         else if (opt.hasValue(false))
1328             ret = Expression_optimize(e.e2, result, keepLvalue);
1329         else
1330         {
1331             expOptimize(e.e1, result, keepLvalue);
1332             expOptimize(e.e2, result, keepLvalue);
1333         }
1334     }
1335 
1336     // Optimize the expression until it can no longer be simplified.
1337     size_t b;
1338     while (1)
1339     {
1340         if (b++ == global.recursionLimit)
1341         {
1342             error(e.loc, "infinite loop while optimizing expression");
1343             fatal();
1344         }
1345 
1346         auto ex = ret;
1347         switch (ex.op)
1348         {
1349             case EXP.variable:          visitVar(ex.isVarExp()); break;
1350             case EXP.tuple:             visitTuple(ex.isTupleExp()); break;
1351             case EXP.arrayLiteral:      visitArrayLiteral(ex.isArrayLiteralExp()); break;
1352             case EXP.assocArrayLiteral: visitAssocArrayLiteral(ex.isAssocArrayLiteralExp()); break;
1353             case EXP.structLiteral:     visitStructLiteral(ex.isStructLiteralExp()); break;
1354 
1355             case EXP.import_:
1356             case EXP.assert_:
1357             case EXP.dotIdentifier:
1358             case EXP.dotTemplateDeclaration:
1359             case EXP.dotTemplateInstance:
1360             case EXP.delegate_:
1361             case EXP.dotType:
1362             case EXP.uadd:
1363             case EXP.delete_:
1364             case EXP.vector:
1365             case EXP.vectorArray:
1366             case EXP.array:
1367             case EXP.delegatePointer:
1368             case EXP.delegateFunctionPointer:
1369             case EXP.preMinusMinus:
1370             case EXP.prePlusPlus:       visitUna(cast(UnaExp)ex); break;
1371 
1372             case EXP.negate:            visitNeg(ex.isNegExp()); break;
1373             case EXP.tilde:             visitCom(ex.isComExp()); break;
1374             case EXP.not:               visitNop(ex.isNotExp()); break;
1375             case EXP.symbolOffset:      visitSymOff(ex.isSymOffExp()); break;
1376             case EXP.address:           visitAddr(ex.isAddrExp()); break;
1377             case EXP.star:              visitPtr(ex.isPtrExp()); break;
1378             case EXP.dotVariable:       visitDotVar(ex.isDotVarExp()); break;
1379             case EXP.new_:              visitNew(ex.isNewExp()); break;
1380             case EXP.call:              visitCall(ex.isCallExp()); break;
1381             case EXP.cast_:             visitCast(ex.isCastExp()); break;
1382 
1383             case EXP.addAssign:
1384             case EXP.minAssign:
1385             case EXP.mulAssign:
1386             case EXP.divAssign:
1387             case EXP.modAssign:
1388             case EXP.andAssign:
1389             case EXP.orAssign:
1390             case EXP.xorAssign:
1391             case EXP.powAssign:
1392             case EXP.leftShiftAssign:
1393             case EXP.rightShiftAssign:
1394             case EXP.unsignedRightShiftAssign:
1395             case EXP.concatenateElemAssign:
1396             case EXP.concatenateDcharAssign:
1397             case EXP.concatenateAssign: visitBinAssign(ex.isBinAssignExp()); break;
1398 
1399             case EXP.minusMinus:
1400             case EXP.plusPlus:
1401             case EXP.assign:
1402             case EXP.construct:
1403             case EXP.blit:
1404             case EXP.in_:
1405             case EXP.remove:
1406             case EXP.dot:                       visitBin(cast(BinExp)ex); break;
1407 
1408             case EXP.add:                       visitAdd(ex.isAddExp()); break;
1409             case EXP.min:                       visitMin(ex.isMinExp()); break;
1410             case EXP.mul:                       visitMul(ex.isMulExp()); break;
1411             case EXP.div:                       visitDiv(ex.isDivExp()); break;
1412             case EXP.mod:                       visitMod(ex.isModExp()); break;
1413             case EXP.leftShift:                 visitShl(ex.isShlExp()); break;
1414             case EXP.rightShift:                visitShr(ex.isShrExp()); break;
1415             case EXP.unsignedRightShift:        visitUshr(ex.isUshrExp()); break;
1416             case EXP.and:                       visitAnd(ex.isAndExp()); break;
1417             case EXP.or:                        visitOr(ex.isOrExp()); break;
1418             case EXP.xor:                       visitXor(ex.isXorExp()); break;
1419             case EXP.pow:                       visitPow(ex.isPowExp()); break;
1420             case EXP.comma:                     visitComma(ex.isCommaExp()); break;
1421             case EXP.arrayLength:               visitArrayLength(ex.isArrayLengthExp()); break;
1422             case EXP.notEqual:
1423             case EXP.equal:                     visitEqual(ex.isEqualExp()); break;
1424             case EXP.notIdentity:
1425             case EXP.identity:                  visitIdentity(ex.isIdentityExp()); break;
1426             case EXP.index:                     visitIndex(ex.isIndexExp()); break;
1427             case EXP.slice:                     visitSlice(ex.isSliceExp()); break;
1428             case EXP.andAnd:
1429             case EXP.orOr:                      visitLogical(ex.isLogicalExp()); break;
1430             case EXP.lessThan:
1431             case EXP.lessOrEqual:
1432             case EXP.greaterThan:
1433             case EXP.greaterOrEqual:            visitCmp(cast(CmpExp)ex); break;
1434             case EXP.concatenate:               visitCat(ex.isCatExp()); break;
1435             case EXP.question:                  visitCond(ex.isCondExp()); break;
1436 
1437             default:                            visitExp(ex); break;
1438         }
1439 
1440         if (ex == ret)
1441             break;
1442     }
1443     return ret;
1444 }