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 1
    if (e.op == TOK.slice)
40 1
        return true;
41 1
    if (e.op == TOK.arrayLiteral)
42
    {
43 1
        Type t = e.type.toBasetype();
44 1
        while (t.ty == Tarray || t.ty == Tsarray)
45 1
            t = t.nextOf().toBasetype();
46 1
        return (t.ty != Tvoid);
47
    }
48 1
    Type tb = e.type.toBasetype();
49 1
    if (tb.ty == Tarray || tb.ty == Tsarray)
50
    {
51 1
        if (isUnaArrayOp(e.op))
52
        {
53 1
            return isArrayOpValid((cast(UnaExp)e).e1);
54
        }
55 1
        if (isBinArrayOp(e.op) || isBinAssignArrayOp(e.op) || e.op == TOK.assign)
56
        {
57 1
            BinExp be = cast(BinExp)e;
58 1
            return isArrayOpValid(be.e1) && isArrayOpValid(be.e2);
59
        }
60 1
        if (e.op == TOK.construct)
61
        {
62 1
            BinExp be = cast(BinExp)e;
63 1
            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 1
        return false;
70
    }
71 1
    return true;
72
}
73

74
bool isNonAssignmentArrayOp(Expression e)
75
{
76 1
    if (e.op == TOK.slice)
77 1
        return isNonAssignmentArrayOp((cast(SliceExp)e).e1);
78

79 1
    Type tb = e.type.toBasetype();
80 1
    if (tb.ty == Tarray || tb.ty == Tsarray)
81
    {
82 1
        return (isUnaArrayOp(e.op) || isBinArrayOp(e.op));
83
    }
84 1
    return false;
85
}
86

87
bool checkNonAssignmentArrayOp(Expression e, bool suggestion = false)
88
{
89 1
    if (isNonAssignmentArrayOp(e))
90
    {
91 1
        const(char)* s = "";
92 1
        if (suggestion)
93 1
            s = " (possible missing [])";
94 1
        e.error("array operation `%s` without destination memory not allowed%s", e.toChars(), s);
95 1
        return true;
96
    }
97 1
    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 1
    Type tb = e.type.toBasetype();
119 1
    assert(tb.ty == Tarray || tb.ty == Tsarray);
120 1
    Type tbn = tb.nextOf().toBasetype();
121 1
    if (tbn.ty == Tvoid)
122
    {
123 1
        e.error("cannot perform array operations on `void[]` arrays");
124 1
        return ErrorExp.get();
125
    }
126 1
    if (!isArrayOpValid(e))
127 1
        return arrayOpInvalidError(e);
128

129 1
    auto tiargs = new Objects();
130 1
    auto args = new Expressions();
131 1
    buildArrayOp(sc, e, tiargs, args);
132

133
    import dmd.dtemplate : TemplateDeclaration;
134 1
    __gshared TemplateDeclaration arrayOp;
135 1
    if (arrayOp is null)
136
    {
137
        // Create .object._arrayOp
138 1
        Identifier idArrayOp = Identifier.idPool("_arrayOp");
139 1
        Expression id = new IdentifierExp(e.loc, Id.empty);
140 1
        id = new DotIdExp(e.loc, id, Id.object);
141 1
        id = new DotIdExp(e.loc, id, idArrayOp);
142

143 1
        id = id.expressionSemantic(sc);
144 1
        if (auto te = id.isTemplateExp())
145 1
            arrayOp = te.td;
146
        else
147 0
            ObjectNotFound(idArrayOp);   // fatal error
148
    }
149

150 1
    auto fd = resolveFuncCall(e.loc, sc, arrayOp, tiargs, null, args, FuncResolveFlag.standard);
151 1
    if (!fd || fd.errors)
152 0
        return ErrorExp.get();
153 1
    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 1
    Type tn = e.e1.type.toBasetype().nextOf();
164

165 1
    if (tn && (!tn.isMutable() || !tn.isAssignable()))
166
    {
167 1
        e.error("slice `%s` is not mutable", e.e1.toChars());
168 1
        if (e.op == TOK.addAssign)
169 1
            checkPossibleAddCatError!(AddAssignExp, CatAssignExp)(e.isAddAssignExp);
170 1
        return ErrorExp.get();
171
    }
172 1
    if (e.e1.op == TOK.arrayLiteral)
173
    {
174 1
        return e.e1.modifiableLvalue(sc, e.e1);
175
    }
176

177 1
    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 1
        extern (D) this(Scope* sc, Objects* tiargs, Expressions* args)
196
        {
197 1
            this.sc = sc;
198 1
            this.tiargs = tiargs;
199 1
            this.args = args;
200
        }
201

202
        override void visit(Expression e)
203
        {
204 1
            tiargs.push(e.type);
205 1
            args.push(e);
206
        }
207

208
        override void visit(SliceExp e)
209
        {
210 1
            visit(cast(Expression) e);
211
        }
212

213
        override void visit(CastExp e)
214
        {
215 1
            visit(cast(Expression) e);
216
        }
217

218
        override void visit(UnaExp e)
219
        {
220 1
            Type tb = e.type.toBasetype();
221 1
            if (tb.ty != Tarray && tb.ty != Tsarray) // hoist scalar expressions
222
            {
223 1
                visit(cast(Expression) e);
224
            }
225
            else
226
            {
227
                // RPN, prefix unary ops with u
228 1
                OutBuffer buf;
229 1
                buf.writestring("u");
230 1
                buf.writestring(Token.toString(e.op));
231 1
                e.e1.accept(this);
232 1
                tiargs.push(new StringExp(Loc.initial, buf.extractSlice()).expressionSemantic(sc));
233
            }
234
        }
235

236
        override void visit(BinExp e)
237
        {
238 1
            Type tb = e.type.toBasetype();
239 1
            if (tb.ty != Tarray && tb.ty != Tsarray) // hoist scalar expressions
240
            {
241 1
                visit(cast(Expression) e);
242
            }
243
            else
244
            {
245
                // RPN
246 1
                e.e1.accept(this);
247 1
                e.e2.accept(this);
248 1
                tiargs.push(new StringExp(Loc.initial, Token.toString(e.op)).expressionSemantic(sc));
249
            }
250
        }
251
    }
