1 /** 2 * Compile-time checks associated with the @mustuse attribute. 3 * 4 * Copyright: Copyright (C) 2022-2023 by The D Language Foundation, All Rights Reserved 5 * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/mustuse.d, _mustuse.d) 7 * Documentation: https://dlang.org/phobos/dmd_mustuse.html 8 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/mustuse.d 9 */ 10 11 module dmd.mustuse; 12 13 import dmd.dscope; 14 import dmd.dsymbol; 15 import dmd.errors; 16 import dmd.expression; 17 import dmd.globals; 18 import dmd.identifier; 19 import dmd.location; 20 21 /** 22 * Check whether discarding an expression would violate the requirements of 23 * @mustuse. If so, emit an error. 24 * 25 * Params: 26 * e = the expression to check 27 * sc = scope in which `e` was semantically analyzed 28 * 29 * Returns: true on error, false on success. 30 */ 31 bool checkMustUse(Expression e, Scope* sc) 32 { 33 import dmd.id : Id; 34 35 assert(e.type); 36 if (auto sym = e.type.toDsymbol(sc)) 37 { 38 auto sd = sym.isStructDeclaration(); 39 // isStructDeclaration returns non-null for both structs and unions 40 if (sd && hasMustUseAttribute(sd, sc) && !isAssignment(e) && !isIncrementOrDecrement(e)) 41 { 42 error(e.loc, "ignored value of `@%s` type `%s`; prepend a `cast(void)` if intentional", 43 Id.udaMustUse.toChars(), e.type.toPrettyChars(true)); 44 return true; 45 } 46 } 47 return false; 48 } 49 50 /** 51 * Called from a symbol's semantic to check for reserved usage of @mustuse. 52 * 53 * If such usage is found, emits an errror. 54 * 55 * Params: 56 * sym = symbol to check 57 */ 58 void checkMustUseReserved(Dsymbol sym) 59 { 60 import dmd.attrib : foreachUdaNoSemantic; 61 import dmd.errors : error; 62 import dmd.id : Id; 63 64 // Can't use foreachUda (and by extension hasMustUseAttribute) while 65 // semantic analysis of `sym` is still in progress 66 foreachUdaNoSemantic(sym, (exp) { 67 if (isMustUseAttribute(exp)) 68 { 69 if (sym.isFuncDeclaration()) 70 { 71 error(sym.loc, "`@%s` on functions is reserved for future use", 72 Id.udaMustUse.toChars()); 73 sym.errors = true; 74 } 75 else if (sym.isClassDeclaration() || sym.isEnumDeclaration()) 76 { 77 error(sym.loc, "`@%s` on `%s` types is reserved for future use", 78 Id.udaMustUse.toChars(), sym.kind()); 79 sym.errors = true; 80 } 81 } 82 return 0; // continue 83 }); 84 } 85 86 /** 87 * Returns: true if the given expression is an assignment, either simple (a = b) 88 * or compound (a += b, etc). 89 */ 90 private bool isAssignment(Expression e) 91 { 92 if (e.isAssignExp || e.isBinAssignExp || e.isConstructExp || e.isBlitExp) 93 return true; 94 if (auto ce = e.isCallExp()) 95 { 96 if (auto fd = ce.f) 97 { 98 auto id = fd.ident; 99 if (id && isAssignmentOpId(id)) 100 return true; 101 } 102 } 103 return false; 104 } 105 106 /** 107 * Returns: true if id is the identifier of an assignment operator overload. 108 */ 109 private bool isAssignmentOpId(Identifier id) 110 { 111 import dmd.id : Id; 112 113 return id == Id.assign 114 || id == Id.addass 115 || id == Id.subass 116 || id == Id.mulass 117 || id == Id.divass 118 || id == Id.modass 119 || id == Id.andass 120 || id == Id.orass 121 || id == Id.xorass 122 || id == Id.shlass 123 || id == Id.shrass 124 || id == Id.ushrass 125 || id == Id.catass 126 || id == Id.indexass 127 || id == Id.slice 128 || id == Id.sliceass 129 || id == Id.opOpAssign 130 || id == Id.opIndexOpAssign 131 || id == Id.opSliceOpAssign 132 || id == Id.powass; 133 } 134 135 /** 136 * Returns: true if the given expression is an increment (++) or decrement (--). 137 */ 138 private bool isIncrementOrDecrement(Expression e) 139 { 140 import dmd.dtemplate : isExpression; 141 import dmd.location; 142 import dmd.id : Id; 143 import dmd.tokens : EXP; 144 145 if (e.op == EXP.plusPlus 146 || e.op == EXP.minusMinus 147 || e.op == EXP.prePlusPlus 148 || e.op == EXP.preMinusMinus) 149 return true; 150 if (auto call = e.isCallExp()) 151 { 152 // Check for overloaded preincrement 153 // e.g., a.opUnary!"++" 154 if (auto fd = call.f) 155 { 156 if (fd.ident == Id.opUnary && fd.parent) 157 { 158 if (auto ti = fd.parent.isTemplateInstance()) 159 { 160 auto tiargs = ti.tiargs; 161 if (tiargs && tiargs.length >= 1) 162 { 163 if (auto argExp = (*tiargs)[0].isExpression()) 164 { 165 if (auto op = argExp.isStringExp()) 166 { 167 if (op.len == 2 && op.sz == 1) 168 { 169 const s = op.peekString(); 170 if (s == "++" || s == "--") 171 return true; 172 } 173 } 174 } 175 } 176 } 177 } 178 } 179 } 180 else if (auto comma = e.isCommaExp()) 181 { 182 // Check for overloaded postincrement 183 // e.g., (auto tmp = a, ++a, tmp) 184 if (comma.e1) 185 { 186 if (auto left = comma.e1.isCommaExp()) 187 { 188 if (auto middle = left.e2) 189 { 190 if (middle && isIncrementOrDecrement(middle)) 191 return true; 192 } 193 } 194 } 195 } 196 return false; 197 } 198 199 /** 200 * Returns: true if the given symbol has the @mustuse attribute. 201 */ 202 private bool hasMustUseAttribute(Dsymbol sym, Scope* sc) 203 { 204 import dmd.attrib : foreachUda; 205 206 bool result = false; 207 208 foreachUda(sym, sc, (Expression uda) { 209 if (isMustUseAttribute(uda)) 210 { 211 result = true; 212 return 1; // break 213 } 214 return 0; // continue 215 }); 216 217 return result; 218 } 219 220 /** 221 * Returns: true if the given expression is core.attribute.mustuse. 222 */ 223 private bool isMustUseAttribute(Expression e) 224 { 225 import dmd.attrib : isCoreUda; 226 import dmd.id : Id; 227 228 // Logic based on dmd.objc.Supported.declaredAsOptionalCount 229 auto typeExp = e.isTypeExp; 230 if (!typeExp) 231 return false; 232 233 auto typeEnum = typeExp.type.isTypeEnum(); 234 if (!typeEnum) 235 return false; 236 237 if (isCoreUda(typeEnum.sym, Id.udaMustUse)) 238 return true; 239 240 return false; 241 }