d: Implement expression-based contract syntax
authorIain Buclaw <ibuclaw@gdcproject.org>
Thu, 7 Jan 2021 17:30:30 +0000 (18:30 +0100)
committerIain Buclaw <ibuclaw@gdcproject.org>
Sat, 9 Jan 2021 22:45:46 +0000 (23:45 +0100)
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.

Reviewed-on: https://github.com/dlang/dmd/pull/12106

gcc/d/ChangeLog:

* dmd/MERGE: Merge upstream dmd e598f69c0.

gcc/d/dmd/MERGE
gcc/d/dmd/arraytypes.h
gcc/d/dmd/declaration.h
gcc/d/dmd/func.c
gcc/d/dmd/hdrgen.c
gcc/d/dmd/parse.c
gcc/d/dmd/statement.c
gcc/d/dmd/statement.h
gcc/testsuite/gdc.test/compilable/extra-files/header1.d
gcc/testsuite/gdc.test/fail_compilation/fail17502.d
gcc/testsuite/gdc.test/runnable/testcontracts.d

index 25b2b3ac9651e52d59001cb43da4f57d2f706b06..f6c8f6f02cf2ac2e65c4fe368e8fa4d28ebaf2fa 100644 (file)
@@ -1,4 +1,4 @@
-9038e64c5b67a10763d32893f53bb6c610df3595
+e598f69c0726ad1bf6b2e15e0b60d7cead737fad
 
 The first line of this file holds the git revision number of the last
 merge done from the dlang/dmd repository.
index ea57214f734b8850bbfb01703caf13ee26deddca..627464a635f2e45985d0ae2b32108e04caff4595 100644 (file)
@@ -61,3 +61,5 @@ typedef Array<class ReturnStatement *> ReturnStatements;
 typedef Array<class GotoStatement *> GotoStatements;
 
 typedef Array<class TemplateInstance *> TemplateInstances;
+
+typedef Array<struct Ensure> Ensures;
index a4e776697fccb3a385c82e9a7eef4a0dc4df5636..19e4d1a5aa84e51d78fb4bab2da9fe42a934ae78 100644 (file)
@@ -20,6 +20,16 @@ class LabelDsymbol;
 class Initializer;
 class Module;
 class ForeachStatement;
+struct Ensure
+{
+    Identifier *id;
+    Statement *ensure;
+
+    Ensure();
+    Ensure(Identifier *id, Statement *ensure);
+    Ensure syntaxCopy();
+    static Ensures *arraySyntaxCopy(Ensures *a);
+};
 class FuncDeclaration;
 class ExpInitializer;
 class StructDeclaration;
@@ -516,8 +526,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
@@ -526,8 +538,7 @@ public:
 
     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
index 53621adcc41942cf9f14321c7f508cf878787574..f8f43dc09bba9ed2a25714b25da438fd15bff9cd 100644 (file)
@@ -292,6 +292,42 @@ public:
     }
 };
 
+/***********************************************************
+ * Tuple of result identifier (possibly null) and statement.
+ * This is used to store out contracts: out(id){ ensure }
+ */
+Ensure::Ensure()
+{
+    this->id = NULL;
+    this->ensure = NULL;
+}
+
+Ensure::Ensure(Identifier *id, Statement *ensure)
+{
+    this->id = id;
+    this->ensure = ensure;
+}
+
+Ensure Ensure::syntaxCopy()
+{
+    return Ensure(id, ensure->syntaxCopy());
+}
+
+/*****************************************
+ * Do syntax copy of an array of Ensure's.
+ */
+Ensures *Ensure::arraySyntaxCopy(Ensures *a)
+{
+    Ensures *b = NULL;
+    if (a)
+    {
+        b = a->copy();
+        for (size_t i = 0; i < a->length; i++)
+            (*b)[i] = (*a)[i].syntaxCopy();
+    }
+    return b;
+}
+
 /********************************* FuncDeclaration ****************************/
 
 FuncDeclaration::FuncDeclaration(Loc loc, Loc endloc, Identifier *id, StorageClass storage_class, Type *type)