252

253 1
    scope v = new BuildArrayOpVisitor(sc, tiargs, args);
254 1
    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 1
    const tyf = tfrom.nextOf().toBasetype().ty;
268 1
    const tyt = tto  .nextOf().toBasetype().ty;
269 1
    return tyf == tyt ||
270 1
           tyf == Tint32 && tyt == Tfloat64;
271
}
272

273
/***********************************************
274
 * Test if expression is a unary array op.
275
 */
276
bool isUnaArrayOp(TOK op)
277
{
278 1
    switch (op)
279
    {
280 1
    case TOK.negate:
281 1
    case TOK.tilde:
282 1
        return true;
283 1
    default:
284 1
        break;
285
    }
286 1
    return false;
287
}
288

289
/***********************************************
290
 * Test if expression is a binary array op.
291
 */
292
bool isBinArrayOp(TOK op)
293
{
294 1
    switch (op)
295
    {
296 1
    case TOK.add:
297 1
    case TOK.min:
298 1
    case TOK.mul:
299 1
    case TOK.div:
300 1
    case TOK.mod:
301 1
    case TOK.xor:
302 1
    case TOK.and:
303 1
    case TOK.or:
304 1
    case TOK.pow:
305 1
        return true;
306 1
    default:
307 1
        break;
308
    }
309 1
    return false;
310
}
311

312
/***********************************************
313
 * Test if expression is a binary assignment array op.
314
 */
315
bool isBinAssignArrayOp(TOK op)
316
{
317 1
    switch (op)
318
    {
319 1
    case TOK.addAssign:
320 1
    case TOK.minAssign:
321 1
    case TOK.mulAssign:
322 1
    case TOK.divAssign:
323 1
    case TOK.modAssign:
324 1
    case TOK.xorAssign:
325 1
    case TOK.andAssign:
326 1
    case TOK.orAssign:
327 1
    case TOK.powAssign:
328 1
        return true;
329 1
    default:
330 1
        break;
331
    }
332 1
    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 1
    if (e.op == TOK.slice)
342 1
        return true;
343 1
    if (e.op == TOK.arrayLiteral)
344
    {
345 1
        Type t = e.type.toBasetype();
346 1
        while (t.ty == Tarray || t.ty == Tsarray)
347 1
            t = t.nextOf().toBasetype();
348 1
        return (t.ty != Tvoid);
349
    }
350 1
    Type tb = e.type.toBasetype();
351 1
    if (tb.ty == Tarray)
352
    {
353 1
        return (isUnaArrayOp(e.op) ||
354 1
                isBinArrayOp(e.op) ||
355 1
                isBinAssignArrayOp(e.op) ||
356 1
                e.op == TOK.assign);
357
    }
358 1
    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 1
    e.error("invalid array operation `%s` (possible missing [])", e.toChars());
373 1
    if (e.op == TOK.add)
374 1
        checkPossibleAddCatError!(AddExp, CatExp)(e.isAddExp());
375 1
    else if (e.op == TOK.addAssign)
376 1
        checkPossibleAddCatError!(AddAssignExp, CatAssignExp)(e.isAddAssignExp());
377 1
    return ErrorExp.get();
378
}
379

380
private void checkPossibleAddCatError(AddT, CatT)(AddT ae)
381
{
382 1
    if (!ae.e2.type || ae.e2.type.ty != Tarray || !ae.e2.type.implicitConvTo(ae.e1.type))
383 1
        return;
384 1
    CatT ce = new CatT(ae.loc, ae.e1, ae.e2);
385 1
    ae.errorSupplemental("did you mean to concatenate (`%s`) instead ?", ce.toChars());
386
}

Read our documentation on viewing source code .

Loading