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