Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement DIP 1038: @mustuse #13589

Merged
merged 25 commits into from
Apr 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0a0a5db
DIP 1038: add check for discarded @mustUse values
pbackus Jan 29, 2022
1ca8126
DIP 1038: add more unit tests
pbackus Jan 30, 2022
252ff63
DIP 1038: support overloaded assignment operators
pbackus Jan 30, 2022
37352c8
DIP 1038: support overloaded ++ and --
pbackus Jan 30, 2022
96e1c87
DIP 1038: error on reserved use of @mustUse
pbackus Jan 30, 2022
6143a09
DIP 1038: update frontend.h
pbackus Jan 30, 2022
59ecf35
dmd.must_use: add module ddoc comment
pbackus Jan 30, 2022
663cd24
DIP 1038: use qualified type name in error message
pbackus Jan 30, 2022
64915e8
DIP 1038: add must_use.d to src/dmd/README.md
pbackus Jan 30, 2022
6ad79d6
Move startsWith and endsWith to dmd.common.string
pbackus Jan 30, 2022
e7211ee
Simplify id comparison in isIncrementOrDecrement
pbackus Jan 30, 2022
041bd06
Remove unneeded dynamic cast in checkMustUseReserved
pbackus Jan 30, 2022
aac05d6
Refactor to eliminate unchecked cast
pbackus Jan 30, 2022
d8b7eb8
DIP 1038: make "reserved" error more concise
pbackus Jan 31, 2022
f6d5143
DIP 1038: add changelog entry
pbackus Jan 31, 2022
38b2ef9
src/must_use.d: fix copyright date
pbackus Feb 7, 2022
e488fcc
Rename dmd.must_use to dmd.mustuse
pbackus Feb 7, 2022
2201eac
Use DDoc convention for "Returns" comments
pbackus Feb 8, 2022
5149f89
Change 'scope' StringExps to 'static const'
pbackus Feb 8, 2022
911fee6
Mark startsWith and endsWith 'pure @safe'
pbackus Feb 8, 2022
b8045ec
Don't use string comparison in isAssignment
pbackus Feb 8, 2022
af9b85a
DIP 1038: make @mustuse lower case
pbackus Feb 8, 2022
8aa6b6e
DIP 1038: do less expensive checks first
pbackus Feb 24, 2022
c6281b0
DIP 1038: eliminate redundant null checks
pbackus Mar 6, 2022
80cd6cd
Remove extra newlines at end-of-file
pbackus Apr 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions changelog/mustUse.dd
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Implement DIP 1038: @mustuse

`@mustuse` is a new attribute that can be applied to a `struct` or `union` type
to make ignoring an expression of that type into a compile-time error. It can
be used to implement alternative error-handling mechanisms for code that cannot
use exceptions, including `@nogc` and BetterC code.