@@ -314,10 +350,11 @@ FuncDeclaration::FuncDeclaration(Loc loc, Loc endloc, Identifier *id, StorageCla
     fdrequire = NULL;
     fdensure = NULL;
     mangleString = NULL;
-    outId = NULL;
     vresult = NULL;
     returnLabel = NULL;
     fensure = NULL;
+    frequires = NULL;
+    fensures = NULL;
     fbody = NULL;
     localsymtab = NULL;
     vthis = NULL;
@@ -372,10 +409,9 @@ Dsymbol *FuncDeclaration::syntaxCopy(Dsymbol *s)
     FuncDeclaration *f =
         s ? (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->fbody    = fbody    ? fbody->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;
 }
@@ -441,6 +477,28 @@ static void initInferAttributes(FuncDeclaration *fd)
         fd->flags |= FUNCFLAGinferScope;
 }
 
+// Returns true if a contract can appear without a function body.
+static bool allowsContractWithoutBody(FuncDeclaration *funcdecl)
+{
+    assert(!funcdecl->fbody);
+
+    /* Contracts can only appear without a body when they are virtual
+     * interface functions or abstract.
+     */
+    Dsymbol *parent = funcdecl->toParent();
+    InterfaceDeclaration *id = parent->isInterfaceDeclaration();
+
+    if (!funcdecl->isAbstract() &&
+        (funcdecl->fensures || funcdecl->frequires) &&
+        !(id && funcdecl->isVirtual()))
+    {
+        ClassDeclaration *cd = parent->isClassDeclaration();
+        if (!(cd && cd->isAbstract()))
+            return false;
+    }
+    return true;
+}
+
 // Do the semantic analysis on the external interface to the function.
 
 void FuncDeclaration::semantic(Scope *sc)
@@ -780,11 +838,6 @@ void FuncDeclaration::semantic(Scope *sc)
             error("destructors, postblits and invariants are not allowed in union %s", ud->toChars());
     }
 
