| /** | |
| * 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()); | |
| } |