For more information, see
$(LINK https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1038.md, DIP 1038).
2 changes: 1 addition & 1 deletion src/build.d
Original file line number Diff line number Diff line change
Expand Up @@ -1530,7 +1530,7 @@ auto sourceFiles()
dinterpret.d dmacro.d dmangle.d dmodule.d doc.d dscope.d dstruct.d dsymbol.d dsymbolsem.d
dtemplate.d dtoh.d dversion.d escape.d expression.d expressionsem.d func.d hdrgen.d impcnvtab.d
imphint.d importc.d init.d initsem.d inline.d inlinecost.d intrange.d json.d lambdacomp.d
mtype.d nogc.d nspace.d ob.d objc.d opover.d optimize.d
mtype.d mustuse.d nogc.d nspace.d ob.d objc.d opover.d optimize.d
parse.d parsetimevisitor.d permissivevisitor.d printast.d safe.d sapply.d
semantic2.d semantic3.d sideeffect.d statement.d statement_rewrite_walker.d
statementsem.d staticassert.d staticcond.d stmtstate.d target.d templateparamsem.d traits.d
Expand Down
2 changes: 2 additions & 0 deletions src/dmd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ Note that these groups have no strict meaning, the category assignments are a bi
| [impcnvtab.d](https://github.com/dlang/dmd/blob/master/src/dmd/impcnvtab.d) | Define an implicit conversion table for basic types |
| [importc.d](https://github.com/dlang/dmd/blob/master/src/dmd/importc.d) | Helpers specific to ImportC |
| [sideeffect.d](https://github.com/dlang/dmd/blob/master/src/dmd/sideeffect.d) | Extract side-effects of expressions for certain lowerings. |
| [mustuse.d](https://github.com/dlang/dmd/blob/master/src/dmd/mustuse.d) | Helpers related to the `@mustuse` attribute |


**Compile Time Function Execution (CTFE)**

Expand Down
5 changes: 5 additions & 0 deletions src/dmd/dsymbolsem.d
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import dmd.init;
import dmd.initsem;
import dmd.hdrgen;
import dmd.mtype;
import dmd.mustuse;
import dmd.nogc;
import dmd.nspace;
import dmd.objc;
Expand Down Expand Up @@ -2046,6 +2047,7 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor

ed.semanticRun = PASS.semantic;
UserAttributeDeclaration.checkGNUABITag(ed, sc.linkage);
checkMustUseReserved(ed);

if (!ed.members && !ed.memtype) // enum ident;
{
Expand Down Expand Up @@ -3056,6 +3058,7 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor
funcdecl.visibility = sc.visibility;
funcdecl.userAttribDecl = sc.userAttribDecl;
UserAttributeDeclaration.checkGNUABITag(funcdecl, funcdecl.linkage);
checkMustUseReserved(funcdecl);

if (!funcdecl.originalType)
funcdecl.originalType = funcdecl.type.syntaxCopy();
Expand Down Expand Up @@ -4770,6 +4773,7 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor
}
cldec.semanticRun = PASS.semantic;
UserAttributeDeclaration.checkGNUABITag(cldec, sc.linkage);
checkMustUseReserved(cldec);

if (cldec.baseok < Baseok.done)
{
Expand Down Expand Up @@ -5475,6 +5479,7 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor
idec.classKind = ClassKind.cpp;
idec.cppnamespace = sc.namespace;
UserAttributeDeclaration.checkGNUABITag(idec, sc.linkage);
checkMustUseReserved(idec);

if (sc.linkage == LINK.objc)
objc.setObjc(idec);
Expand Down
4 changes: 4 additions & 0 deletions src/dmd/expressionsem.d
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import dmd.initsem;
import dmd.inline;
import dmd.intrange;
import dmd.mtype;
import dmd.mustuse;
import dmd.nspace;
import dmd.opover;
import dmd.optimize;
Expand Down Expand Up @@ -8177,7 +8178,10 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
return;

if (e.type is Type.tvoid)
{
checkMustUse(e.e1, sc);
pbackus marked this conversation as resolved.
Show resolved Hide resolved
discardValue(e.e1);
}
else if (!e.allowCommaExp && !e.isGenerated)
e.error("Using the result of a comma expression is not allowed");
}
Expand Down
1 change: 1 addition & 0 deletions src/dmd/frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -8398,6 +8398,7 @@ struct Id final
static Identifier* udaGNUAbiTag;
static Identifier* udaSelector;
static Identifier* udaOptional;
static Identifier* udaMustUse;
static Identifier* TRUE;
static Identifier* FALSE;
static Identifier* dllimport;
Expand Down
1 change: 1 addition & 0 deletions src/dmd/id.d
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@ immutable Msgtable[] msgtable =
{ "udaGNUAbiTag", "gnuAbiTag" },
{ "udaSelector", "selector" },
{ "udaOptional", "optional"},
{ "udaMustUse", "mustuse" },

// C names, for undefined identifier error messages
{ "NULL" },
Expand Down
247 changes: 247 additions & 0 deletions src/dmd/mustuse.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
/**
* 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.errors : error;
import dmd.id : Id;

if (sym.userAttribDecl is null || sym.userAttribDecl.atts is null)
return;

// Can't use foreachUda (and by extension hasMustUseAttribute) while
// semantic analysis of `sym` is still in progress
// TODO: factor out common code from this function and checkGNUABITag
foreach (exp; *sym.userAttribDecl.atts)
{
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;
}
}
}
}

/**
* 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;
}
4 changes: 3 additions & 1 deletion src/dmd/statementsem.d
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import dmd.importc;
import dmd.init;
import dmd.intrange;
import dmd.mtype;
import dmd.mustuse;
import dmd.nogc;
import dmd.opover;
import dmd.parse;
Expand Down Expand Up @@ -211,7 +212,8 @@ package (dmd) extern (C++) final class StatementSemanticVisitor : Visitor
if (f.checkForwardRef(s.exp.loc))
s.exp = ErrorExp.get();
}

if (checkMustUse(s.exp, sc))
s.exp = ErrorExp.get();
if (!(sc.flags & SCOPE.Cfile) && discardValue(s.exp))
s.exp = ErrorExp.get();

Expand Down
9 changes: 9 additions & 0 deletions test/compilable/must_use_assign.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import core.attribute;

@mustuse struct S {}

void test()
{
S a, b;
a = b;
}
5 changes: 5 additions & 0 deletions test/compilable/must_use_not_reserved.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import core.attribute;

@mustuse int n;
@mustuse alias A = int;
@mustuse template tpl() {}
Loading