d: Merge upstream dmd, druntime 4c18eed967, phobos d945686a4.
[official-gcc.git] / gcc / d / dmd / arrayop.d
blob25bbb3f32ad46051b6ef7c65ef1866f1c35a5f1e
1 /**
2 * Implement array operations, such as `a[] = b[] + c[]`.
4 * Specification: $(LINK2 https://dlang.org/spec/arrays.html#array-operations, Array Operations)
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
14 module dmd.arrayop;
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;
35 /**********************************************
36 * Check that there are no uses of arrays without [].
38 bool isArrayOpValid(Expression e)
40 //printf("isArrayOpValid() %s\n", e.toChars());
41 if (e.op == EXP.slice)
42 return true;
43 if (e.op == EXP.arrayLiteral)
45 Type t = e.type.toBasetype();
46 while (t.ty == Tarray || t.ty == Tsarray)
47 t = t.nextOf().toBasetype();
48 return (t.ty != Tvoid);
50 Type tb = e.type.toBasetype();
51 if (tb.ty == Tarray || tb.ty == Tsarray)
53 if (isUnaArrayOp(e.op))
55 return isArrayOpValid(e.isUnaExp().e1);
57 if (isBinArrayOp(e.op) || isBinAssignArrayOp(e.op) || e.op == EXP.assign)
59 BinExp be = e.isBinExp();
60 return isArrayOpValid(be.e1) && isArrayOpValid(be.e2);
62 if (e.op == EXP.construct)
64 BinExp be = e.isBinExp();
65 return be.e1.op == EXP.slice && isArrayOpValid(be.e2);
67 // if (e.op == EXP.call)
68 // {
69 // TODO: Decide if [] is required after arrayop calls.
70 // }
71 return false;
73 return true;
76 bool isNonAssignmentArrayOp(Expression e)
78 if (e.op == EXP.slice)
79 return isNonAssignmentArrayOp(e.isSliceExp().e1);
81 Type tb = e.type.toBasetype();
82 if (tb.ty == Tarray || tb.ty == Tsarray)
84 return (isUnaArrayOp(e.op) || isBinArrayOp(e.op));
86 return false;
89 bool checkNonAssignmentArrayOp(Expression e, bool suggestion = false)
91 if (isNonAssignmentArrayOp(e))
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;
99 return false;
102 /***********************************
103 * Construct the array operation expression, call object._arrayOp!(tiargs)(args).
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.
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
117 Expression arrayOp(BinExp e, Scope* sc)
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)
125 error(e.loc, "cannot perform array operations on `void[]` arrays");
126 return ErrorExp.get();
128 if (!isArrayOpValid(e))
129 return arrayOpInvalidError(e);
131 auto tiargs = new Objects();
132 auto args = buildArrayOp(sc, e, tiargs);
134 import dmd.dtemplate : TemplateDeclaration;
135 __gshared TemplateDeclaration arrayOp;
136 if (arrayOp is null)
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);
144 id = id.expressionSemantic(sc);
145 if (auto te = id.isTemplateExp())
146 arrayOp = te.td;
147 else
148 ObjectNotFound(idArrayOp); // fatal error
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);
157 /// ditto
158 Expression arrayOp(BinAssignExp e, Scope* sc)
160 //printf("BinAssignExp.arrayOp() %s\n", e.toChars());
162 /* Check that the elements of e1 can be assigned to
164 Type tn = e.e1.type.toBasetype().nextOf();
166 if (tn && (!tn.isMutable() || !tn.isAssignable()))
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();
173 if (e.e1.op == EXP.arrayLiteral)
175 return e.e1.modifiableLvalue(sc, e.e1);
178 return arrayOp(e.isBinExp(), sc);
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.
186 private Expressions* buildArrayOp(Scope* sc, Expression e, Objects* tiargs)
188 extern (C++) final class BuildArrayOpVisitor : Visitor
190 alias visit = Visitor.visit;
191 Scope* sc;
192 Objects* tiargs;
193 Expressions* args;
195 public:
196 extern (D) this(Scope* sc, Objects* tiargs) scope @safe
198 this.sc = sc;
199 this.tiargs = tiargs;
200 this.args = new Expressions();
203 override void visit(Expression e)
205 tiargs.push(e.type);
206 args.push(e);
209 override void visit(SliceExp e)
211 visit(cast(Expression) e);
214 override void visit(CastExp e)
216 visit(cast(Expression) e);
219 override void visit(UnaExp e)
221 Type tb = e.type.toBasetype();
222 if (tb.ty != Tarray && tb.ty != Tsarray) // hoist scalar expressions
224 visit(cast(Expression) e);
226 else
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));
237 override void visit(BinExp e)
239 Type tb = e.type.toBasetype();
240 if (tb.ty != Tarray && tb.ty != Tsarray) // hoist scalar expressions
242 visit(cast(Expression) e);
244 else
246 // RPN
247 e.e1.accept(this);
248 e.e2.accept(this);
249 tiargs.push(new StringExp(Loc.initial, EXPtoString(e.op)).expressionSemantic(sc));
254 scope v = new BuildArrayOpVisitor(sc, tiargs);
255 e.accept(v);
256 return v.args;
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
267 bool isArrayOpImplicitCast(TypeDArray tfrom, TypeDArray tto)
269 const tyf = tfrom.nextOf().toBasetype().ty;
270 const tyt = tto .nextOf().toBasetype().ty;
271 return tyf == tyt ||
272 tyf == Tint32 && tyt == Tfloat64;
275 /***********************************************
276 * Test if expression is a unary array op.
278 bool isUnaArrayOp(EXP op) @safe
280 switch (op)
282 case EXP.negate:
283 case EXP.tilde:
284 return true;
285 default:
286 break;
288 return false;
291 /***********************************************
292 * Test if expression is a binary array op.
294 bool isBinArrayOp(EXP op) @safe
296 switch (op)
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;
311 return false;
314 /***********************************************
315 * Test if expression is a binary assignment array op.
317 bool isBinAssignArrayOp(EXP op) @safe
319 switch (op)
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;
334 return false;
337 /***********************************************
338 * Test if operand is a valid array op operand.
340 bool isArrayOpOperand(Expression e)
342 //printf("Expression.isArrayOpOperand() %s\n", e.toChars());
343 if (e.op == EXP.slice)
344 return true;
345 if (e.op == EXP.arrayLiteral)
347 Type t = e.type.toBasetype();
348 while (t.ty == Tarray || t.ty == Tsarray)
349 t = t.nextOf().toBasetype();
350 return (t.ty != Tvoid);
352 Type tb = e.type.toBasetype();
353 if (tb.ty == Tarray)
355 return (isUnaArrayOp(e.op) ||
356 isBinArrayOp(e.op) ||
357 isBinAssignArrayOp(e.op) ||
358 e.op == EXP.assign);
360 return false;
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
372 ErrorExp arrayOpInvalidError(Expression e)
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();
382 private void checkPossibleAddCatError(AddT, CatT)(AddT ae)
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());