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