Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 22 additions & 0 deletions changelog/expression-based_contract_syntax.dd
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Implement DIP 1009 - Add Expression-Based Contract Syntax

Expression-based contract syntax has been added: Contracts that consist of a single assertion can now be written more succinctly and multiple `in` or `out` contracts can be specified for the same function.

Example:

---
class C {
private int x;
invariant(x >= 0);
// ...
}

int fun(ref int a, int b)
in(a > 0)
in(b >= 0, "b cannot be negative")
out(r; r > 0, "return must be positive")
out(; a != 0)
{
// ...
}
---
1 change: 1 addition & 0 deletions src/dmd/arraytypes.d
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@ alias GotoCaseStatements = Array!(GotoCaseStatement);
alias ReturnStatements = Array!(ReturnStatement);
alias GotoStatements = Array!(GotoStatement);
alias TemplateInstances = Array!(TemplateInstance);
alias Ensures = Array!(Ensure);
2 changes: 2 additions & 0 deletions src/dmd/arraytypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,6 @@ typedef Array<class GotoStatement *> GotoStatements;

typedef Array<class TemplateInstance *> TemplateInstances;

typedef Array<struct Ensure> Ensures;

#endif
12 changes: 9 additions & 3 deletions src/dmd/astbase.d
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ struct ASTBase
alias Catches = Array!(Catch);
alias Identifiers = Array!(Identifier);
alias Initializers = Array!(Initializer);
alias Ensures = Array!(Ensure);

enum Sizeok : int
{
Expand Down Expand Up @@ -674,13 +675,18 @@ struct ASTBase
}
}

extern (C++) struct Ensure
{
Identifier id;
Statement ensure;
}