-    /* Contracts can only appear without a body when they are virtual interface functions
-     */
-    if (!fbody && (fensure || frequire) && !(id && isVirtual()))
-        error("in and out contracts require function body");
-
     if (parent->isStructDeclaration())
     {
         if (isCtorDeclaration())
@@ -1157,6 +1210,12 @@ void FuncDeclaration::semantic(Scope *sc)
     // Reflect this->type to f because it could be changed by findVtblIndex
     f = type->toTypeFunction();
 
+Ldone:
+    /* Contracts can only appear without a body when they are virtual interface functions
+     */
+    if (!fbody && !allowsContractWithoutBody(this))
+        error("in and out contracts can only appear without a body when they are virtual interface functions or abstract");
+
     /* Do not allow template instances to add virtual functions
      * to a class.
      */
@@ -1186,7 +1245,6 @@ void FuncDeclaration::semantic(Scope *sc)
     if (isMain())
         checkDmain();       // Check main() parameters and return type
 
-Ldone:
     /* Purity and safety can be inferred for some functions by examining
      * the function body.
      */
@@ -1270,7 +1328,7 @@ void FuncDeclaration::semantic2(Scope *sc)
  */
 static bool needsFensure(FuncDeclaration *fd)
 {
-    if (fd->fensure)
+    if (fd->fensures)
         return true;
 
     for (size_t i = 0; i < fd->foverrides.length; i++)
@@ -1287,16 +1345,83 @@ static bool needsFensure(FuncDeclaration *fd)
 }
 
 /****************************************************
- * Rewrite contracts as nested functions, then call them. Doing it as nested
- * functions means that overriding functions can call them.
+ * Check whether result variable can be built.
+ * Returns:
+ *     `true` if the function has a return type that
+ *     is different from `void`.
+ */
+static bool canBuildResultVar(FuncDeclaration *fd)
+{
+    TypeFunction *f = (TypeFunction *)fd->type;
+    return f && f->nextOf() && f->nextOf()->toBasetype()->ty != Tvoid;
+}
+
+/****************************************************
+ * Rewrite contracts as statements.
  * Params:
- *      fd = the function to rewrite contracts for
+ *      fdx = the function to rewrite contracts for
  */
 static void buildEnsureRequire(FuncDeclaration *fdx)
 {
+    if (fdx->frequires)
+    {
+        /*   in { statements1... }
+         *   in { statements2... }
+         *   ...
+         * becomes:
+         *   in { { statements1... } { statements2... } ... }
+         */
+        assert(fdx->frequires->length);
+        Loc loc = (*fdx->frequires)[0]->loc;
+        Statements *s = new Statements;
+        for (size_t i = 0; i < fdx->frequires->length; i++)
+        {
+            Statement *r = (*fdx->frequires)[i];
+            s->push(new ScopeStatement(r->loc, r, r->loc));
+        }
+        fdx->frequire = new CompoundStatement(loc, s);
+    }
+
+    if (fdx->fensures)
+    {
+        /*   out(id1) { statements1... }
+         *   out(id2) { statements2... }
+         *   ...
+         * becomes:
+         *   out(__result) { { ref id1 = __result; { statements1... } }
+         *                   { ref id2 = __result; { statements2... } } ... }
+         */
+        assert(fdx->fensures->length);
+        Loc loc = (*fdx->fensures)[0].ensure->loc;
+        Statements *s = new Statements;
+        for (size_t i = 0; i < fdx->fensures->length; i++)
+        {
+            Ensure r = (*fdx->fensures)[i];
+            if (r.id && canBuildResultVar(fdx))
+            {
+                Loc rloc = r.ensure->loc;
+                IdentifierExp *resultId = new IdentifierExp(rloc, Id::result);
+                ExpInitializer *init = new ExpInitializer(rloc, resultId);
+                StorageClass stc = STCref | STCtemp | STCresult;
+                VarDeclaration *decl = new VarDeclaration(rloc, NULL, r.id, init);
+                decl->storage_class = stc;
+                ExpStatement *sdecl = new ExpStatement(rloc, decl);
+                s->push(new ScopeStatement(rloc, new CompoundStatement(rloc, sdecl, r.ensure), rloc));
+            }
+            else
+            {
+                s->push(r.ensure);
+            }
+        }
+        fdx->fensure = new CompoundStatement(loc, s);
+    }
+
     if (!fdx->isVirtual())
         return;
 
+    /* Rewrite contracts as nested functions, then call them. Doing it as nested
+     * functions means that overriding functions can call them.
+     */
     TypeFunction *f = (TypeFunction *)fdx->type;
 
     if (fdx->frequire)
@@ -1322,9 +1447,6 @@ static void buildEnsureRequire(FuncDeclaration *fdx)
         fdx->fdrequire = fd;
     }
 
-    if (!fdx->outId && f->nextOf() && f->nextOf()->toBasetype()->ty != Tvoid)
-        fdx->outId = Id::result; // provide a default
-
     if (fdx->fensure)
     {
         /*   out (result) { ... }
@@ -1335,9 +1457,9 @@ static void buildEnsureRequire(FuncDeclaration *fdx)
         Loc loc = fdx->fensure->loc;
         Parameters *fparams = new Parameters();
         Parameter *p = NULL;
-        if (fdx->outId)
+        if (canBuildResultVar(fdx))
         {
-            p = new Parameter(STCref | STCconst, f->nextOf(), fdx->outId, NULL, NULL);
+            p = new Parameter(STCref | STCconst, f->nextOf(), Id::result, NULL, NULL);
             fparams->push(p);
         }
         TypeFunction *tf = new TypeFunction(ParameterList(fparams), Type::tvoid, LINKd);
@@ -1350,8 +1472,8 @@ static void buildEnsureRequire(FuncDeclaration *fdx)
         fd->fbody = fdx->fensure;
         Statement *s1 = new ExpStatement(loc, fd);
         Expression *eresult = NULL;
-        if (fdx->outId)
-            eresult = new IdentifierExp(loc, fdx->outId);
+        if (canBuildResultVar(fdx))
+            eresult = new IdentifierExp(loc, Id::result);
         Expression *e = new CallExp(loc, new VarExp(loc, fd, false), eresult);
         Statement *s2 = new ExpStatement(loc, e);
         fdx->fensure = new CompoundStatement(loc, s1, s2);
@@ -1435,13 +1557,13 @@ void FuncDeclaration::semantic3(Scope *sc)
 
     unsigned oldErrors = global.errors;
 
-    if (frequire)
+    if (frequires)
     {
         for (size_t i = 0; i < foverrides.length; i++)
         {
             FuncDeclaration *fdv = foverrides[i];
 
-            if (fdv->fbody && !fdv->frequire)
+            if (fdv->fbody && !fdv->frequires)
             {
                 error("cannot have an in contract when overriden function %s does not have an in contract", fdv->toPrettyChars());
                 break;
@@ -1450,9 +1572,9 @@ void FuncDeclaration::semantic3(Scope *sc)
     }
 
     // Remember whether we need to generate an 'out' contract.
-    bool needEnsure = needsFensure(this);
+    const bool needEnsure = needsFensure(this);
 
-    if (fbody || frequire || needEnsure)
+    if (fbody || frequires || needEnsure)
     {
         /* Symbol table into which we place parameters and nested functions,
          * solely to diagnose name collisions.
@@ -2039,7 +2161,7 @@ void FuncDeclaration::semantic3(Scope *sc)
         }
 
         frequire = mergeFrequire(frequire);
-        fensure = mergeFensure(fensure, outId);
+        fensure = mergeFensure(fensure, Id::result);
 
         Statement *freq = frequire;
         Statement *fens = fensure;
@@ -2075,8 +2197,18 @@ void FuncDeclaration::semantic3(Scope *sc)
         {
             /* fensure is composed of the [out] contracts
              */
-            if (f->next->ty == Tvoid && outId)
-                error("void functions have no result");
+            if (f->next->ty == Tvoid && fensures)
+            {
+                for (size_t i = 0; i < fensures->length; i++)
+                {
+                    Ensure e = (*fensures)[i];
+                    if (e.id)
+                    {
+                        error(e.ensure->loc, "`void` functions have no result");
+                        //fens = NULL;
+                    }
+                }
+            }
 
             sc2 = scout;    //push
             sc2->flags = (sc2->flags & ~SCOPEcontract) | SCOPEensure;
@@ -2263,8 +2395,7 @@ void FuncDeclaration::semantic3(Scope *sc)
             }
 
             // If declaration has no body, don't set sbody to prevent incorrect codegen.
-            InterfaceDeclaration *id = parent->isInterfaceDeclaration();
-            if (fbody || (id && (fdensure || fdrequire) && isVirtual()))
+            if (fbody || allowsContractWithoutBody(this))
                 fbody = sbody;
         }
 
