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-2022 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
16 import core.stdc.stdio;
17 import dmd.arraytypes;
19 import dmd.declaration;
22 import dmd.expression;
23 import dmd.expressionsem;
28 import dmd.identifier;
30 import dmd.common.outbuffer;
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)
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)
69 // TODO: Decide if [] is required after arrayop calls.
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));
89 bool checkNonAssignmentArrayOp(Expression e, bool suggestion = false)
91 if (isNonAssignmentArrayOp(e))
95 s = " (possible missing [])";
96 e.error("array operation `%s` without destination memory not allowed%s", e.toChars(), s);
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
114 * https://github.com/dlang/druntime/blob/master/src/object.d#L3944
115 * https://github.com/dlang/druntime/blob/master/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();
125 e.error("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 = new Expressions();
133 buildArrayOp(sc, e, tiargs, args);
135 import dmd.dtemplate : TemplateDeclaration;
136 __gshared TemplateDeclaration arrayOp;
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);
145 id = id.expressionSemantic(sc);
146 if (auto te = id.isTemplateExp())
149 ObjectNotFound(idArrayOp); // fatal error
152 auto fd = resolveFuncCall(e.loc, sc, arrayOp, tiargs, null, 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);
159 Expression arrayOp(BinAssignExp e, Scope* sc)
161 //printf("BinAssignExp.arrayOp() %s\n", e.toChars());
163 /* Check that the elements of e1 can be assigned to
165 Type tn = e.e1.type.toBasetype().nextOf();
167 if (tn && (!tn.isMutable() || !tn.isAssignable()))
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();
174 if (e.e1.op == EXP.arrayLiteral)
176 return e.e1.modifiableLvalue(sc, e.e1);
179 return arrayOp(e.isBinExp(), sc);
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.
187 private void buildArrayOp(Scope* sc, Expression e, Objects* tiargs, Expressions* args)
189 extern (C++) final class BuildArrayOpVisitor : Visitor
191 alias visit = Visitor.visit;
197 extern (D) this(Scope* sc, Objects* tiargs, Expressions* args)
200 this.tiargs = tiargs;
204 override void visit(Expression e)
210 override void visit(SliceExp e)
212 visit(cast(Expression) e);
215 override void visit(CastExp e)
217 visit(cast(Expression) e);
220 override void visit(UnaExp e)
222 Type tb = e.type.toBasetype();
223 if (tb.ty != Tarray && tb.ty != Tsarray) // hoist scalar expressions
225 visit(cast(Expression) e);
229 // RPN, prefix unary ops with u
231 buf.writestring("u");
232 buf.writestring(EXPtoString(e.op));
234 tiargs.push(new StringExp(Loc.initial, buf.extractSlice()).expressionSemantic(sc));
238 override void visit(BinExp e)
240 Type tb = e.type.toBasetype();
241 if (tb.ty != Tarray && tb.ty != Tsarray) // hoist scalar expressions
243 visit(cast(Expression) e);
250 tiargs.push(new StringExp(Loc.initial, EXPtoString(e.op)).expressionSemantic(sc));
255 scope v = new BuildArrayOpVisitor(sc, tiargs, args);
259 /***********************************************
260 * Some implicit casting can be performed by the _arrayOp template.
262 * tfrom = type converting from
263 * tto = type converting to
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;
272 tyf == Tint32 && tyt == Tfloat64;
275 /***********************************************
276 * Test if expression is a unary array op.
278 bool isUnaArrayOp(EXP op)
291 /***********************************************
292 * Test if expression is a binary array op.
294 bool isBinArrayOp(EXP op)
314 /***********************************************
315 * Test if expression is a binary assignment array op.
317 bool isBinAssignArrayOp(EXP op)
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)
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();
355 return (isUnaArrayOp(e.op) ||
356 isBinArrayOp(e.op) ||
357 isBinAssignArrayOp(e.op) ||
364 /***************************************************
365 * Print error message about invalid array operation.
367 * e = expression with the invalid array operation
369 * instance of ErrorExp
372 ErrorExp arrayOpInvalidError(Expression e)
374 e.error("invalid array operation `%s` (possible missing [])", e.toChars());
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))
386 CatT ce = new CatT(ae.loc, ae.e1, ae.e2);
387 ae.errorSupplemental("did you mean to concatenate (`%s`) instead ?", ce.toChars());