1 /**
2  * Implement array operations, such as `a[] = b[] + c[]`.
3  *
4  * Specification: $(LINK2 https://dlang.org/spec/arrays.html#array-operations, Array Operations)
5  *
6  * Copyright:   Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
7  * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
8  * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
9  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/arrayop.d, _arrayop.d)
10  * Documentation:  https://dlang.org/phobos/dmd_arrayop.html
11  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/arrayop.d
12  */
13 
14 module dmd.arrayop;
15 
16 import core.stdc.stdio;
17 import dmd.arraytypes;
18 import dmd.astenums;
19 import dmd.declaration;
20 import dmd.dscope;
21 import dmd.dsymbol;
22 import dmd.expression;
23 import dmd.expressionsem;
24 import dmd.func;
25 import dmd.globals;
26 import dmd.hdrgen;
27 import dmd.id;
28 import dmd.identifier;
29 import dmd.location;
30 import dmd.mtype;
31 import dmd.common.outbuffer;
32 import dmd.statement;
33 import dmd.tokens;
34 import dmd.visitor;
35 
36 /**********************************************
37  * Check that there are no uses of arrays without [].
38  */
39 bool isArrayOpValid(Expression e)
40 {
41     //printf("isArrayOpValid() %s\n", e.toChars());
42     if (e.op == EXP.slice)
43         return true;
44     if (e.op == EXP.arrayLiteral)
45     {
46         Type t = e.type.toBasetype();
47         while (t.ty == Tarray || t.ty == Tsarray)
48             t = t.nextOf().toBasetype();
49         return (t.ty != Tvoid);
50     }
51     Type tb = e.type.toBasetype();
52     if (tb.ty == Tarray || tb.ty == Tsarray)
53     {
54         if (isUnaArrayOp(e.op))
55         {
56             return isArrayOpValid(e.isUnaExp().e1);
57         }
58         if (isBinArrayOp(e.op) || isBinAssignArrayOp(e.op) || e.op == EXP.assign)
59         {
60             BinExp be = e.isBinExp();
61             return isArrayOpValid(be.e1) && isArrayOpValid(be.e2);
62         }
63         if (e.op == EXP.construct)
64         {
65             BinExp be = e.isBinExp();
66             return be.e1.op == EXP.slice && isArrayOpValid(be.e2);
67         }
68         // if (e.op == EXP.call)
69         // {
70         // TODO: Decide if [] is required after arrayop calls.
71         // }
72         return false;
73     }
74     return true;
75 }
76 
77 bool isNonAssignmentArrayOp(Expression e)
78 {
79     if (e.op == EXP.slice)
80         return isNonAssignmentArrayOp(e.isSliceExp().e1);
81 
82     Type tb = e.type.toBasetype();
83     if (tb.ty == Tarray || tb.ty == Tsarray)
84     {
85         return (isUnaArrayOp(e.op) || isBinArrayOp(e.op));
86     }
87     return false;
88 }
89 
90 bool checkNonAssignmentArrayOp(Expression e, bool suggestion = false)
91 {
92     if (isNonAssignmentArrayOp(e))
93     {
94         const(char)* s = "";
95         if (suggestion)
96             s = " (possible missing [])";
97         e.error("array operation `%s` without destination memory not allowed%s", e.toChars(), s);
98         return true;
99     }
100     return false;
101 }
102 
103 /***********************************
104  * Construct the array operation expression, call object._arrayOp!(tiargs)(args).
105  *
106  * Encode operand types and operations into tiargs using reverse polish notation (RPN) to preserve precedence.
107  * Unary operations are prefixed with "u" (e.g. "u~").
108  * Pass operand values (slices or scalars) as args.
109  *
110  * Scalar expression sub-trees of `e` are evaluated before calling
111  * into druntime to hoist them out of the loop. This is a valid
112  * evaluation order as the actual array operations have no
113  * side-effect.
114  * References:
115  * https://github.com/dlang/dmd/blob/cdfadf8a18f474e6a1b8352af2541efe3e3467cc/druntime/src/object.d#L4694
116  * https://github.com/dlang/dmd/blob/master/druntime/src/core/internal/array/operations.d
117  */
118 Expression arrayOp(BinExp e, Scope* sc)
119 {
120     //printf("BinExp.arrayOp() %s\n", e.toChars());
121     Type tb = e.type.toBasetype();
122     assert(tb.ty == Tarray || tb.ty == Tsarray);
123     Type tbn = tb.nextOf().toBasetype();
124     if (tbn.ty == Tvoid)
125     {
126         e.error("cannot perform array operations on `void[]` arrays");
127         return ErrorExp.get();
128     }
129     if (!isArrayOpValid(e))
130         return arrayOpInvalidError(e);
131 
132     auto tiargs = new Objects();
133     auto args = buildArrayOp(sc, e, tiargs);
134 
135     import dmd.dtemplate : TemplateDeclaration;
136     __gshared TemplateDeclaration arrayOp;
137     if (arrayOp is null)
138     {
139         // Create .object._arrayOp
140         Identifier idArrayOp = Identifier.idPool("_arrayOp");
141         Expression id = new IdentifierExp(e.loc, Id.empty);
142         id = new DotIdExp(e.loc, id, Id.object);
143         id = new DotIdExp(e.loc, id, idArrayOp);
144 
145         id = id.expressionSemantic(sc);
146         if (auto te = id.isTemplateExp())
147             arrayOp = te.td;
148         else
149             ObjectNotFound(idArrayOp);   // fatal error
150     }
151 
152     auto fd = resolveFuncCall(e.loc, sc, arrayOp, tiargs, null, ArgumentList(args), FuncResolveFlag.standard);
153     if (!fd || fd.errors)
154         return ErrorExp.get();
155     return new CallExp(e.loc, new VarExp(e.loc, fd, false), args).expressionSemantic(sc);
156 }
157 
158 /// ditto
159 Expression arrayOp(BinAssignExp e, Scope* sc)
160 {
161     //printf("BinAssignExp.arrayOp() %s\n", e.toChars());
162 
163     /* Check that the elements of e1 can be assigned to
164      */
165     Type tn = e.e1.type.toBasetype().nextOf();
166 
167     if (tn && (!tn.isMutable() || !tn.isAssignable()))
168     {
169         e.error("slice `%s` is not mutable", e.e1.toChars());
170         if (e.op == EXP.addAssign)
171             checkPossibleAddCatError!(AddAssignExp, CatAssignExp)(e.isAddAssignExp);
172         return ErrorExp.get();
173     }
174     if (e.e1.op == EXP.arrayLiteral)
175     {
176         return e.e1.modifiableLvalue(sc, e.e1);
177     }
178 
179     return arrayOp(e.isBinExp(), sc);
180 }
181 
182 /******************************************
183  * Convert the expression tree e to template and function arguments,
184  * using reverse polish notation (RPN) to encode order of operations.
185  * Encode operations as string arguments, using a "u" prefix for unary operations.
186  */
187 private Expressions* buildArrayOp(Scope* sc, Expression e, Objects* tiargs)
188 {
189     extern (C++) final class BuildArrayOpVisitor : Visitor
190     {
191         alias visit = Visitor.visit;
192         Scope* sc;
193         Objects* tiargs;
194         Expressions* args;
195 
196     public:
197         extern (D) this(Scope* sc, Objects* tiargs) scope
198         {
199             this.sc = sc;
200             this.tiargs = tiargs;
201             this.args = new Expressions();
202         }
203 
204         override void visit(Expression e)
205         {
206             tiargs.push(e.type);
207             args.push(e);
208         }
209 
210         override void visit(SliceExp e)
211         {
212             visit(cast(Expression) e);
213         }
214 
215         override void visit(CastExp e)
216         {
217             visit(cast(Expression) e);
218         }
219 
220         override void visit(UnaExp e)
221         {
222             Type tb = e.type.toBasetype();
223             if (tb.ty != Tarray && tb.ty != Tsarray) // hoist scalar expressions
224             {
225                 visit(cast(Expression) e);
226             }
227             else
228             {
229                 // RPN, prefix unary ops with u
230                 OutBuffer buf;
231                 buf.writestring("u");
232                 buf.writestring(EXPtoString(e.op));
233                 e.e1.accept(this);
234                 tiargs.push(new StringExp(Loc.initial, buf.extractSlice()).expressionSemantic(sc));
235             }
236         }
237 
238         override void visit(BinExp e)
239         {
240             Type tb = e.type.toBasetype();
241             if (tb.ty != Tarray && tb.ty != Tsarray) // hoist scalar expressions
242             {
243                 visit(cast(Expression) e);
244             }
245             else
246             {
247                 // RPN
248                 e.e1.accept(this);
249                 e.e2.accept(this);
250                 tiargs.push(new StringExp(Loc.initial, EXPtoString(e.op)).expressionSemantic(sc));
251             }
252         }
253     }
254 
255     scope v = new BuildArrayOpVisitor(sc, tiargs);
256     e.accept(v);
257     return v.args;
258 }
259 
260 /***********************************************
261  * Some implicit casting can be performed by the _arrayOp template.
262  * Params:
263  *      tfrom = type converting from
264  *      tto   = type converting to
265  * Returns:
266  *      true if can be performed by _arrayOp
267  */
268 bool isArrayOpImplicitCast(TypeDArray tfrom, TypeDArray tto)
269 {
270     const tyf = tfrom.nextOf().toBasetype().ty;
271     const tyt = tto  .nextOf().toBasetype().ty;
272     return tyf == tyt ||
273            tyf == Tint32 && tyt == Tfloat64;
274 }
275 
276 /***********************************************
277  * Test if expression is a unary array op.
278  */
279 bool isUnaArrayOp(EXP op)
280 {
281     switch (op)
282     {
283     case EXP.negate:
284     case EXP.tilde:
285         return true;
286     default:
287         break;
288     }
289     return false;
290 }
291 
292 /***********************************************
293  * Test if expression is a binary array op.
294  */
295 bool isBinArrayOp(EXP op)
296 {
297     switch (op)
298     {
299     case EXP.add:
300     case EXP.min:
301     case EXP.mul:
302     case EXP.div:
303     case EXP.mod:
304     case EXP.xor:
305     case EXP.and:
306     case EXP.or:
307     case EXP.pow:
308         return true;
309     default:
310         break;
311     }
312     return false;
313 }
314 
315 /***********************************************
316  * Test if expression is a binary assignment array op.
317  */
318 bool isBinAssignArrayOp(EXP op)
319 {
320     switch (op)
321     {
322     case EXP.addAssign:
323     case EXP.minAssign:
324     case EXP.mulAssign:
325     case EXP.divAssign:
326     case EXP.modAssign:
327     case EXP.xorAssign:
328     case EXP.andAssign:
329     case EXP.orAssign:
330     case EXP.powAssign:
331         return true;
332     default:
333         break;
334     }
335     return false;
336 }
337 
338 /***********************************************
339  * Test if operand is a valid array op operand.
340  */
341 bool isArrayOpOperand(Expression e)
342 {
343     //printf("Expression.isArrayOpOperand() %s\n", e.toChars());
344     if (e.op == EXP.slice)
345         return true;
346     if (e.op == EXP.arrayLiteral)
347     {
348         Type t = e.type.toBasetype();
349         while (t.ty == Tarray || t.ty == Tsarray)
350             t = t.nextOf().toBasetype();
351         return (t.ty != Tvoid);
352     }
353     Type tb = e.type.toBasetype();
354     if (tb.ty == Tarray)
355     {
356         return (isUnaArrayOp(e.op) ||
357                 isBinArrayOp(e.op) ||
358                 isBinAssignArrayOp(e.op) ||
359                 e.op == EXP.assign);
360     }
361     return false;
362 }
363 
364 
365 /***************************************************
366  * Print error message about invalid array operation.
367  * Params:
368  *      e = expression with the invalid array operation
369  * Returns:
370  *      instance of ErrorExp
371  */
372 
373 ErrorExp arrayOpInvalidError(Expression e)
374 {
375     e.error("invalid array operation `%s` (possible missing [])", e.toChars());
376     if (e.op == EXP.add)
377         checkPossibleAddCatError!(AddExp, CatExp)(e.isAddExp());
378     else if (e.op == EXP.addAssign)
379         checkPossibleAddCatError!(AddAssignExp, CatAssignExp)(e.isAddAssignExp());
380     return ErrorExp.get();
381 }
382 
383 private void checkPossibleAddCatError(AddT, CatT)(AddT ae)
384 {
385     if (!ae.e2.type || ae.e2.type.ty != Tarray || !ae.e2.type.implicitConvTo(ae.e1.type))
386         return;
387     CatT ce = new CatT(ae.loc, ae.e1, ae.e2);
388     ae.errorSupplemental("did you mean to concatenate (`%s`) instead ?", ce.toChars());
389 }