@@ -2277,7 +2408,7 @@ void FuncDeclaration::semantic3(Scope *sc)
             }
         }
 
-        if (naked && (fensure || frequire))
+        if (naked && (fensures || frequires))
             error("naked assembly functions with contracts are not supported");
 
         sc2->callSuper = 0;
@@ -2610,11 +2741,8 @@ void FuncDeclaration::buildResultVar(Scope *sc, Type *tret)
          * So, in here it may be a temporary type for vresult, and after
          * fbody->semantic() running, vresult->type might be modified.
          */
-        vresult = new VarDeclaration(loc, tret, outId ? outId : Id::result, NULL);
-        vresult->storage_class |= STCnodtor;
-
-        if (outId == Id::result)
-            vresult->storage_class |= STCtemp;
+        vresult = new VarDeclaration(loc, tret, Id::result, NULL);
+        vresult->storage_class |= STCnodtor | STCtemp;
         if (!isVirtual())
             vresult->storage_class |= STCconst;
         vresult->storage_class |= STCresult;
@@ -2685,7 +2813,7 @@ Statement *FuncDeclaration::mergeFrequire(Statement *sf)
          * be completed before code generation occurs.
          * https://issues.dlang.org/show_bug.cgi?id=3602
          */
-        if (fdv->frequire && fdv->semanticRun != PASSsemantic3done)
+        if (fdv->frequires && fdv->semanticRun != PASSsemantic3done)
         {
             assert(fdv->_scope);
             Scope *sc = fdv->_scope->push();
@@ -2758,7 +2886,7 @@ Statement *FuncDeclaration::mergeFensure(Statement *sf, Identifier *oid)
             //printf("fdv->fensure: %s\n", fdv->fensure->toChars());
             // Make the call: __ensure(result)
             Expression *eresult = NULL;
-            if (outId)
+            if (canBuildResultVar(this))
             {
                 eresult = new IdentifierExp(loc, oid);
 
index a04a8c523a3bb3b3477653e152eae2b378dbf39a..a351930ce457389b5b9bdada2c14cee94f7aa7f7 100644 (file)
@@ -1950,32 +1950,70 @@ public:
         int saveauto = hgs->autoMember;
         hgs->tpltMember = 0;
         hgs->autoMember = 0;
-
         buf->writenl();
-
+        bool requireDo = false;
         // in{}
-        if (f->frequire)
+        if (f->frequires)
         {
-            buf->writestring("in");
-            buf->writenl();
-            f->frequire->accept(this);
+            for (size_t i = 0; i < f->frequires->length; i++)
+            {
+                Statement *frequire = (*f->frequires)[i];
+                buf->writestring("in");
+                if (ExpStatement *es = frequire->isExpStatement())
+                {
+                    assert(es->exp && es->exp->op == TOKassert);
+                    buf->writestring(" (");
+                    ((AssertExp *)es->exp)->e1->accept(this);
+                    buf->writeByte(')');
+                    buf->writenl();
+                    requireDo = false;
+                }
+                else
+                {
+                    buf->writenl();
+                    frequire->accept(this);
+                    requireDo = true;
+                }
+            }
         }
 
         // out{}
-        if (f->fensure)
+        if (f->fensures)
         {
-            buf->writestring("out");
-            if (f->outId)
+            for (size_t i = 0; i < f->fensures->length; i++)
             {
-                buf->writeByte('(');
-                buf->writestring(f->outId->toChars());
-                buf->writeByte(')');
+                Ensure fensure = (*f->fensures)[i];
+                buf->writestring("out");
+                if (ExpStatement *es = fensure.ensure->isExpStatement())
+                {
+                    assert(es->exp && es->exp->op == TOKassert);
+                    buf->writestring(" (");
+                    if (fensure.id)
+                    {
+                        buf->writestring(fensure.id->toChars());
+                    }
+                    buf->writestring("; ");
+                    ((AssertExp *)es->exp)->e1->accept(this);
+                    buf->writeByte(')');
+                    buf->writenl();
+                    requireDo = false;
+                }
+                else
+                {
+                    if (fensure.id)
+                    {
+                        buf->writeByte('(');
+                        buf->writestring(fensure.id->toChars());
+                        buf->writeByte(')');
+                    }
+                    buf->writenl();
+                    fensure.ensure->accept(this);
+                    requireDo = true;
+                }
             }
-            buf->writenl();
-            f->fensure->accept(this);
         }
 
-        if (f->frequire || f->fensure)
+        if (requireDo)
         {
             buf->writestring("body");
             buf->writenl();
@@ -2093,7 +2131,18 @@ public:
         if (stcToBuffer(buf, d->storage_class))
             buf->writeByte(' ');
         buf->writestring("invariant");
-        bodyToBuffer(d);
+        if (ExpStatement *es = d->fbody->isExpStatement())
+        {
+            assert(es->exp && es->exp->op == TOKassert);
+            buf->writestring(" (");
+            ((AssertExp *)es->exp)->e1->accept(this);
+            buf->writestring(");");
+            buf->writenl();
+        }
+        else
+        {
+            bodyToBuffer(d);
+        }
     }
 
     void visit(UnitTestDeclaration *d)
index e414a4d69c0722696e329c7380a92aa5924909fe..80aaac089eccfd2c0b6163b21e64cf13708d4359 100644 (file)
@@ -372,11 +372,11 @@ Dsymbols *Parser::parseDeclDefs(int once, Dsymbol **pLastDecl, PrefixAttributes
             case TOKinvariant:
             {
                 Token *t = peek(&token);
-                if ((t->value == TOKlparen && peek(t)->value == TOKrparen) ||
-                    t->value == TOKlcurly)
+                if (t->value == TOKlparen || t->value == TOKlcurly)
                 {
-                    // invariant {}
-                    // invariant() {}
+                    // invariant { statements... }
+                    // invariant() { statements... }
+                    // invariant (expression);
                     s = parseInvariant(pAttrs);
                 }
                 else
@@ -1846,7 +1846,9 @@ Dsymbol *Parser::parseSharedStaticDtor(PrefixAttributes *pAttrs)
 
 /*****************************************
  * Parse an invariant definition:
- *      invariant() { body }
+ *      invariant { statements... }
+ *      invariant() { statements... }
+ *      invariant (expression);
  * Current token is 'invariant'.
  */
 
@@ -1856,10 +1858,35 @@ Dsymbol *Parser::parseInvariant(PrefixAttributes *pAttrs)
     StorageClass stc = pAttrs ? pAttrs->storageClass : STCundefined;
 
     nextToken();
-    if (token.value == TOKlparen)       // optional ()
+    if (token.value == TOKlparen) // optional () or invariant (expression);
     {
         nextToken();
-        check(TOKrparen);
+        if (token.value != TOKrparen) // invariant (expression);
+        {
+            Expression *e = parseAssignExp();
+            Expression *msg = NULL;
+            if (token.value == TOKcomma)
+            {
+                nextToken();
+                if (token.value != TOKrparen)
+                {
+                    msg = parseAssignExp();
+                    if (token.value == TOKcomma)
+                        nextToken();
+                }
+            }
+            check(TOKrparen);
+            check(TOKsemicolon);
+            e = new AssertExp(loc, e, msg);
+            ExpStatement *fbody = new ExpStatement(loc, e);
+            InvariantDeclaration *f = new InvariantDeclaration(loc, token.loc, stc);
+            f->fbody = fbody;
+            return f;
+        }
+        else
+        {
+            nextToken();
+        }
     }
 
     InvariantDeclaration *f = new InvariantDeclaration(loc, Loc(), stc);
@@ -4426,11 +4453,12 @@ FuncDeclaration *Parser::parseContracts(FuncDeclaration *f)
     // The following is irrelevant, as it is overridden by sc->linkage in
     // TypeFunction::semantic
     linkage = LINKd;            // nested functions have D linkage
+    bool requireDo = false;
 L1:
     switch (token.value)
     {
         case TOKlcurly:
-            if (f->frequire || f->fensure)
+            if (requireDo)
                 error("missing body { ... } after in or out");
             f->fbody = parseStatement(PSsemi);
             f->endloc = endloc;
@@ -4448,35 +4476,100 @@ L1:
             break;
 
         case TOKin:
+        {
+            // in { statements... }
+            // in (expression)
+            Loc loc = token.loc;
             nextToken();
-            if (f->frequire)
-                error("redundant 'in' statement");
-            f->frequire = parseStatement(PScurly | PSscope);
+            if (!f->frequires)
+            {
+                f->frequires = new Statements();
+            }
+            if (token.value == TOKlparen)
+            {
+                nextToken();
+                Expression *e = parseAssignExp();
+                Expression *msg = NULL;
+                if (token.value == TOKcomma)
+                {
+                    nextToken();
+                    if (token.value != TOKrparen)
+                    {
+                        msg = parseAssignExp();
+                        if (token.value == TOKcomma)
+                            nextToken();
+                    }
+                }
+                check(TOKrparen);
+                e = new AssertExp(loc, e, msg);
+                f->frequires->push(new ExpStatement(loc, e));
+                requireDo = false;
+            }
+            else
+            {
+                f->frequires->push(parseStatement(PScurly | PSscope));
+                requireDo = true;
+            }
             goto L1;
+        }
 
         case TOKout:
-            // parse: out (identifier) { statement }
+        {
+            // out { statements... }
+            // out (; expression)
+            // out (identifier) { statements... }
+            // out (identifier; expression)
+            Loc loc = token.loc;
             nextToken();
+            if (!f->fensures)
+            {
+                f->fensures = new Ensures();
+            }
+            Identifier *id = NULL;
             if (token.value != TOKlcurly)
             {
                 check(TOKlparen);
-                if (token.value != TOKidentifier)
-                    error("(identifier) following 'out' expected, not %s", token.toChars());
-                f->outId = token.ident;
-                nextToken();
+                if (token.value != TOKidentifier && token.value != TOKsemicolon)
+                    error("`(identifier) { ... }` or `(identifier; expression)` following `out` expected, not `%s`", token.toChars());
+                if (token.value != TOKsemicolon)
+                {
+                    id = token.ident;
+                    nextToken();
+                }
+                if (token.value == TOKsemicolon)
+                {
+                    nextToken();
+                    Expression *e = parseAssignExp();
+                    Expression *msg = NULL;
+                    if (token.value == TOKcomma)
+                    {
+                        nextToken();
+                        if (token.value != TOKrparen)
+                        {
+                            msg = parseAssignExp();
+                            if (token.value == TOKcomma)
+                                nextToken();
+                        }
+                    }
+                    check(TOKrparen);
+                    e = new AssertExp(loc, e, msg);
+                    f->fensures->push(Ensure(id, new ExpStatement(loc, e)));
+                    requireDo = false;
+                    goto L1;
+                }
                 check(TOKrparen);
             }
-            if (f->fensure)
-                error("redundant 'out' statement");
-            f->fensure = parseStatement(PScurly | PSscope);
+            f->fensures->push(Ensure(id, parseStatement(PScurly | PSscope)));
+            requireDo = true;
             goto L1;
+        }
 
         case TOKsemicolon:
             if (!literal)
             {
                 // Bugzilla 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;
             }
@@ -4486,10 +4579,10 @@ L1:
         Ldefault:
             if (literal)
             {
-                const char *sbody = (f->frequire || f->fensure) ? "body " : "";
+                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 these even with no body
             {
                 error("semicolon expected following function declaration");
             }
index 9d2e7e228692ef694ebfebae0d0f0848a614a1cb..76dfe1d3f06f33d910d4c30110e0d0ab0bc00e5a 100644 (file)
@@ -109,6 +109,24 @@ Statement *Statement::syntaxCopy()
     return NULL;
 }
 
+/*************************************
+ * Do syntax copy of an array of Statement's.
+ */
+Statements *Statement::arraySyntaxCopy(Statements *a)
+{
+    Statements *b = NULL;
+    if (a)
+    {
+        b = a->copy();
+        for (size_t i = 0; i < a->length; i++)
+        {
+            Statement *s = (*a)[i];
+            (*b)[i] = s ? s->syntaxCopy() : NULL;
+        }
+    }
+    return b;
+}
+
 void Statement::print()
 {
     fprintf(stderr, "%s\n", toChars());
@@ -560,14 +578,7 @@ CompoundStatement *CompoundStatement::create(Loc loc, Statement *s1, Statement *
 
 Statement *CompoundStatement::syntaxCopy()
 {
-    Statements *a = new Statements();
-    a->setDim(statements->length);
-    for (size_t i = 0; i < statements->length; i++)
-    {
-        Statement *s = (*statements)[i];
-        (*a)[i] = s ? s->syntaxCopy() : NULL;
-    }
-    return new CompoundStatement(loc, a);
+    return new CompoundStatement(loc, Statement::arraySyntaxCopy(statements));
 }
 
 Statements *CompoundStatement::flatten(Scope *)
index 4ceec87566c8d238945cd203b3256af56ac121bc..08eb5fd0fa5121416d6e31ec87b094cd44c567f6 100644 (file)
@@ -74,6 +74,7 @@ public:
 
     Statement(Loc loc);
     virtual Statement *syntaxCopy();
+    static Statements *arraySyntaxCopy(Statements *a);
 
     void print();
     const char *toChars();
index d8221eb882a430aee441a58abfb16127b30d2c3a..5eb6afd1787b29def995e85cee312fb43fe6ecf5 100644 (file)
@@ -10,6 +10,30 @@ static assert(true, "message");
 
 alias double mydbl;
 
+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
 {
@@ -133,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)
@@ -244,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) { }
index 8202b1e93806f4c6106ed7be0a2b3e400533f9c1..b1366d136b9c26a3bdb0c201941a62367ba733f0 100644 (file)
@@ -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
index 9a2639d1afb120c96316622db7349621b6d82413..f99a10afff24e5d2df42c1aa29b407e7cd54b270 100644 (file)
@@ -421,6 +421,56 @@ void test6417()
     (new Bug6417).bar();
 }
 
+/*******************************************/
+// 6549
+
+class C6549
+{
+    static int ocount = 0;
+    static int icount = 0;
+
+    abstract int foo(int)
+    in { ++icount; }
+    out { ++ocount; }
+}
+
+class CD6549 : C6549
+{
+    override int foo(int)
+    in { assert(false); }
+    do { return 10; }
+}
+
+abstract class D6549
+{
+    static int icount = 0;
+    static int ocount = 0;
+
+    int foo(int)
+    in { ++icount; }
+    out { ++ocount; }
+}
+
+class DD6549 : D6549
+{
+    override int foo(int)
+    in { assert(false); }
+    do { return 10; }
+}
+
+void test6549()
+{
+    auto c = new CD6549;
+    c.foo(10);
+    assert(C6549.icount == 1);
+    assert(C6549.ocount == 1);
+
+    auto d = new DD6549;
+    d.foo(10);
+    assert(D6549.icount == 1);
+    assert(D6549.ocount == 1);
+}
+
 /*******************************************/
 // 7218
 
@@ -1030,6 +1080,81 @@ 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 (true, "cover trailing comma case",)
+  out (; true, "cover trailing comma case",)
+{
+    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;
+}
+
+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;
+}
+
+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()
 {
     test1();
@@ -1043,6 +1168,7 @@ int main()
     test9();
     test4785();
     test6417();
+    test6549();
     test7218();
     test7517();
     test8073();
@@ -1051,6 +1177,13 @@ int main()
     test15524();
     test15524a();
     test14779();
+    dip1009_1(1);
+    dip1009_2(1);
+    dip1009_3(1);
+    dip1009_4(1);
+    dip1009_6(1, 1);
+    dip1009_7(3);
+    new DIP1009_8().foo();
 
     printf("Success\n");
     return 0;