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