extern (C++) class FuncDeclaration : Declaration
{
Statement fbody;
Statement frequire;
Statement fensure;
Statements* frequires;
Ensures* fensures;
Loc endloc;
Identifier outId;
StorageClass storage_class;
Type type;
bool inferRetType;
Expand Down
1 change: 1 addition & 0 deletions src/dmd/astcodegen.d
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ struct ASTCodegen
alias initializerToExpression = dmd.initsem.initializerToExpression;
alias typeToExpression = dmd.typesem.typeToExpression;
alias UserAttributeDeclaration = dmd.attrib.UserAttributeDeclaration;
alias Ensure = dmd.func.Ensure; // workaround for bug in older DMD frontends

alias MODFlags = dmd.mtype.MODFlags;
alias Type = dmd.mtype.Type;
Expand Down
14 changes: 10 additions & 4 deletions src/dmd/declaration.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ class LabelDsymbol;
class Initializer;
class Module;
class ForeachStatement;
struct Ensure
{
Identifier *id;
Statement *ensure;
};
class FuncDeclaration;
class ExpInitializer;
class StructDeclaration;
Expand Down Expand Up @@ -487,8 +492,10 @@ class FuncDeclaration : public Declaration
{
public:
Types *fthrows; // Array of Type's of exceptions (not used)
Statement *frequire;
Statement *fensure;
Statements *frequires; // in contracts
Ensures *fensures; // out contracts
Statement *frequire; // lowered in contract
Statement *fensure; // lowered out contract
Statement *fbody;

FuncDeclarations foverrides; // functions this function overrides
Expand All @@ -497,8 +504,7 @@ class FuncDeclaration : public Declaration

const char *mangleString; // mangled symbol created from mangleExact()

Identifier *outId; // identifier for out statement
VarDeclaration *vresult; // variable corresponding to outId
VarDeclaration *vresult; // result variable for out contracts
LabelDsymbol *returnLabel; // where the return goes

// used to prevent symbols in different
Expand Down
2 changes: 1 addition & 1 deletion src/dmd/dsymbolsem.d
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ package bool allowsContractWithoutBody(FuncDeclaration funcdecl)
InterfaceDeclaration id = parent.isInterfaceDeclaration();

if (!funcdecl.isAbstract() &&
(funcdecl.fensure || funcdecl.frequire) &&
(funcdecl.fensures || funcdecl.frequires) &&
!(id && funcdecl.isVirtual()))
{
auto cd = parent.isClassDeclaration();
Expand Down
140 changes: 116 additions & 24 deletions src/dmd/func.d
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,48 @@ enum FUNCFLAG : uint
compileTimeOnly = 0x100, /// is a compile time only function; no code will be generated for it
}

/***********************************************************
* Tuple of result identifier (possibly null) and statement.
* This is used to store out contracts: out(id){ ensure }
*/
extern (C++) struct Ensure
{
Identifier id;
Statement ensure;

Ensure syntaxCopy()
{
return Ensure(id, ensure.syntaxCopy());
}

/*****************************************
* Do syntax copy of an array of Ensure's.
*/
static Ensures* arraySyntaxCopy(Ensures* a)
{
Ensures* b = null;
if (a)
{
b = a.copy();
foreach (i, e; *a)
{
(*b)[i] = e.syntaxCopy();
}
}
return b;
}

}

/***********************************************************
*/
extern (C++) class FuncDeclaration : Declaration
{
Types* fthrows; /// Array of Type's of exceptions (not used)
Statement frequire; /// in contract body
Statement fensure; /// out contract body
Statements* frequires; /// in contracts
Ensures* fensures; /// out contracts
Statement frequire; /// lowered in contract
Statement fensure; /// lowered out contract
Statement fbody; /// function body

FuncDeclarations foverrides; /// functions this function overrides
Expand All @@ -167,8 +202,7 @@ extern (C++) class FuncDeclaration : Declaration

const(char)* mangleString; /// mangled symbol created from mangleExact()

Identifier outId; /// identifier for out statement
VarDeclaration vresult; /// variable corresponding to outId
VarDeclaration vresult; /// result variable for out contracts
LabelDsymbol returnLabel; /// where the return goes

// used to prevent symbols in different
Expand Down Expand Up @@ -275,9 +309,8 @@ extern (C++) class FuncDeclaration : Declaration
{
//printf("FuncDeclaration::syntaxCopy('%s')\n", toChars());
FuncDeclaration f = s ? cast(FuncDeclaration)s : new FuncDeclaration(loc, endloc, ident, storage_class, type.syntaxCopy());
f.outId = outId;
f.frequire = frequire ? frequire.syntaxCopy() : null;
f.fensure = fensure ? fensure.syntaxCopy() : null;
f.frequires = frequires ? Statement.arraySyntaxCopy(frequires) : null;
f.fensures = fensures ? Ensure.arraySyntaxCopy(fensures) : null;
f.fbody = fbody ? fbody.syntaxCopy() : null;
assert(!fthrows); // deprecated
return f;
Expand Down Expand Up @@ -1891,6 +1924,18 @@ extern (C++) class FuncDeclaration : Declaration
return false;
}

/****************************************************
* Check whether result variable can be built.
* Returns:
* `true` if the function has a return type that
* is different from `void`.
*/
extern (D) private bool canBuildResultVar()
{
auto f = cast(TypeFunction)type;
return f && f.nextOf() && f.nextOf().toBasetype().ty != Tvoid;
}

/****************************************************
* Declare result variable lazily.
*/
Expand All @@ -1904,11 +1949,8 @@ extern (C++) class FuncDeclaration : Declaration
* So, in here it may be a temporary type for vresult, and after
* fbody.dsymbolSemantic() running, vresult.type might be modified.
*/
vresult = new VarDeclaration(loc, tret, outId ? outId : Id.result, null);
vresult.storage_class |= STC.nodtor;

if (outId == Id.result)
vresult.storage_class |= STC.temp;
vresult = new VarDeclaration(loc, tret, Id.result, null);
vresult.storage_class |= STC.nodtor | STC.temp;
if (!isVirtual())
vresult.storage_class |= STC.const_;
vresult.storage_class |= STC.result;
Expand Down Expand Up @@ -1976,7 +2018,7 @@ extern (C++) class FuncDeclaration : Declaration
* be completed before code generation occurs.
* https://issues.dlang.org/show_bug.cgi?id=3602
*/
if (fdv.frequire && fdv.semanticRun != PASS.semantic3done)
if (fdv.frequires && fdv.semanticRun != PASS.semantic3done)
{
assert(fdv._scope);
Scope* sc = fdv._scope.push();
Expand Down Expand Up @@ -2019,7 +2061,7 @@ extern (C++) class FuncDeclaration : Declaration
*/
static bool needsFensure(FuncDeclaration fd)
{
if (fd.fensure)
if (fd.fensures)
return true;

foreach (fdv; fd.foverrides)
Expand All @@ -2031,14 +2073,67 @@ extern (C++) class FuncDeclaration : Declaration
}

/****************************************************
* Rewrite contracts as nested functions, then call them. Doing it as nested
* functions means that overriding functions can call them.
* Rewrite contracts as statements.
*/
final void buildEnsureRequire()
{

if (frequires)
{
/* in { statements1... }
* in { statements2... }
* ...
* becomes:
* in { { statements1... } { statements2... } ... }
*/
assert(frequires.dim);
auto loc = (*frequires)[0].loc;
auto s = new Statements;
foreach (r; *frequires)
{
s.push(new ScopeStatement(r.loc, r, r.loc));
}
frequire = new CompoundStatement(loc, s);
}

if (fensures)
{
/* out(id1) { statements1... }
* out(id2) { statements2... }
* ...
* becomes:
* out(__result) { { ref id1 = __result; { statements1... } }
* { ref id2 = __result; { statements2... } } ... }
*/
assert(fensures.dim);
auto loc = (*fensures)[0].ensure.loc;
auto s = new Statements;
foreach (r; *fensures)
{
if (r.id && canBuildResultVar())
{
auto rloc = r.ensure.loc;
auto resultId = new IdentifierExp(rloc, Id.result);
auto init = new ExpInitializer(rloc, resultId);
auto stc = STC.ref_ | STC.temp | STC.result;
auto decl = new VarDeclaration(rloc, null, r.id, init, stc);
auto sdecl = new ExpStatement(rloc, decl);
s.push(new ScopeStatement(rloc, new CompoundStatement(rloc, sdecl, r.ensure), rloc));
}
else
{
s.push(r.ensure);
}
}
fensure = new CompoundStatement(loc, s);
}

if (!isVirtual())
return;

/* Rewrite contracts as nested functions, then call them. Doing it as nested
* functions means that overriding functions can call them.
*/
TypeFunction f = cast(TypeFunction) type;

if (frequire)
Expand All @@ -2063,9 +2158,6 @@ extern (C++) class FuncDeclaration : Declaration
fdrequire = fd;
}

if (!outId && f.nextOf() && f.nextOf().toBasetype().ty != Tvoid)
outId = Id.result; // provide a default

if (fensure)
{
/* out (result) { ... }
Expand All @@ -2076,9 +2168,9 @@ extern (C++) class FuncDeclaration : Declaration
Loc loc = fensure.loc;
auto fparams = new Parameters();
Parameter p = null;
if (outId)
if (canBuildResultVar())
{
p = new Parameter(STC.ref_ | STC.const_, f.nextOf(), outId, null);
p = new Parameter(STC.ref_ | STC.const_, f.nextOf(), Id.result, null);
fparams.push(p);
}
auto tf = new TypeFunction(fparams, Type.tvoid, 0, LINK.d);
Expand All @@ -2090,8 +2182,8 @@ extern (C++) class FuncDeclaration : Declaration
fd.fbody = fensure;
Statement s1 = new ExpStatement(loc, fd);
Expression eresult = null;
if (outId)
eresult = new IdentifierExp(loc, outId);
if (canBuildResultVar())
eresult = new IdentifierExp(loc, Id.result);
Expression e = new CallExp(loc, new VarExp(loc, fd, false), eresult);
Statement s2 = new ExpStatement(loc, e);
fensure = new CompoundStatement(loc, s1, s2);
Expand Down Expand Up @@ -2136,7 +2228,7 @@ extern (C++) class FuncDeclaration : Declaration
//printf("fdv.fensure: %s\n", fdv.fensure.toChars());
// Make the call: __ensure(result)
Expression eresult = null;
if (outId)
if (canBuildResultVar())
{
eresult = new IdentifierExp(loc, oid);

Expand Down
Loading