/** * Implement array operations, such as `a[] = b[] + c[]`. * * Specification: $(LINK2 https://dlang.org/spec/arrays.html#array-operations, Array Operations) * * Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/arrayop.d, _arrayop.d) * Documentation: https://dlang.org/phobos/dmd_arrayop.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/arrayop.d */ module dmd.arrayop; import core.stdc.stdio; import dmd.arraytypes; import dmd.astenums; import dmd.declaration; import dmd.dscope; import dmd.dsymbol; import dmd.expression; import dmd.expressionsem; import dmd.func; import dmd.globals; import dmd.hdrgen; import dmd.id; import dmd.identifier; import dmd.mtype; import dmd.common.outbuffer; import dmd.statement; import dmd.tokens; import dmd.visitor; /********************************************** * Check that there are no uses of arrays without []. */ bool isArrayOpValid(Expression e) { //printf("isArrayOpValid() %s\n", e.toChars()); if (e.op == EXP.slice) return true; if (e.op == EXP.arrayLiteral) { Type t = e.type.toBasetype(); while (t.ty == Tarray || t.ty == Tsarray) t = t.nextOf().toBasetype(); return (t.ty != Tvoid); } Type tb = e.type.toBasetype(); if (tb.ty == Tarray || tb.ty == Tsarray) { if (isUnaArrayOp(e.op)) { return isArrayOpValid(e.isUnaExp().e1); } if (isBinArrayOp(e.op) || isBinAssignArrayOp(e.op) || e.op == EXP.assign) { BinExp be = e.isBinExp(); return isArrayOpValid(be.e1) && isArrayOpValid(be.e2); } if (e.op == EXP.construct) { BinExp be = e.isBinExp(); return be.e1.op == EXP.slice && isArrayOpValid(be.e2); } // if (e.op == EXP.call) // { // TODO: Decide if [] is required after arrayop calls. // } return false; } return true; } bool isNonAssignmentArrayOp(Expression e) { if (e.op == EXP.slice) return isNonAssignmentArrayOp(e.isSliceExp().e1); Type tb = e.type.toBasetype(); if (tb.ty == Tarray || tb.ty == Tsarray) { return (isUnaArrayOp(e.op) || isBinArrayOp(e.op)); } return false; } bool checkNonAssignmentArrayOp(Expression e, bool suggestion = false) { if (isNonAssignmentArrayOp(e)) { const(char)* s = ""; if (suggestion) s = " (possible missing [])"; e.error("array operation `%s` without destination memory not allowed%s", e.toChars(), s); return true; } return false; } /*********************************** * Construct the array operation expression, call object._arrayOp!(tiargs)(args). * * Encode operand types and operations into tiargs using reverse polish notation (RPN) to preserve precedence. * Unary operations are prefixed with "u" (e.g. "u~"). * Pass operand values (slices or scalars) as args. * * Scalar expression sub-trees of `e` are evaluated before calling * into druntime to hoist them out of the loop. This is a valid * evaluation order as the actual array operations have no * side-effect. * References: * https://github.com/dlang/druntime/blob/master/src/object.d#L3944 * https://github.com/dlang/druntime/blob/master/src/core/internal/array/operations.d */ Expression arrayOp(BinExp e, Scope* sc) { //printf("BinExp.arrayOp() %s\n", e.toChars()); Type tb = e.type.toBasetype(); assert(tb.ty == Tarray || tb.ty == Tsarray); Type tbn = tb.nextOf().toBasetype(); if (tbn.ty == Tvoid) { e.error("cannot perform array operations on `void[]` arrays"); return ErrorExp.get(); } if (!isArrayOpValid(e)) return arrayOpInvalidError(e); auto tiargs = new Objects(); auto args = new Expressions(); buildArrayOp(sc, e, tiargs, args); import dmd.dtemplate : TemplateDeclaration; __gshared TemplateDeclaration arrayOp; if (arrayOp is null) { // Create .object._arrayOp Identifier idArrayOp = Identifier.idPool("_arrayOp"); Expression id = new IdentifierExp(e.loc, Id.empty); id = new DotIdExp(e.loc, id, Id.object); id = new DotIdExp(e.loc, id, idArrayOp); id = id.expressionSemantic(sc); if (auto te = id.isTemplateExp()) arrayOp = te.td; else ObjectNotFound(idArrayOp); // fatal error } auto fd = resolveFuncCall(e.loc, sc, arrayOp, tiargs, null, args, FuncResolveFlag.standard); if (!fd || fd.errors) return ErrorExp.get(); return new CallExp(e.loc, new VarExp(e.loc, fd, false), args).expressionSemantic(sc); } /// ditto Expression arrayOp(BinAssignExp e, Scope* sc) { //printf("BinAssignExp.arrayOp() %s\n", e.toChars()); /* Check that the elements of e1 can be assigned to */ Type tn = e.e1.type.toBasetype().nextOf(); if (tn && (!tn.isMutable() || !tn.isAssignable())) { e.error("slice `%s` is not mutable", e.e1.toChars()); if (e.op == EXP.addAssign) checkPossibleAddCatError!(AddAssignExp, CatAssignExp)(e.isAddAssignExp); return ErrorExp.get(); } if (e.e1.op == EXP.arrayLiteral) { return e.e1.modifiableLvalue(sc, e.e1); } return arrayOp(e.isBinExp(), sc); } /****************************************** * Convert the expression tree e to template and function arguments, * using reverse polish notation (RPN) to encode order of operations. * Encode operations as string arguments, using a "u" prefix for unary operations. */ private void buildArrayOp(Scope* sc, Expression e, Objects* tiargs, Expressions* args) { extern (C++) final class BuildArrayOpVisitor : Visitor { alias visit = Visitor.visit; Scope* sc; Objects* tiargs; Expressions* args; public: extern (D) this(Scope* sc, Objects* tiargs, Expressions* args) { this.sc = sc; this.tiargs = tiargs; this.args = args; } override void visit(Expression e) { tiargs.push(e.type); args.push(e); } override void visit(SliceExp e) { visit(cast(Expression) e); } override void visit(CastExp e) { visit(cast(Expression) e); } override void visit(UnaExp e) { Type tb = e.type.toBasetype(); if (tb.ty != Tarray && tb.ty != Tsarray) // hoist scalar expressions { visit(cast(Expression) e); } else { // RPN, prefix unary ops with u OutBuffer buf; buf.writestring("u"); buf.writestring(EXPtoString(e.op)); e.e1.accept(this); tiargs.push(new StringExp(Loc.initial, buf.extractSlice()).expressionSemantic(sc)); } } override void visit(BinExp e) { Type tb = e.type.toBasetype(); if (tb.ty != Tarray && tb.ty != Tsarray) // hoist scalar expressions { visit(cast(Expression) e); } else { // RPN e.e1.accept(this); e.e2.accept(this); tiargs.push(new StringExp(Loc.initial, EXPtoString(e.op)).expressionSemantic(sc)); } } } scope v = new BuildArrayOpVisitor(sc, tiargs, args); e.accept(v); } /*********************************************** * Some implicit casting can be performed by the _arrayOp template. * Params: * tfrom = type converting from * tto = type converting to * Returns: * true if can be performed by _arrayOp */ bool isArrayOpImplicitCast(TypeDArray tfrom, TypeDArray tto) { const tyf = tfrom.nextOf().toBasetype().ty; const tyt = tto .nextOf().toBasetype().ty; return tyf == tyt || tyf == Tint32 && tyt == Tfloat64; } /*********************************************** * Test if expression is a unary array op. */ bool isUnaArrayOp(EXP op) { switch (op) { case EXP.negate: case EXP.tilde: return true; default: break; } return false; } /*********************************************** * Test if expression is a binary array op. */ bool isBinArrayOp(EXP op) { switch (op) { case EXP.add: case EXP.min: case EXP.mul: case EXP.div: case EXP.mod: case EXP.xor: case EXP.and: case EXP.or: case EXP.pow: return true; default: break; } return false; } /*********************************************** * Test if expression is a binary assignment array op. */ bool isBinAssignArrayOp(EXP op) { switch (op) { case EXP.addAssign: case EXP.minAssign: case EXP.mulAssign: case EXP.divAssign: case EXP.modAssign: case EXP.xorAssign: case EXP.andAssign: case EXP.orAssign: case EXP.powAssign: return true; default: break; } return false; } /*********************************************** * Test if operand is a valid array op operand. */ bool isArrayOpOperand(Expression e) { //printf("Expression.isArrayOpOperand() %s\n", e.toChars()); if (e.op == EXP.slice) return true; if (e.op == EXP.arrayLiteral) { Type t = e.type.toBasetype(); while (t.ty == Tarray || t.ty == Tsarray) t = t.nextOf().toBasetype(); return (t.ty != Tvoid); } Type tb = e.type.toBasetype(); if (tb.ty == Tarray) { return (isUnaArrayOp(e.op) || isBinArrayOp(e.op) || isBinAssignArrayOp(e.op) || e.op == EXP.assign); } return false; } /*************************************************** * Print error message about invalid array operation. * Params: * e = expression with the invalid array operation * Returns: * instance of ErrorExp */ ErrorExp arrayOpInvalidError(Expression e) { e.error("invalid array operation `%s` (possible missing [])", e.toChars()); if (e.op == EXP.add) checkPossibleAddCatError!(AddExp, CatExp)(e.isAddExp()); else if (e.op == EXP.addAssign) checkPossibleAddCatError!(AddAssignExp, CatAssignExp)(e.isAddAssignExp()); return ErrorExp.get(); } private void checkPossibleAddCatError(AddT, CatT)(AddT ae) { if (!ae.e2.type || ae.e2.type.ty != Tarray || !ae.e2.type.implicitConvTo(ae.e1.type)) return; CatT ce = new CatT(ae.loc, ae.e1, ae.e2); ae.errorSupplemental("did you mean to concatenate (`%s`) instead ?", ce.toChars()); }