From 067e5d6004b3d3fb06808f0f1ba6d2b331bb1806 Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Fri, 23 Jun 2017 19:53:31 +0200 Subject: [PATCH 1/8] Implement new contract syntax. --- src/dmd/parse.d | 83 +++++++++++++++++++++++++++++------ test/runnable/testcontracts.d | 42 ++++++++++++++++++ 2 files changed, 111 insertions(+), 14 deletions(-) diff --git a/src/dmd/parse.d b/src/dmd/parse.d index f5c23444dc49..b95ba974818d 100644 --- a/src/dmd/parse.d +++ b/src/dmd/parse.d @@ -4666,11 +4666,12 @@ final class Parser(AST) : Lexer // The following is irrelevant, as it is overridden by sc.linkage in // TypeFunction::semantic linkage = LINK.d; // nested functions have D linkage + bool requireDo = false; L1: switch (token.value) { case TOK.leftCurly: - if (f.frequire || f.fensure) + if (requireDo) error("missing `do { ... }` after `in` or `out`"); f.fbody = parseStatement(ParseStatementFlags.semi); f.endloc = endloc; @@ -4720,27 +4721,81 @@ final class Parser(AST) : Lexer } case TOK.in_: + // in { statements... } + // in (expression) + auto loc = token.loc; nextToken(); if (f.frequire) - error("redundant `in` statement"); - f.frequire = parseStatement(ParseStatementFlags.curly | ParseStatementFlags.scope_); + error("redundant `in` contract"); + if (token.value == TOK.leftParentheses) + { + nextToken(); + AST.Expression e = parseAssignExp(), msg = null; + if (token.value == TOK.comma) + { + nextToken(); + if (token.value != TOK.rightParentheses) + { + msg = parseAssignExp(); + if (token.value == TOK.comma) + nextToken(); + } + } + check(TOK.rightParentheses); + e = new AST.AssertExp(loc, e, msg); + f.frequire = new AST.ExpStatement(loc, e); + requireDo = false; + } + else + { + f.frequire = parseStatement(ParseStatementFlags.curly | ParseStatementFlags.scope_); + requireDo = true; + } goto L1; case TOK.out_: - // parse: out (identifier) { statement } + // out { statements... } + // out (; expression) + // out (identifier) { statements... } + // out (identifier; expression) + auto loc = token.loc; nextToken(); + if (f.fensure) + error("redundant `out` contract"); if (token.value != TOK.leftCurly) { check(TOK.leftParentheses); - if (token.value != TOK.identifier) - error("`(identifier)` following `out` expected, not `%s`", token.toChars()); - f.outId = token.ident; - nextToken(); + if (token.value != TOK.identifier && token.value != TOK.semicolon) + error("`(identifier) { ... }` or `(identifier; expression)` following `out` expected, not `%s`", token.toChars()); + if (token.value != TOK.semicolon) + { + f.outId = token.ident; + nextToken(); + } + if (token.value == TOK.semicolon) + { + nextToken(); + AST.Expression e = parseAssignExp(), msg = null; + if (token.value == TOK.comma) + { + nextToken(); + if (token.value != TOK.rightParentheses) + { + msg = parseAssignExp(); + if (token.value == TOK.comma) + nextToken(); + } + } + check(TOK.rightParentheses); + e = new AST.AssertExp(loc, e, msg); + f.fensure = new AST.ExpStatement(loc, e); + requireDo = false; + goto L1; + } check(TOK.rightParentheses); } - if (f.fensure) - error("redundant `out` statement"); f.fensure = parseStatement(ParseStatementFlags.curly | ParseStatementFlags.scope_); + requireDo = true; goto L1; case TOK.semicolon: @@ -4748,8 +4803,8 @@ final class Parser(AST) : Lexer { // https://issues.dlang.org/show_bug.cgi?id=15799 // Semicolon becomes a part of function declaration - // only when neither of contracts exists. - if (!f.frequire && !f.fensure) + // only when 'do' is not required + if (!requireDo) nextToken(); break; } @@ -4758,10 +4813,10 @@ final class Parser(AST) : Lexer default: if (literal) { - const(char)* sbody = (f.frequire || f.fensure) ? "do " : ""; + const(char)* sbody = requireDo ? "do " : ""; error("missing `%s{ ... }` for function literal", sbody); } - else if (!f.frequire && !f.fensure) // allow these even with no body + else if (!requireDo) // allow contracts even with no body { TOK t = token.value; if (t == TOK.const_ || t == TOK.immutable_ || t == TOK.inout_ || t == TOK.return_ || diff --git a/test/runnable/testcontracts.d b/test/runnable/testcontracts.d index 744c9b9ac787..0db5e0186e55 100644 --- a/test/runnable/testcontracts.d +++ b/test/runnable/testcontracts.d @@ -1080,6 +1080,44 @@ void test14779() /*******************************************/ +//******************************************/ +// DIP 1009 + +int dip1009_1(int x) +in (x>0, "x must be positive!") +out (r; r<0, "r must be negative!") +{ + return -x; +} + +int dip1009_2(int x) +in (x>0) +out (r; r<0) +{ + return -x; +} + +int dip1009_3(int x) +in (x>0,) +out (r; r<0,) +do +{ + return -x; +} + +void dip1009_4(int x) +in (x>0) +out (; x>1) +{ + x += 1; +} + +void dip1009_5(int x) +in (x>0) +out (; x>1); + +/*******************************************/ + int main() { test1(); @@ -1102,6 +1140,10 @@ int main() test15524(); test15524a(); test14779(); + dip1009_1(1); + dip1009_2(1); + dip1009_3(1); + dip1009_4(1); printf("Success\n"); return 0; From b0e47826f2847765eec3ad2594c2715f3fa4faba Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Sat, 24 Jun 2017 11:53:03 +0200 Subject: [PATCH 2/8] Support for multiple contracts. --- src/dmd/arraytypes.d | 1 + src/dmd/arraytypes.h | 2 + src/dmd/astbase.d | 12 ++- src/dmd/declaration.h | 14 ++- src/dmd/dsymbolsem.d | 2 +- src/dmd/func.d | 140 +++++++++++++++++++++++++----- src/dmd/hdrgen.d | 58 ++++++++++--- src/dmd/parse.d | 23 +++-- src/dmd/semantic3.d | 23 +++-- src/dmd/statement.d | 25 ++++-- src/dmd/transitivevisitor.d | 25 ++++-- test/fail_compilation/fail17502.d | 8 +- test/runnable/testcontracts.d | 49 ++++++++--- 13 files changed, 292 insertions(+), 90 deletions(-) diff --git a/src/dmd/arraytypes.d b/src/dmd/arraytypes.d index 3cd800c9b25d..e86f3fc0e9a7 100644 --- a/src/dmd/arraytypes.d +++ b/src/dmd/arraytypes.d @@ -51,3 +51,4 @@ alias GotoCaseStatements = Array!(GotoCaseStatement); alias ReturnStatements = Array!(ReturnStatement); alias GotoStatements = Array!(GotoStatement); alias TemplateInstances = Array!(TemplateInstance); +alias Ensures = Array!(Ensure); diff --git a/src/dmd/arraytypes.h b/src/dmd/arraytypes.h index 73450d8728ae..7f39692de9bc 100644 --- a/src/dmd/arraytypes.h +++ b/src/dmd/arraytypes.h @@ -67,4 +67,6 @@ typedef Array GotoStatements; typedef Array TemplateInstances; +typedef Array Ensures; + #endif diff --git a/src/dmd/astbase.d b/src/dmd/astbase.d index c8001d010901..76e4614a7ac0 100644 --- a/src/dmd/astbase.d +++ b/src/dmd/astbase.d @@ -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 { @@ -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; diff --git a/src/dmd/declaration.h b/src/dmd/declaration.h index f9d6512288fa..c8ba91d285b7 100644 --- a/src/dmd/declaration.h +++ b/src/dmd/declaration.h @@ -25,6 +25,11 @@ class LabelDsymbol; class Initializer; class Module; class ForeachStatement; +struct Ensure +{ + Identifier *id; + Statement *ensure; +}; class FuncDeclaration; class ExpInitializer; class StructDeclaration; @@ -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 @@ -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 diff --git a/src/dmd/dsymbolsem.d b/src/dmd/dsymbolsem.d index 619b07a20663..1a24a7f7c9aa 100644 --- a/src/dmd/dsymbolsem.d +++ b/src/dmd/dsymbolsem.d @@ -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(); diff --git a/src/dmd/func.d b/src/dmd/func.d index cb31525494a7..86a740a91a78 100644 --- a/src/dmd/func.d +++ b/src/dmd/func.d @@ -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 @@ -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 @@ -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; @@ -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. */ @@ -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; @@ -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(); @@ -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) @@ -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) @@ -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) { ... } @@ -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); @@ -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); @@ -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); diff --git a/src/dmd/hdrgen.d b/src/dmd/hdrgen.d index 208545ee061f..c967042dc5bb 100644 --- a/src/dmd/hdrgen.d +++ b/src/dmd/hdrgen.d @@ -1910,26 +1910,58 @@ public: hgs.autoMember = 0; buf.writenl(); // in{} - if (f.frequire) + if (f.frequires) { - buf.writestring("in"); - buf.writenl(); - f.frequire.accept(this); + foreach (frequire; *f.frequires) + { + buf.writestring("in"); + buf.writenl(); + if (!frequire.isScopeStatement()) + { + buf.writeByte('{'); + buf.writenl(); + buf.level++; + frequire.accept(this); + buf.level--; + buf.writeByte('}'); + buf.writenl(); + } + else + { + frequire.accept(this); + } + } } // out{} - if (f.fensure) + if (f.fensures) { - buf.writestring("out"); - if (f.outId) + foreach (fensure; *f.fensures) { - buf.writeByte('('); - buf.writestring(f.outId.toChars()); - buf.writeByte(')'); + buf.writestring("out"); + if (fensure.id) + { + buf.writeByte('('); + buf.writestring(fensure.id.toChars()); + buf.writeByte(')'); + } + buf.writenl(); + if (!fensure.ensure.isScopeStatement()) + { + buf.writeByte('{'); + buf.writenl(); + buf.level++; + fensure.ensure.accept(this); + buf.level--; + buf.writeByte('}'); + buf.writenl(); + } + else + { + fensure.ensure.accept(this); + } } - buf.writenl(); - f.fensure.accept(this); } - if (f.frequire || f.fensure) + if (f.frequires || f.fensures) { buf.writestring("do"); buf.writenl(); diff --git a/src/dmd/parse.d b/src/dmd/parse.d index b95ba974818d..0ddb740159a1 100644 --- a/src/dmd/parse.d +++ b/src/dmd/parse.d @@ -4725,8 +4725,10 @@ final class Parser(AST) : Lexer // in (expression) auto loc = token.loc; nextToken(); - if (f.frequire) - error("redundant `in` contract"); + if (!f.frequires) + { + f.frequires = new AST.Statements; + } if (token.value == TOK.leftParentheses) { nextToken(); @@ -4743,12 +4745,12 @@ final class Parser(AST) : Lexer } check(TOK.rightParentheses); e = new AST.AssertExp(loc, e, msg); - f.frequire = new AST.ExpStatement(loc, e); + f.frequires.push(new AST.ExpStatement(loc, e)); requireDo = false; } else { - f.frequire = parseStatement(ParseStatementFlags.curly | ParseStatementFlags.scope_); + f.frequires.push(parseStatement(ParseStatementFlags.curly | ParseStatementFlags.scope_)); requireDo = true; } goto L1; @@ -4760,8 +4762,11 @@ final class Parser(AST) : Lexer // out (identifier; expression) auto loc = token.loc; nextToken(); - if (f.fensure) - error("redundant `out` contract"); + if (!f.fensures) + { + f.fensures = new AST.Ensures; + } + Identifier id = null; if (token.value != TOK.leftCurly) { check(TOK.leftParentheses); @@ -4769,7 +4774,7 @@ final class Parser(AST) : Lexer error("`(identifier) { ... }` or `(identifier; expression)` following `out` expected, not `%s`", token.toChars()); if (token.value != TOK.semicolon) { - f.outId = token.ident; + id = token.ident; nextToken(); } if (token.value == TOK.semicolon) @@ -4788,13 +4793,13 @@ final class Parser(AST) : Lexer } check(TOK.rightParentheses); e = new AST.AssertExp(loc, e, msg); - f.fensure = new AST.ExpStatement(loc, e); + f.fensures.push(AST.Ensure(id, new AST.ExpStatement(loc, e))); requireDo = false; goto L1; } check(TOK.rightParentheses); } - f.fensure = parseStatement(ParseStatementFlags.curly | ParseStatementFlags.scope_); + f.fensures.push(AST.Ensure(id, parseStatement(ParseStatementFlags.curly | ParseStatementFlags.scope_))); requireDo = true; goto L1; diff --git a/src/dmd/semantic3.d b/src/dmd/semantic3.d index be34c9d3368c..6d4a21c27804 100644 --- a/src/dmd/semantic3.d +++ b/src/dmd/semantic3.d @@ -281,12 +281,12 @@ private extern(C++) final class Semantic3Visitor : Visitor uint oldErrors = global.errors; - if (funcdecl.frequire) + if (funcdecl.frequires) { for (size_t i = 0; i < funcdecl.foverrides.dim; i++) { FuncDeclaration fdv = funcdecl.foverrides[i]; - if (fdv.fbody && !fdv.frequire) + if (fdv.fbody && !fdv.frequires) { funcdecl.error("cannot have an in contract when overridden function `%s` does not have an in contract", fdv.toPrettyChars()); break; @@ -297,7 +297,7 @@ private extern(C++) final class Semantic3Visitor : Visitor // Remember whether we need to generate an 'out' contract. immutable bool needEnsure = FuncDeclaration.needsFensure(funcdecl); - if (funcdecl.fbody || funcdecl.frequire || needEnsure) + if (funcdecl.fbody || funcdecl.frequires || needEnsure) { /* Symbol table into which we place parameters and nested functions, * solely to diagnose name collisions. @@ -884,7 +884,7 @@ private extern(C++) final class Semantic3Visitor : Visitor } funcdecl.frequire = funcdecl.mergeFrequire(funcdecl.frequire); - funcdecl.fensure = funcdecl.mergeFensure(funcdecl.fensure, funcdecl.outId); + funcdecl.fensure = funcdecl.mergeFensure(funcdecl.fensure, Id.result); Statement freq = funcdecl.frequire; Statement fens = funcdecl.fensure; @@ -920,8 +920,17 @@ private extern(C++) final class Semantic3Visitor : Visitor { /* fensure is composed of the [out] contracts */ - if (f.next.ty == Tvoid && funcdecl.outId) - funcdecl.error("`void` functions have no result"); + if (f.next.ty == Tvoid && funcdecl.fensures) + { + foreach (e; *funcdecl.fensures) + { + if (e.id) + { + funcdecl.error(e.ensure.loc, "`void` functions have no result"); + //fens = null; + } + } + } sc2 = scout; //push sc2.flags = (sc2.flags & ~SCOPE.contract) | SCOPE.ensure; @@ -1118,7 +1127,7 @@ private extern(C++) final class Semantic3Visitor : Visitor } } - if (funcdecl.naked && (funcdecl.fensure || funcdecl.frequire)) + if (funcdecl.naked && (funcdecl.fensures || funcdecl.frequires)) funcdecl.error("naked assembly functions with contracts are not supported"); sc2.ctorflow.callSuper = CSX.none; diff --git a/src/dmd/statement.d b/src/dmd/statement.d index d881a4daf4a5..d3660e90eccd 100644 --- a/src/dmd/statement.d +++ b/src/dmd/statement.d @@ -85,6 +85,23 @@ extern (C++) abstract class Statement : RootObject assert(0); } + /************************************* + * Do syntax copy of an array of Statement's. + */ + static Statements* arraySyntaxCopy(Statements* a) + { + Statements* b = null; + if (a) + { + b = a.copy(); + foreach (i, s; *a) + { + (*b)[i] = s ? s.syntaxCopy() : null; + } + } + return b; + } + override final void print() { fprintf(stderr, "%s\n", toChars()); @@ -853,13 +870,7 @@ extern (C++) class CompoundStatement : Statement override Statement syntaxCopy() { - auto a = new Statements(); - a.setDim(statements.dim); - foreach (i, s; *statements) - { - (*a)[i] = s ? s.syntaxCopy() : null; - } - return new CompoundStatement(loc, a); + return new CompoundStatement(loc, Statement.arraySyntaxCopy(statements)); } override Statements* flatten(Scope* sc) diff --git a/src/dmd/transitivevisitor.d b/src/dmd/transitivevisitor.d index c335d1303040..00238f45e609 100644 --- a/src/dmd/transitivevisitor.d +++ b/src/dmd/transitivevisitor.d @@ -573,13 +573,24 @@ package mixin template ParseVisitMethods(AST) void visitFuncBody(AST.FuncDeclaration f) { //printf("Visiting funcBody\n"); - if (!f.fbody) - return; - if (f.frequire) - f.frequire.accept(this); - if (f.fensure) - f.fensure.accept(this); - f.fbody.accept(this); + if (f.frequires) + { + foreach (frequire; *f.frequires) + { + frequire.accept(this); + } + } + if (f.fensures) + { + foreach (fensure; *f.fensures) + { + fensure.ensure.accept(this); + } + } + if (f.fbody) + { + f.fbody.accept(this); + } } void visitBaseClasses(AST.ClassDeclaration d) diff --git a/test/fail_compilation/fail17502.d b/test/fail_compilation/fail17502.d index 5a68e2052ecc..b1366d136b9c 100644 --- a/test/fail_compilation/fail17502.d +++ b/test/fail_compilation/fail17502.d @@ -1,10 +1,10 @@ /* TEST_OUTPUT: --- -fail_compilation/fail17502.d(12): Error: function `fail17502.Foo.foo` `void` functions have no result -fail_compilation/fail17502.d(13): Error: cannot have parameter of type `const(void)` -fail_compilation/fail17502.d(16): Error: function `fail17502.Foo.bar` `void` functions have no result -fail_compilation/fail17502.d(17): Error: cannot have parameter of type `const(void)` +fail_compilation/fail17502.d(13): Error: function `fail17502.Foo.foo` `void` functions have no result +fail_compilation/fail17502.d(13): Error: undefined identifier `res` +fail_compilation/fail17502.d(17): Error: function `fail17502.Foo.bar` `void` functions have no result +fail_compilation/fail17502.d(17): Error: undefined identifier `res` --- */ class Foo diff --git a/test/runnable/testcontracts.d b/test/runnable/testcontracts.d index 0db5e0186e55..b5ca1e64fee8 100644 --- a/test/runnable/testcontracts.d +++ b/test/runnable/testcontracts.d @@ -1084,37 +1084,62 @@ void test14779() // DIP 1009 int dip1009_1(int x) -in (x>0, "x must be positive!") -out (r; r<0, "r must be negative!") + in (x > 0, "x must be positive!") + out (r; r < 0, "r must be negative!") { return -x; } int dip1009_2(int x) -in (x>0) -out (r; r<0) + in (x > 0) + out (r; r < 0) { return -x; } int dip1009_3(int x) -in (x>0,) -out (r; r<0,) +in (x > 0,) +out (r; r < 0,) do { return -x; } void dip1009_4(int x) -in (x>0) -out (; x>1) + in (x > 0) + out (; x > 1) { x += 1; } -void dip1009_5(int x) -in (x>0) -out (; x>1); +interface DIP1009_5 +{ + void dip1009_5(int x) + in (x > 0) + out (; x > 1); +} + +int dip1009_6(int x, int y) + in (x > 0) + out (r; r > 1) + out (; x > 0) + in (y > 0) + in (x + y > 1) + out (r; r > 1) +{ + return x+y; +} + +int dip1009_7(int x) + in (x>0) + in { assert(x>1); } + out { assert(x>2); } + out (; x>3) + out (r; r>3) +{ + x+=2; + return x; +} /*******************************************/ @@ -1144,6 +1169,8 @@ int main() dip1009_2(1); dip1009_3(1); dip1009_4(1); + dip1009_6(1, 1); + dip1009_7(3); printf("Success\n"); return 0; From 0c001d8f14450404aed2ca2f810e204858162cc4 Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Mon, 26 Jun 2017 22:56:44 +0200 Subject: [PATCH 3/8] Implement new syntax for invariants. --- src/dmd/parse.d | 38 +++++++++++++++++++++++++++++------ test/runnable/testcontracts.d | 23 +++++++++++++++------ 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/dmd/parse.d b/src/dmd/parse.d index 0ddb740159a1..b78e521395b8 100644 --- a/src/dmd/parse.d +++ b/src/dmd/parse.d @@ -533,10 +533,11 @@ final class Parser(AST) : Lexer case TOK.invariant_: { Token* t = peek(&token); - if (t.value == TOK.leftParentheses && peek(t).value == TOK.rightParentheses || t.value == TOK.leftCurly) + if (t.value == TOK.leftParentheses || t.value == TOK.leftCurly) { - // invariant {} - // invariant() {} + // invariant { statements... } + // invariant() { statements... } + // invariant (expression); s = parseInvariant(pAttrs); } else @@ -2616,7 +2617,9 @@ final class Parser(AST) : Lexer /***************************************** * Parse an invariant definition: - * invariant() { body } + * invariant { statements... } + * invariant() { statements... } + * invariant (expression); * Current token is 'invariant'. */ AST.Dsymbol parseInvariant(PrefixAttributes!AST* pAttrs) @@ -2625,10 +2628,33 @@ final class Parser(AST) : Lexer StorageClass stc = getStorageClass!AST(pAttrs); nextToken(); - if (token.value == TOK.leftParentheses) // optional () + if (token.value == TOK.leftParentheses) // optional () or invariant (expression); { nextToken(); - check(TOK.rightParentheses); + if (token.value != TOK.rightParentheses) // invariant (expression); + { + AST.Expression e = parseAssignExp(), msg = null; + if (token.value == TOK.comma) + { + nextToken(); + if (token.value != TOK.rightParentheses) + { + msg = parseAssignExp(); + if (token.value == TOK.comma) + nextToken(); + } + } + check(TOK.rightParentheses); + check(TOK.semicolon); + e = new AST.AssertExp(loc, e, msg); + auto fbody = new AST.ExpStatement(loc, e); + auto f = new AST.InvariantDeclaration(loc, token.loc, stc, null, fbody); + return f; + } + else + { + nextToken(); + } } auto fbody = parseStatement(ParseStatementFlags.curly); diff --git a/test/runnable/testcontracts.d b/test/runnable/testcontracts.d index b5ca1e64fee8..e6c40ab22b0c 100644 --- a/test/runnable/testcontracts.d +++ b/test/runnable/testcontracts.d @@ -1131,16 +1131,26 @@ int dip1009_6(int x, int y) } int dip1009_7(int x) - in (x>0) - in { assert(x>1); } - out { assert(x>2); } - out (; x>3) - out (r; r>3) + in (x > 0) + in { assert(x > 1); } + out { assert(x > 2); } + out (; x > 3) + out (r; r > 3) { - x+=2; + x += 2; return x; } +class DIP1009_8 +{ + private int x = 4; + invariant (x > 0, "x must stay positive"); + invariant (x > 1, "x must be greater than one",); + invariant (x > 2); + invariant (x > 3,); + void foo(){ x = 5; } +} + /*******************************************/ int main() @@ -1171,6 +1181,7 @@ int main() dip1009_4(1); dip1009_6(1, 1); dip1009_7(3); + new DIP1009_8().foo(); printf("Success\n"); return 0; From 144630ef2a5db57f3cd2a06990b08eb6ff1627c1 Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Mon, 9 Apr 2018 18:40:30 +0200 Subject: [PATCH 4/8] Use new contract syntax for header generation. --- src/dmd/hdrgen.d | 74 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/src/dmd/hdrgen.d b/src/dmd/hdrgen.d index c967042dc5bb..c2ab52edd6a2 100644 --- a/src/dmd/hdrgen.d +++ b/src/dmd/hdrgen.d @@ -1909,15 +1909,28 @@ public: hgs.tpltMember = 0; hgs.autoMember = 0; buf.writenl(); + bool requireDo = false; // in{} if (f.frequires) { foreach (frequire; *f.frequires) { buf.writestring("in"); - buf.writenl(); if (!frequire.isScopeStatement()) { + if (auto es = frequire.isExpStatement()) + { + if (es.exp.op == TOK.assert_) + { + buf.writestring(" ("); + (cast(AssertExp)es.exp).e1.accept(this); + buf.writeByte(')'); + buf.writenl(); + requireDo = false; + continue; + } + } + buf.writenl(); buf.writeByte('{'); buf.writenl(); buf.level++; @@ -1925,10 +1938,13 @@ public: buf.level--; buf.writeByte('}'); buf.writenl(); + requireDo = true; } else { + buf.writenl(); frequire.accept(this); + requireDo = true; } } } @@ -1938,15 +1954,32 @@ public: foreach (fensure; *f.fensures) { buf.writestring("out"); - if (fensure.id) - { - buf.writeByte('('); - buf.writestring(fensure.id.toChars()); - buf.writeByte(')'); - } - buf.writenl(); if (!fensure.ensure.isScopeStatement()) { + if (auto es = fensure.ensure.isExpStatement()) + { + if (es.exp.op == TOK.assert_) + { + buf.writestring(" ("); + if (fensure.id) + { + buf.writestring(fensure.id.toChars()); + } + buf.writestring("; "); + (cast(AssertExp)es.exp).e1.accept(this); + buf.writeByte(')'); + buf.writenl(); + requireDo = false; + continue; + } + } + if (fensure.id) + { + buf.writeByte('('); + buf.writestring(fensure.id.toChars()); + buf.writeByte(')'); + } + buf.writenl(); buf.writeByte('{'); buf.writenl(); buf.level++; @@ -1954,14 +1987,23 @@ public: buf.level--; buf.writeByte('}'); buf.writenl(); + requireDo = true; } else { + if (fensure.id) + { + buf.writeByte('('); + buf.writestring(fensure.id.toChars()); + buf.writeByte(')'); + } + buf.writenl(); fensure.ensure.accept(this); + requireDo = true; } } } - if (f.frequires || f.fensures) + if (requireDo) { buf.writestring("do"); buf.writenl(); @@ -2078,6 +2120,20 @@ public: if (stcToBuffer(buf, d.storage_class)) buf.writeByte(' '); buf.writestring("invariant"); + if (!d.fbody.isScopeStatement()) + { + if (auto es = d.fbody.isExpStatement()) + { + if (es.exp.op == TOK.assert_) + { + buf.writestring(" ("); + (cast(AssertExp)es.exp).e1.accept(this); + buf.writestring(");"); + buf.writenl(); + return; + } + } + } bodyToBuffer(d); } From 59b669d303e51ec5a5620ef641ef77a1f302e74f Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Mon, 9 Apr 2018 21:12:47 +0200 Subject: [PATCH 5/8] Workaround for bug in older DMD frontends. --- src/dmd/astcodegen.d | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dmd/astcodegen.d b/src/dmd/astcodegen.d index 1f07e993c665..89b4e4cac379 100644 --- a/src/dmd/astcodegen.d +++ b/src/dmd/astcodegen.d @@ -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; From 85cf2468aaddafcd4c1ab9388b15e95b092f6ac6 Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Tue, 17 Apr 2018 16:56:59 +0200 Subject: [PATCH 6/8] Add changelog entry. --- changelog/expression-based_contract_syntax.dd | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 changelog/expression-based_contract_syntax.dd diff --git a/changelog/expression-based_contract_syntax.dd b/changelog/expression-based_contract_syntax.dd new file mode 100644 index 000000000000..c9434bba0b8c --- /dev/null +++ b/changelog/expression-based_contract_syntax.dd @@ -0,0 +1,22 @@ +Implement DIP 1009 - $(LINK2 https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1009.md, 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) +{ + // ... +} +--- \ No newline at end of file From 1a9de07a9d2df06a3cc6458763f1e23921ffe550 Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Wed, 2 May 2018 11:37:41 +0200 Subject: [PATCH 7/8] Full code coverage. --- src/dmd/hdrgen.d | 85 +++++++------------------ test/compilable/extra-files/header1.d | 71 ++++++++++++++------- test/compilable/extra-files/header1.di | 20 +++++- test/compilable/extra-files/header1i.di | 20 +++++- test/compilable/vcg-ast.d | 11 ++++ test/fail_compilation/fail_contracts1.d | 8 +++ test/fail_compilation/fail_contracts2.d | 8 +++ test/fail_compilation/fail_contracts3.d | 14 ++++ test/fail_compilation/fail_contracts4.d | 8 +++ test/runnable/testcontracts.d | 2 + 10 files changed, 159 insertions(+), 88 deletions(-) create mode 100644 test/fail_compilation/fail_contracts1.d create mode 100644 test/fail_compilation/fail_contracts2.d create mode 100644 test/fail_compilation/fail_contracts3.d create mode 100644 test/fail_compilation/fail_contracts4.d diff --git a/src/dmd/hdrgen.d b/src/dmd/hdrgen.d index c2ab52edd6a2..37538108e59d 100644 --- a/src/dmd/hdrgen.d +++ b/src/dmd/hdrgen.d @@ -1916,29 +1916,14 @@ public: foreach (frequire; *f.frequires) { buf.writestring("in"); - if (!frequire.isScopeStatement()) + if (auto es = frequire.isExpStatement()) { - if (auto es = frequire.isExpStatement()) - { - if (es.exp.op == TOK.assert_) - { - buf.writestring(" ("); - (cast(AssertExp)es.exp).e1.accept(this); - buf.writeByte(')'); - buf.writenl(); - requireDo = false; - continue; - } - } - buf.writenl(); - buf.writeByte('{'); - buf.writenl(); - buf.level++; - frequire.accept(this); - buf.level--; - buf.writeByte('}'); + assert(es.exp && es.exp.op == TOK.assert_); + buf.writestring(" ("); + (cast(AssertExp)es.exp).e1.accept(this); + buf.writeByte(')'); buf.writenl(); - requireDo = true; + requireDo = false; } else { @@ -1954,40 +1939,19 @@ public: foreach (fensure; *f.fensures) { buf.writestring("out"); - if (!fensure.ensure.isScopeStatement()) + if (auto es = fensure.ensure.isExpStatement()) { - if (auto es = fensure.ensure.isExpStatement()) - { - if (es.exp.op == TOK.assert_) - { - buf.writestring(" ("); - if (fensure.id) - { - buf.writestring(fensure.id.toChars()); - } - buf.writestring("; "); - (cast(AssertExp)es.exp).e1.accept(this); - buf.writeByte(')'); - buf.writenl(); - requireDo = false; - continue; - } - } + assert(es.exp && es.exp.op == TOK.assert_); + buf.writestring(" ("); if (fensure.id) { - buf.writeByte('('); buf.writestring(fensure.id.toChars()); - buf.writeByte(')'); } + buf.writestring("; "); + (cast(AssertExp)es.exp).e1.accept(this); + buf.writeByte(')'); buf.writenl(); - buf.writeByte('{'); - buf.writenl(); - buf.level++; - fensure.ensure.accept(this); - buf.level--; - buf.writeByte('}'); - buf.writenl(); - requireDo = true; + requireDo = false; } else { @@ -2120,21 +2084,18 @@ public: if (stcToBuffer(buf, d.storage_class)) buf.writeByte(' '); buf.writestring("invariant"); - if (!d.fbody.isScopeStatement()) + if(auto es = d.fbody.isExpStatement()) { - if (auto es = d.fbody.isExpStatement()) - { - if (es.exp.op == TOK.assert_) - { - buf.writestring(" ("); - (cast(AssertExp)es.exp).e1.accept(this); - buf.writestring(");"); - buf.writenl(); - return; - } - } + assert(es.exp && es.exp.op == TOK.assert_); + buf.writestring(" ("); + (cast(AssertExp)es.exp).e1.accept(this); + buf.writestring(");"); + buf.writenl(); + } + else + { + bodyToBuffer(d); } - bodyToBuffer(d); } override void visit(UnitTestDeclaration d) diff --git a/test/compilable/extra-files/header1.d b/test/compilable/extra-files/header1.d index 9a5ab3fd25ee..b0a73ff2c368 100644 --- a/test/compilable/extra-files/header1.d +++ b/test/compilable/extra-files/header1.d @@ -10,7 +10,29 @@ static assert(true, "message"); alias double mydbl; -alias fl = function () in {} body {}; +alias fl1 = function () + in {} + in (true) + out (; true) + out (r; true) + out + { + } + out (r) + { + } + do + { + return 2; + }; + +alias fl2 = function () + in (true) + out(; true) + out(r; true) + { + return 2; + }; int testmain() in @@ -60,7 +82,7 @@ template Foo(T, int V) d--; asm - { naked ; + { naked ; mov EAX, 3; } @@ -135,11 +157,11 @@ template Foo(T, int V) } try - bar(1, 2); + bar(1, 2); catch(Object o) - x++; + x++; finally - x--; + x--; Object o; synchronized (o) @@ -246,6 +268,7 @@ class Test pure nothrow @safe @nogc unittest {} pure nothrow @safe @nogc invariant {} + pure nothrow @safe @nogc invariant (true); pure nothrow @safe @nogc new (size_t sz) { return null; } pure nothrow @safe @nogc delete (void* p) { } @@ -525,36 +548,36 @@ struct Foo3A(T) // return ref, return scope, return ref scope ref int foo(return ref int a) @safe { - return a; + return a; } int* foo(return scope int* a) @safe { - return a; + return a; } ref int* foo(scope return ref int* a) @safe { - return a; + return a; } struct SafeS { @safe: - ref SafeS foo() return - { - return this; - } - - SafeS foo2() return scope - { - return this; - } - - ref SafeS foo3() return scope - { - return this; - } - - int* p; + ref SafeS foo() return + { + return this; + } + + SafeS foo2() return scope + { + return this; + } + + ref SafeS foo3() return scope + { + return this; + } + + int* p; } diff --git a/test/compilable/extra-files/header1.di b/test/compilable/extra-files/header1.di index 96afd5cad763..9cd522b46dcd 100644 --- a/test/compilable/extra-files/header1.di +++ b/test/compilable/extra-files/header1.di @@ -5,12 +5,30 @@ pragma (lib, "test"); pragma (msg, "Hello World"); static assert(true, "message"); alias mydbl = double; -alias fl = function () +alias fl1 = function () in { } +in (true) +out (; true) +out (r; true) +out +{ +} +out(r) +{ +} do { + return 2; +} +; +alias fl2 = function () +in (true) +out (; true) +out (r; true) +{ + return 2; } ; int testmain(); diff --git a/test/compilable/extra-files/header1i.di b/test/compilable/extra-files/header1i.di index 0edda6888b3b..0302e74b941a 100644 --- a/test/compilable/extra-files/header1i.di +++ b/test/compilable/extra-files/header1i.di @@ -5,12 +5,30 @@ pragma (lib, "test"); pragma (msg, "Hello World"); static assert(true, "message"); alias mydbl = double; -alias fl = function () +alias fl1 = function () in { } +in (true) +out (; true) +out (r; true) +out +{ +} +out(r) +{ +} do { + return 2; +} +; +alias fl2 = function () +in (true) +out (; true) +out (r; true) +{ + return 2; } ; int testmain() diff --git a/test/compilable/vcg-ast.d b/test/compilable/vcg-ast.d index 49c7d957decd..d68a212cb9e5 100644 --- a/test/compilable/vcg-ast.d +++ b/test/compilable/vcg-ast.d @@ -31,3 +31,14 @@ void foo() mixin("int a" ~ i.stringof ~ " = 1;"); } } + +class C +{ + invariant {} + invariant (true); + + int foo() in{} out{} out(r){} in(true) out(; true) out(r; true) + { + return 2; + } +} diff --git a/test/fail_compilation/fail_contracts1.d b/test/fail_compilation/fail_contracts1.d new file mode 100644 index 000000000000..572b821e9824 --- /dev/null +++ b/test/fail_compilation/fail_contracts1.d @@ -0,0 +1,8 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/fail_contracts1.d(8): Error: `(identifier) { ... }` or `(identifier; expression)` following `out` expected, not `)` +--- +*/ + +void foo() out()){} diff --git a/test/fail_compilation/fail_contracts2.d b/test/fail_compilation/fail_contracts2.d new file mode 100644 index 000000000000..2a07a60e5f3a --- /dev/null +++ b/test/fail_compilation/fail_contracts2.d @@ -0,0 +1,8 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/fail_contracts2.d(8): Error: missing `do { ... }` after `in` or `out` +--- +*/ + +void foo()in{}{} diff --git a/test/fail_compilation/fail_contracts3.d b/test/fail_compilation/fail_contracts3.d new file mode 100644 index 000000000000..b0b366b3b7b6 --- /dev/null +++ b/test/fail_compilation/fail_contracts3.d @@ -0,0 +1,14 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/fail_contracts3.d(13): Error: function `fail_contracts3.D.foo` cannot have an in contract when overridden function `fail_contracts3.C.foo` does not have an in contract +--- +*/ + +class C { + void foo(){} +} + +class D : C { + override void foo()in{}do{} +} diff --git a/test/fail_compilation/fail_contracts4.d b/test/fail_compilation/fail_contracts4.d new file mode 100644 index 000000000000..f1b664477cc5 --- /dev/null +++ b/test/fail_compilation/fail_contracts4.d @@ -0,0 +1,8 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/fail_contracts4.d(8): Error: missing `do { ... }` for function literal +--- +*/ + +enum x = delegate int()in(true) out(;true) out(r; true) in{} out(r){}; diff --git a/test/runnable/testcontracts.d b/test/runnable/testcontracts.d index e6c40ab22b0c..f99a10afff24 100644 --- a/test/runnable/testcontracts.d +++ b/test/runnable/testcontracts.d @@ -1086,6 +1086,8 @@ void test14779() int dip1009_1(int x) in (x > 0, "x must be positive!") out (r; r < 0, "r must be negative!") + in (true, "cover trailing comma case",) + out (; true, "cover trailing comma case",) { return -x; } From 23ee1c77d4f6addd2d8b43beb5046723b98a0566 Mon Sep 17 00:00:00 2001 From: Andrei Alexandrescu Date: Sat, 5 May 2018 17:45:06 +0200 Subject: [PATCH 8/8] Fix link --- changelog/expression-based_contract_syntax.dd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog/expression-based_contract_syntax.dd b/changelog/expression-based_contract_syntax.dd index c9434bba0b8c..34905a635a1f 100644 --- a/changelog/expression-based_contract_syntax.dd +++ b/changelog/expression-based_contract_syntax.dd @@ -1,4 +1,4 @@ -Implement DIP 1009 - $(LINK2 https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1009.md, Add Expression-Based Contract Syntax) +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. @@ -19,4 +19,4 @@ int fun(ref int a, int b) { // ... } ---- \ No newline at end of file +---