blob: 4eb422805db8ed765986edea48d791700a7b2d63 [file] [log] [blame]
/**
* Compile-time checks associated with the @mustuse attribute.
*
* Copyright: Copyright (C) 2022 by The D Language Foundation, All Rights Reserved
* 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/mustuse.d, _mustuse.d)
* Documentation: https://dlang.org/phobos/dmd_mustuse.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/mustuse.d
*/
module dmd.mustuse;
import dmd.dscope;
import dmd.dsymbol;
import dmd.expression;
import dmd.globals;
import dmd.identifier;
// Used in isIncrementOrDecrement
private static const StringExp plusPlus, minusMinus;
// Loc.initial cannot be used in static initializers, so
// these need a static constructor.
static this()
{
plusPlus = new StringExp(Loc.initial, "++");
minusMinus = new StringExp(Loc.initial, "--");
}
/**
* Check whether discarding an expression would violate the requirements of
* @mustuse. If so, emit an error.
*
* Params:
* e = the expression to check
* sc = scope in which `e` was semantically analyzed
*
* Returns: true on error, false on success.
*/
bool checkMustUse(Expression e, Scope* sc)
{
import dmd.id : Id;
assert(e.type);
if (auto sym = e.type.toDsymbol(sc))
{
auto sd = sym.isStructDeclaration();
// isStructDeclaration returns non-null for both structs and unions
if (sd && hasMustUseAttribute(sd, sc) && !isAssignment(e) && !isIncrementOrDecrement(e))
{
e.error("ignored value of `@%s` type `%s`; prepend a `cast(void)` if intentional",
Id.udaMustUse.toChars(), e.type.toPrettyChars(true));
return true;
}
}
return false;
}
/**
* Called from a symbol's semantic to check for reserved usage of @mustuse.
*
* If such usage is found, emits an errror.
*
* Params:
* sym = symbol to check
*/
void checkMustUseReserved(Dsymbol sym)
{
import dmd.attrib : foreachUdaNoSemantic;
import dmd.errors : error;
import dmd.id : Id;
// Can't use foreachUda (and by extension hasMustUseAttribute) while
// semantic analysis of `sym` is still in progress
foreachUdaNoSemantic(sym, (exp) {
if (isMustUseAttribute(exp))
{
if (sym.isFuncDeclaration())
{
error(sym.loc, "`@%s` on functions is reserved for future use",
Id.udaMustUse.toChars());
sym.errors = true;
}
else if (sym.isClassDeclaration() || sym.isEnumDeclaration())
{
error(sym.loc, "`@%s` on `%s` types is reserved for future use",
Id.udaMustUse.toChars(), sym.kind());
sym.errors = true;
}
}
return 0; // continue
});
}
/**
* Returns: true if the given expression is an assignment, either simple (a = b)
* or compound (a += b, etc).
*/
private bool isAssignment(Expression e)
{
if (e.isAssignExp || e.isBinAssignExp)
return true;
if (auto ce = e.isCallExp())
{
if (auto fd = ce.f)
{
auto id = fd.ident;
if (id && isAssignmentOpId(id))
return true;
}
}
return false;
}
/**
* Returns: true if id is the identifier of an assignment operator overload.
*/
private bool isAssignmentOpId(Identifier id)
{
import dmd.id : Id;
return id == Id.assign
|| id == Id.addass
|| id == Id.subass
|| id == Id.mulass
|| id == Id.divass
|| id == Id.modass
|| id == Id.andass
|| id == Id.orass
|| id == Id.xorass
|| id == Id.shlass
|| id == Id.shrass
|| id == Id.ushrass
|| id == Id.catass
|| id == Id.indexass
|| id == Id.slice
|| id == Id.sliceass
|| id == Id.opOpAssign
|| id == Id.opIndexOpAssign
|| id == Id.opSliceOpAssign
|| id == Id.powass;
}
/**
* Returns: true if the given expression is an increment (++) or decrement (--).
*/
private bool isIncrementOrDecrement(Expression e)
{
import dmd.dtemplate : isExpression;
import dmd.globals : Loc;
import dmd.id : Id;
import dmd.tokens : EXP;
if (e.op == EXP.plusPlus
|| e.op == EXP.minusMinus
|| e.op == EXP.prePlusPlus
|| e.op == EXP.preMinusMinus)
return true;
if (auto call = e.isCallExp())
{
// Check for overloaded preincrement
// e.g., a.opUnary!"++"
if (auto fd = call.f)
{
if (fd.ident == Id.opUnary && fd.parent)
{
if (auto ti = fd.parent.isTemplateInstance())
{
auto tiargs = ti.tiargs;
if (tiargs && tiargs.length >= 1)
{
if (auto argExp = (*tiargs)[0].isExpression())
{
auto op = argExp.isStringExp();
if (op && (op.compare(plusPlus) == 0 || op.compare(minusMinus) == 0))
return true;
}
}
}
}
}
}
else if (auto comma = e.isCommaExp())
{
// Check for overloaded postincrement
// e.g., (auto tmp = a, ++a, tmp)
if (comma.e1)
{
if (auto left = comma.e1.isCommaExp())
{
if (auto middle = left.e2)
{
if (middle && isIncrementOrDecrement(middle))
return true;
}
}
}
}
return false;
}
/**
* Returns: true if the given symbol has the @mustuse attribute.
*/
private bool hasMustUseAttribute(Dsymbol sym, Scope* sc)
{
import dmd.attrib : foreachUda;
bool result = false;
foreachUda(sym, sc, (Expression uda) {
if (isMustUseAttribute(uda))
{
result = true;
return 1; // break
}
return 0; // continue
});
return result;
}
/**
* Returns: true if the given expression is core.attribute.mustuse.
*/
private bool isMustUseAttribute(Expression e)
{
import dmd.attrib : isCoreUda;
import dmd.id : Id;
// Logic based on dmd.objc.Supported.declaredAsOptionalCount
auto typeExp = e.isTypeExp;
if (!typeExp)
return false;
auto typeEnum = typeExp.type.isTypeEnum();
if (!typeEnum)
return false;
if (isCoreUda(typeEnum.sym, Id.udaMustUse))
return true;
return false;
}