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 }