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-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 12 */ 13 14 module dmd.arrayop; 15 16 import core.stdc.stdio; 17 import dmd.arraytypes; 18 import dmd.astenums; 19 import dmd.declaration; 20 import dmd.dscope; 21 import dmd.dsymbol; 22 import dmd.expression; 23 import dmd.expressionsem; 24 import dmd.func; 25 import dmd.globals; 26 import dmd.hdrgen; 27 import dmd.id; 28 import dmd.identifier; 29 import dmd.location; 30 import dmd.mtype; 31 import dmd.common.outbuffer; 32 import dmd.statement; 33 import dmd.tokens; 34 import dmd.visitor; 35 36 /********************************************** 37 * Check that there are no uses of arrays without []. 38 */ 39 bool isArrayOpValid(Expression e) 40 { 41 //printf("isArrayOpValid() %s\n", e.toChars()); 42 if (e.op == EXP.slice) 43 return true; 44 if (e.op == EXP.arrayLiteral) 45 { 46 Type t = e.type.toBasetype(); 47 while (t.ty == Tarray || t.ty == Tsarray) 48 t = t.nextOf().toBasetype(); 49 return (t.ty != Tvoid); 50 } 51 Type tb = e.type.toBasetype(); 52 if (tb.ty == Tarray || tb.ty == Tsarray) 53 { 54 if (isUnaArrayOp(e.op)) 55 { 56 return isArrayOpValid(e.isUnaExp().e1); 57 } 58 if (isBinArrayOp(e.op) || isBinAssignArrayOp(e.op) || e.op == EXP.assign) 59 { 60 BinExp be = e.isBinExp(); 61 return isArrayOpValid(be.e1) && isArrayOpValid(be.e2); 62 } 63 if (e.op == EXP.construct) 64 { 65 BinExp be = e.isBinExp(); 66 return be.e1.op == EXP.slice && isArrayOpValid(be.e2); 67 } 68 // if (e.op == EXP.call) 69 // { 70 // TODO: Decide if [] is required after arrayop calls. 71 // } 72 return false; 73 } 74 return true; 75 } 76 77 bool isNonAssignmentArrayOp(Expression e) 78 { 79 if (e.op == EXP.slice) 80 return isNonAssignmentArrayOp(e.isSliceExp().e1); 81 82 Type tb = e.type.toBasetype(); 83 if (tb.ty == Tarray || tb.ty == Tsarray) 84 { 85 return (isUnaArrayOp(e.op) || isBinArrayOp(e.op)); 86 } 87 return false; 88 } 89 90 bool checkNonAssignmentArrayOp(Expression e, bool suggestion = false) 91 { 92 if (isNonAssignmentArrayOp(e)) 93 { 94 const(char)* s = ""; 95 if (suggestion) 96 s = " (possible missing [])"; 97 e.error("array operation `%s` without destination memory not allowed%s", e.toChars(), s); 98 return true; 99 } 100 return false; 101 } 102 103 /*********************************** 104 * Construct the array operation expression, call object._arrayOp!(tiargs)(args). 105 * 106 * Encode operand types and operations into tiargs using reverse polish notation (RPN) to preserve precedence. 107 * Unary operations are prefixed with "u" (e.g. "u~"). 108 * Pass operand values (slices or scalars) as args. 109 * 110 * Scalar expression sub-trees of `e` are evaluated before calling 111 * into druntime to hoist them out of the loop. This is a valid 112 * evaluation order as the actual array operations have no 113 * side-effect. 114 * References: 115 * https://github.com/dlang/dmd/blob/cdfadf8a18f474e6a1b8352af2541efe3e3467cc/druntime/src/object.d#L4694 116 * https://github.com/dlang/dmd/blob/master/druntime/src/core/internal/array/operations.d 117 */ 118 Expression arrayOp(BinExp e, Scope* sc) 119 { 120 //printf("BinExp.arrayOp() %s\n", e.toChars()); 121 Type tb = e.type.toBasetype(); 122 assert(tb.ty == Tarray || tb.ty == Tsarray); 123 Type tbn = tb.nextOf().toBasetype(); 124 if (tbn.ty == Tvoid) 125 { 126 e.error("cannot perform array operations on `void[]` arrays"); 127 return ErrorExp.get(); 128 } 129 if (!isArrayOpValid(e)) 130 return arrayOpInvalidError(e); 131 132 auto tiargs = new Objects(); 133 auto args = buildArrayOp(sc, e, tiargs); 134 135 import dmd.dtemplate : TemplateDeclaration; 136 __gshared TemplateDeclaration arrayOp; 137 if (arrayOp is null) 138 { 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); 144 145 id = id.expressionSemantic(sc); 146 if (auto te = id.isTemplateExp()) 147 arrayOp = te.td; 148 else 149 ObjectNotFound(idArrayOp); // fatal error 150 } 151 152 auto fd = resolveFuncCall(e.loc, sc, arrayOp, tiargs, null, ArgumentList(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); 156 } 157 158 /// ditto 159 Expression arrayOp(BinAssignExp e, Scope* sc) 160 { 161 //printf("BinAssignExp.arrayOp() %s\n", e.toChars()); 162 163 /* Check that the elements of e1 can be assigned to 164 */ 165 Type tn = e.e1.type.toBasetype().nextOf(); 166 167 if (tn && (!tn.isMutable() || !tn.isAssignable())) 168 { 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(); 173 } 174 if (e.e1.op == EXP.arrayLiteral) 175 { 176 return e.e1.modifiableLvalue(sc, e.e1); 177 } 178 179 return arrayOp(e.isBinExp(), sc); 180 } 181 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. 186 */ 187 private Expressions* buildArrayOp(Scope* sc, Expression e, Objects* tiargs) 188 { 189 extern (C++) final class BuildArrayOpVisitor : Visitor 190 { 191 alias visit = Visitor.visit; 192 Scope* sc; 193 Objects* tiargs; 194 Expressions* args; 195 196 public: 197 extern (D) this(Scope* sc, Objects* tiargs) scope 198 { 199 this.sc = sc; 200 this.tiargs = tiargs; 201 this.args = new Expressions(); 202 } 203 204 override void visit(Expression e) 205 { 206 tiargs.push(e.type); 207 args.push(e); 208 } 209 210 override void visit(SliceExp e) 211 { 212 visit(cast(Expression) e); 213 } 214 215 override void visit(CastExp e) 216 { 217 visit(cast(Expression) e); 218 } 219 220 override void visit(UnaExp e) 221 { 222 Type tb = e.type.toBasetype(); 223 if (tb.ty != Tarray && tb.ty != Tsarray) // hoist scalar expressions 224 { 225 visit(cast(Expression) e); 226 } 227 else 228 { 229 // RPN, prefix unary ops with u 230 OutBuffer buf; 231 buf.writestring("u"); 232 buf.writestring(EXPtoString(e.op)); 233 e.e1.accept(this); 234 tiargs.push(new StringExp(Loc.initial, buf.extractSlice()).expressionSemantic(sc)); 235 } 236 } 237 238 override void visit(BinExp e) 239 { 240 Type tb = e.type.toBasetype(); 241 if (tb.ty != Tarray && tb.ty != Tsarray) // hoist scalar expressions 242 { 243 visit(cast(Expression) e); 244 } 245 else 246 { 247 // RPN 248 e.e1.accept(this); 249 e.e2.accept(this); 250 tiargs.push(new StringExp(Loc.initial, EXPtoString(e.op)).expressionSemantic(sc)); 251 } 252 } 253 } 254 255 scope v = new BuildArrayOpVisitor(sc, tiargs); 256 e.accept(v); 257 return v.args; 258 } 259 260 /*********************************************** 261 * Some implicit casting can be performed by the _arrayOp template. 262 * Params: 263 * tfrom = type converting from 264 * tto = type converting to 265 * Returns: 266 * true if can be performed by _arrayOp 267 */ 268 bool isArrayOpImplicitCast(TypeDArray tfrom, TypeDArray tto) 269 { 270 const tyf = tfrom.nextOf().toBasetype().ty; 271 const tyt = tto .nextOf().toBasetype().ty; 272 return tyf == tyt || 273 tyf == Tint32 && tyt == Tfloat64; 274 } 275 276 /*********************************************** 277 * Test if expression is a unary array op. 278 */ 279 bool isUnaArrayOp(EXP op) 280 { 281 switch (op) 282 { 283 case EXP.negate: 284 case EXP.tilde: 285 return true; 286 default: 287 break; 288 } 289 return false; 290 } 291 292 /*********************************************** 293 * Test if expression is a binary array op. 294 */ 295 bool isBinArrayOp(EXP op) 296 { 297 switch (op) 298 { 299 case EXP.add: 300 case EXP.min: 301 case EXP.mul: 302 case EXP.div: 303 case EXP.mod: 304 case EXP.xor: 305 case EXP.and: 306 case EXP.or: 307 case EXP.pow: 308 return true; 309 default: 310 break; 311 } 312 return false; 313 } 314 315 /*********************************************** 316 * Test if expression is a binary assignment array op. 317 */ 318 bool isBinAssignArrayOp(EXP op) 319 { 320 switch (op) 321 { 322 case EXP.addAssign: 323 case EXP.minAssign: 324 case EXP.mulAssign: 325 case EXP.divAssign: 326 case EXP.modAssign: 327 case EXP.xorAssign: 328 case EXP.andAssign: 329 case EXP.orAssign: 330 case EXP.powAssign: 331 return true; 332 default: 333 break; 334 } 335 return false; 336 } 337 338 /*********************************************** 339 * Test if operand is a valid array op operand. 340 */ 341 bool isArrayOpOperand(Expression e) 342 { 343 //printf("Expression.isArrayOpOperand() %s\n", e.toChars()); 344 if (e.op == EXP.slice) 345 return true; 346 if (e.op == EXP.arrayLiteral) 347 { 348 Type t = e.type.toBasetype(); 349 while (t.ty == Tarray || t.ty == Tsarray) 350 t = t.nextOf().toBasetype(); 351 return (t.ty != Tvoid); 352 } 353 Type tb = e.type.toBasetype(); 354 if (tb.ty == Tarray) 355 { 356 return (isUnaArrayOp(e.op) || 357 isBinArrayOp(e.op) || 358 isBinAssignArrayOp(e.op) || 359 e.op == EXP.assign); 360 } 361 return false; 362 } 363 364 365 /*************************************************** 366 * Print error message about invalid array operation. 367 * Params: 368 * e = expression with the invalid array operation 369 * Returns: 370 * instance of ErrorExp 371 */ 372 373 ErrorExp arrayOpInvalidError(Expression e) 374 { 375 e.error("invalid array operation `%s` (possible missing [])", e.toChars()); 376 if (e.op == EXP.add) 377 checkPossibleAddCatError!(AddExp, CatExp)(e.isAddExp()); 378 else if (e.op == EXP.addAssign) 379 checkPossibleAddCatError!(AddAssignExp, CatAssignExp)(e.isAddAssignExp()); 380 return ErrorExp.get(); 381 } 382 383 private void checkPossibleAddCatError(AddT, CatT)(AddT ae) 384 { 385 if (!ae.e2.type || ae.e2.type.ty != Tarray || !ae.e2.type.implicitConvTo(ae.e1.type)) 386 return; 387 CatT ce = new CatT(ae.loc, ae.e1, ae.e2); 388 ae.errorSupplemental("did you mean to concatenate (`%s`) instead ?", ce.toChars()); 389 }