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
16 import core
.stdc
.stdio
;
17 import dmd
.arraytypes
;
19 import dmd
.declaration
;
23 import dmd
.expression
;
24 import dmd
.expressionsem
;
28 import dmd
.identifier
;
31 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 error(e
.loc
, "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/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();
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
;
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())
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
);
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
;
196 extern (D
) this(Scope
* sc
, Objects
* tiargs
) scope @safe
199 this.tiargs
= tiargs
;
200 this.args
= new Expressions();
203 override void visit(Expression 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
);
228 // RPN, prefix unary ops with u
230 buf
.writestring("u");
231 buf
.writestring(EXPtoString(e
.op
));
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
);
249 tiargs
.push(new StringExp(Loc
.initial
, EXPtoString(e
.op
)).expressionSemantic(sc
));
254 scope v
= new BuildArrayOpVisitor(sc
, tiargs
);
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
) @safe
291 /***********************************************
292 * Test if expression is a binary array op.
294 bool isBinArrayOp(EXP op
) @safe
314 /***********************************************
315 * Test if expression is a binary assignment array op.
317 bool isBinAssignArrayOp(EXP op
) @safe
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 error(e
.loc
, "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 errorSupplemental(ae
.loc
, "did you mean to concatenate (`%s`) instead ?", ce
.toChars());