| |
| /* Compiler implementation of the D programming language |
| * Copyright (C) 1999-2019 by The D Language Foundation, All Rights Reserved |
| * written by Walter Bright |
| * http://www.digitalmars.com |
| * Distributed under the Boost Software License, Version 1.0. |
| * http://www.boost.org/LICENSE_1_0.txt |
| * https://github.com/D-Programming-Language/dmd/blob/master/src/func.c |
| */ |
| |
| #include "root/dsystem.h" |
| |
| #include "mars.h" |
| #include "init.h" |
| #include "declaration.h" |
| #include "attrib.h" |
| #include "expression.h" |
| #include "scope.h" |
| #include "mtype.h" |
| #include "aggregate.h" |
| #include "identifier.h" |
| #include "id.h" |
| #include "module.h" |
| #include "statement.h" |
| #include "template.h" |
| #include "hdrgen.h" |
| #include "target.h" |
| #include "parse.h" |
| #include "root/rmem.h" |
| #include "visitor.h" |
| #include "objc.h" |
| |
| Expression *addInvariant(Loc loc, Scope *sc, AggregateDeclaration *ad, VarDeclaration *vthis, bool direct); |
| bool checkReturnEscape(Scope *sc, Expression *e, bool gag); |
| bool checkReturnEscapeRef(Scope *sc, Expression *e, bool gag); |
| bool checkNestedRef(Dsymbol *s, Dsymbol *p); |
| Statement *semantic(Statement *s, Scope *sc); |
| void semantic(Catch *c, Scope *sc); |
| Expression *resolve(Loc loc, Scope *sc, Dsymbol *s, bool hasOverloads); |
| Expression *semantic(Expression *e, Scope *sc); |
| int blockExit(Statement *s, FuncDeclaration *func, bool mustNotThrow); |
| TypeIdentifier *getThrowable(); |
| |
| RET retStyle(TypeFunction *tf); |
| void MODtoBuffer(OutBuffer *buf, MOD mod); |
| char *MODtoChars(MOD mod); |
| bool MODimplicitConv(MOD modfrom, MOD modto); |
| MATCH MODmethodConv(MOD modfrom, MOD modto); |
| void allocFieldinit(Scope *sc, size_t dim); |
| void freeFieldinit(Scope *sc); |
| Objc *objc(); |
| |
| |
| /* A visitor to walk entire statements and provides ability to replace any sub-statements. |
| */ |
| class StatementRewriteWalker : public Visitor |
| { |
| /* Point the currently visited statement. |
| * By using replaceCurrent() method, you can replace AST during walking. |
| */ |
| Statement **ps; |
| public: |
| void visitStmt(Statement *&s) { ps = &s; s->accept(this); } |
| void replaceCurrent(Statement *s) { *ps = s; } |
| |
| void visit(ErrorStatement *) { } |
| void visit(PeelStatement *s) |
| { |
| if (s->s) |
| visitStmt(s->s); |
| } |
| void visit(ExpStatement *) { } |
| void visit(DtorExpStatement *) { } |
| void visit(CompileStatement *) { } |
| void visit(CompoundStatement *s) |
| { |
| if (s->statements && s->statements->dim) |
| { |
| for (size_t i = 0; i < s->statements->dim; i++) |
| { |
| if ((*s->statements)[i]) |
| visitStmt((*s->statements)[i]); |
| } |
| } |
| } |
| void visit(CompoundDeclarationStatement *s) { visit((CompoundStatement *)s); } |
| void visit(UnrolledLoopStatement *s) |
| { |
| if (s->statements && s->statements->dim) |
| { |
| for (size_t i = 0; i < s->statements->dim; i++) |
| { |
| if ((*s->statements)[i]) |
| visitStmt((*s->statements)[i]); |
| } |
| } |
| } |
| void visit(ScopeStatement *s) |
| { |
| if (s->statement) |
| visitStmt(s->statement); |
| } |
| void visit(WhileStatement *s) |
| { |
| if (s->_body) |
| visitStmt(s->_body); |
| } |
| void visit(DoStatement *s) |
| { |
| if (s->_body) |
| visitStmt(s->_body); |
| } |
| void visit(ForStatement *s) |
| { |
| if (s->_init) |
| visitStmt(s->_init); |
| if (s->_body) |
| visitStmt(s->_body); |
| } |
| void visit(ForeachStatement *s) |
| { |
| if (s->_body) |
| visitStmt(s->_body); |
| } |
| void visit(ForeachRangeStatement *s) |
| { |
| if (s->_body) |
| visitStmt(s->_body); |
| } |
| void visit(IfStatement *s) |
| { |
| if (s->ifbody) |
| visitStmt(s->ifbody); |
| if (s->elsebody) |
| visitStmt(s->elsebody); |
| } |
| void visit(ConditionalStatement *) { } |
| void visit(PragmaStatement *) { } |
| void visit(StaticAssertStatement *) { } |
| void visit(SwitchStatement *s) |
| { |
| if (s->_body) |
| visitStmt(s->_body); |
| } |
| void visit(CaseStatement *s) |
| { |
| if (s->statement) |
| visitStmt(s->statement); |
| } |
| void visit(CaseRangeStatement *s) |
| { |
| if (s->statement) |
| visitStmt(s->statement); |
| } |
| void visit(DefaultStatement *s) |
| { |
| if (s->statement) |
| visitStmt(s->statement); |
| } |
| void visit(GotoDefaultStatement *) { } |
| void visit(GotoCaseStatement *) { } |
| void visit(SwitchErrorStatement *) { } |
| void visit(ReturnStatement *) { } |
| void visit(BreakStatement *) { } |
| void visit(ContinueStatement *) { } |
| void visit(SynchronizedStatement *s) |
| { |
| if (s->_body) |
| visitStmt(s->_body); |
| } |
| void visit(WithStatement *s) |
| { |
| if (s->_body) |
| visitStmt(s->_body); |
| } |
| void visit(TryCatchStatement *s) |
| { |
| if (s->_body) |
| visitStmt(s->_body); |
| if (s->catches && s->catches->dim) |
| { |
| for (size_t i = 0; i < s->catches->dim; i++) |
| { |
| Catch *c = (*s->catches)[i]; |
| if (c && c->handler) |
| visitStmt(c->handler); |
| } |
| } |
| } |
| void visit(TryFinallyStatement *s) |
| { |
| if (s->_body) |
| visitStmt(s->_body); |
| if (s->finalbody) |
| visitStmt(s->finalbody); |
| } |
| void visit(OnScopeStatement *) { } |
| void visit(ThrowStatement *) { } |
| void visit(DebugStatement *s) |
| { |
| if (s->statement) |
| visitStmt(s->statement); |
| } |
| void visit(GotoStatement *) { } |
| void visit(LabelStatement *s) |
| { |
| if (s->statement) |
| visitStmt(s->statement); |
| } |
| void visit(AsmStatement *) { } |
| void visit(ImportStatement *) { } |
| }; |
| |
| /* Tweak all return statements and dtor call for nrvo_var, for correct NRVO. |
| */ |
| class NrvoWalker : public StatementRewriteWalker |
| { |
| public: |
| FuncDeclaration *fd; |
| Scope *sc; |
| |
| void visit(ReturnStatement *s) |
| { |
| // See if all returns are instead to be replaced with a goto returnLabel; |
| if (fd->returnLabel) |
| { |
| /* Rewrite: |
| * return exp; |
| * as: |
| * vresult = exp; goto Lresult; |
| */ |
| GotoStatement *gs = new GotoStatement(s->loc, Id::returnLabel); |
| gs->label = fd->returnLabel; |
| |
| Statement *s1 = gs; |
| if (s->exp) |
| s1 = new CompoundStatement(s->loc, new ExpStatement(s->loc, s->exp), gs); |
| |
| replaceCurrent(s1); |
| } |
| } |
| void visit(TryFinallyStatement *s) |
| { |
| DtorExpStatement *des; |
| if (fd->nrvo_can && |
| s->finalbody && (des = s->finalbody->isDtorExpStatement()) != NULL && |
| fd->nrvo_var == des->var) |
| { |
| if (!(global.params.useExceptions && ClassDeclaration::throwable)) |
| { |
| /* Don't need to call destructor at all, since it is nrvo |
| */ |
| replaceCurrent(s->_body); |
| s->_body->accept(this); |
| return; |
| } |
| |
| /* Normally local variable dtors are called regardless exceptions. |
| * But for nrvo_var, its dtor should be called only when exception is thrown. |
| * |
| * Rewrite: |
| * try { s->body; } finally { nrvo_var->edtor; } |
| * // equivalent with: |
| * // s->body; scope(exit) nrvo_var->edtor; |
| * as: |
| * try { s->body; } catch(Throwable __o) { nrvo_var->edtor; throw __o; } |
| * // equivalent with: |
| * // s->body; scope(failure) nrvo_var->edtor; |
| */ |
| Statement *sexception = new DtorExpStatement(Loc(), fd->nrvo_var->edtor, fd->nrvo_var); |
| Identifier *id = Identifier::generateId("__o"); |
| |
| Statement *handler = new PeelStatement(sexception); |
| if (blockExit(sexception, fd, false) & BEfallthru) |
| { |
| ThrowStatement *ts = new ThrowStatement(Loc(), new IdentifierExp(Loc(), id)); |
| ts->internalThrow = true; |
| handler = new CompoundStatement(Loc(), handler, ts); |
| } |
| |
| Catches *catches = new Catches(); |
| Catch *ctch = new Catch(Loc(), getThrowable(), id, handler); |
| ctch->internalCatch = true; |
| ::semantic(ctch, sc); // Run semantic to resolve identifier '__o' |
| catches->push(ctch); |
| |
| Statement *s2 = new TryCatchStatement(Loc(), s->_body, catches); |
| replaceCurrent(s2); |
| s2->accept(this); |
| } |
| else |
| StatementRewriteWalker::visit(s); |
| } |
| }; |
| |
| /********************************* FuncDeclaration ****************************/ |
| |
| FuncDeclaration::FuncDeclaration(Loc loc, Loc endloc, Identifier *id, StorageClass storage_class, Type *type) |
| : Declaration(id) |
| { |
| //printf("FuncDeclaration(id = '%s', type = %p)\n", id->toChars(), type); |
| //printf("storage_class = x%x\n", storage_class); |
| this->storage_class = storage_class; |
| this->type = type; |
| if (type) |
| { |
| // Normalize storage_class, because function-type related attributes |
| // are already set in the 'type' in parsing phase. |
| this->storage_class &= ~(STC_TYPECTOR | STC_FUNCATTR); |
| } |
| this->loc = loc; |
| this->endloc = endloc; |
| fthrows = NULL; |
| frequire = NULL; |
| fdrequire = NULL; |
| fdensure = NULL; |
| mangleString = NULL; |
| outId = NULL; |
| vresult = NULL; |
| returnLabel = NULL; |
| fensure = NULL; |
| fbody = NULL; |
| localsymtab = NULL; |
| vthis = NULL; |
| v_arguments = NULL; |
| v_argptr = NULL; |
| parameters = NULL; |
| labtab = NULL; |
| overnext = NULL; |
| overnext0 = NULL; |
| vtblIndex = -1; |
| hasReturnExp = 0; |
| naked = false; |
| generated = false; |
| inlineStatusExp = ILSuninitialized; |
| inlineStatusStmt = ILSuninitialized; |
| inlining = PINLINEdefault; |
| inlineNest = 0; |
| ctfeCode = NULL; |
| isArrayOp = 0; |
| semantic3Errors = false; |
| fes = NULL; |
| interfaceVirtual = NULL; |
| introducing = 0; |
| tintro = NULL; |
| /* The type given for "infer the return type" is a TypeFunction with |
| * NULL for the return type. |
| */ |
| inferRetType = (type && type->nextOf() == NULL); |
| storage_class2 = 0; |
| hasReturnExp = 0; |
| nrvo_can = 1; |
| nrvo_var = NULL; |
| shidden = NULL; |
| builtin = BUILTINunknown; |
| tookAddressOf = 0; |
| requiresClosure = false; |
| inlinedNestedCallees = NULL; |
| flags = 0; |
| returns = NULL; |
| gotos = NULL; |
| selector = NULL; |
| } |
| |
| FuncDeclaration *FuncDeclaration::create(Loc loc, Loc endloc, Identifier *id, StorageClass storage_class, Type *type) |
| { |
| return new FuncDeclaration(loc, endloc, id, storage_class, type); |
| } |
| |
| Dsymbol *FuncDeclaration::syntaxCopy(Dsymbol *s) |
| { |
| //printf("FuncDeclaration::syntaxCopy('%s')\n", toChars()); |
| 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; |
| assert(!fthrows); // deprecated |
| return f; |
| } |
| |
| /********************************** |
| * Decide if attributes for this function can be inferred from examining |
| * the function body. |
| * Returns: |
| * true if can |
| */ |
| static bool canInferAttributes(FuncDeclaration *fd, Scope *sc) |
| { |
| if (!fd->fbody) |
| return false; |
| |
| if (fd->isVirtualMethod()) |
| return false; // since they may be overridden |
| |
| if (sc->func && |
| /********** this is for backwards compatibility for the moment ********/ |
| (!fd->isMember() || (sc->func->isSafeBypassingInference() && !fd->isInstantiated()))) |
| return true; |
| |
| if (fd->isFuncLiteralDeclaration() || // externs are not possible with literals |
| (fd->storage_class & STCinference) || // do attribute inference |
| (fd->inferRetType && !fd->isCtorDeclaration())) |
| return true; |
| |
| if (fd->isInstantiated()) |
| { |
| TemplateInstance *ti = fd->parent->isTemplateInstance(); |
| if (ti == NULL || ti->isTemplateMixin() || ti->tempdecl->ident == fd->ident) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /***************************************** |
| * Initialize for inferring the attributes of this function. |
| */ |
| static void initInferAttributes(FuncDeclaration *fd) |
| { |
| //printf("initInferAttributes() for %s\n", toPrettyChars()); |
| TypeFunction *tf = fd->type->toTypeFunction(); |
| if (tf->purity == PUREimpure) // purity not specified |
| fd->flags |= FUNCFLAGpurityInprocess; |
| |
| if (tf->trust == TRUSTdefault) |
| fd->flags |= FUNCFLAGsafetyInprocess; |
| |
| if (!tf->isnothrow) |
| fd->flags |= FUNCFLAGnothrowInprocess; |
| |
| if (!tf->isnogc) |
| fd->flags |= FUNCFLAGnogcInprocess; |
| |
| if (!fd->isVirtual() || fd->introducing) |
| fd->flags |= FUNCFLAGreturnInprocess; |
| |
| // Initialize for inferring STCscope |
| if (global.params.vsafe) |
| fd->flags |= FUNCFLAGinferScope; |
| } |
| |
| // Do the semantic analysis on the external interface to the function. |
| |
| void FuncDeclaration::semantic(Scope *sc) |
| { |
| TypeFunction *f; |
| AggregateDeclaration *ad; |
| InterfaceDeclaration *id; |
| |
| if (semanticRun != PASSinit && isFuncLiteralDeclaration()) |
| { |
| /* Member functions that have return types that are |
| * forward references can have semantic() run more than |
| * once on them. |
| * See test\interface2.d, test20 |
| */ |
| return; |
| } |
| |
| if (semanticRun >= PASSsemanticdone) |
| return; |
| assert(semanticRun <= PASSsemantic); |
| semanticRun = PASSsemantic; |
| |
| if (_scope) |
| { |
| sc = _scope; |
| _scope = NULL; |
| } |
| |
| parent = sc->parent; |
| Dsymbol *parent = toParent(); |
| |
| foverrides.setDim(0); // reset in case semantic() is being retried for this function |
| |
| storage_class |= sc->stc & ~STCref; |
| ad = isThis(); |
| // Don't nest structs b/c of generated methods which should not access the outer scopes. |
| // https://issues.dlang.org/show_bug.cgi?id=16627 |
| if (ad && !generated) |
| { |
| storage_class |= ad->storage_class & (STC_TYPECTOR | STCsynchronized); |
| ad->makeNested(); |
| } |
| if (sc->func) |
| storage_class |= sc->func->storage_class & STCdisable; |
| // Remove prefix storage classes silently. |
| if ((storage_class & STC_TYPECTOR) && !(ad || isNested())) |
| storage_class &= ~STC_TYPECTOR; |
| |
| //printf("function storage_class = x%llx, sc->stc = x%llx, %x\n", storage_class, sc->stc, Declaration::isFinal()); |
| |
| FuncLiteralDeclaration *fld = isFuncLiteralDeclaration(); |
| if (fld && fld->treq) |
| { |
| Type *treq = fld->treq; |
| assert(treq->nextOf()->ty == Tfunction); |
| if (treq->ty == Tdelegate) |
| fld->tok = TOKdelegate; |
| else if (treq->ty == Tpointer && treq->nextOf()->ty == Tfunction) |
| fld->tok = TOKfunction; |
| else |
| assert(0); |
| linkage = treq->nextOf()->toTypeFunction()->linkage; |
| } |
| else |
| linkage = sc->linkage; |
| inlining = sc->inlining; |
| protection = sc->protection; |
| userAttribDecl = sc->userAttribDecl; |
| |
| if (!originalType) |
| originalType = type->syntaxCopy(); |
| if (type->ty != Tfunction) |
| { |
| if (type->ty != Terror) |
| { |
| error("%s must be a function instead of %s", toChars(), type->toChars()); |
| type = Type::terror; |
| } |
| errors = true; |
| return; |
| } |
| if (!type->deco) |
| { |
| sc = sc->push(); |
| sc->stc |= storage_class & (STCdisable | STCdeprecated); // forward to function type |
| TypeFunction *tf = type->toTypeFunction(); |
| |
| if (sc->func) |
| { |
| /* If the nesting parent is pure without inference, |
| * then this function defaults to pure too. |
| * |
| * auto foo() pure { |
| * auto bar() {} // become a weak purity funciton |
| * class C { // nested class |
| * auto baz() {} // become a weak purity funciton |
| * } |
| * |
| * static auto boo() {} // typed as impure |
| * // Even though, boo cannot call any impure functions. |
| * // See also Expression::checkPurity(). |
| * } |
| */ |
| if (tf->purity == PUREimpure && (isNested() || isThis())) |
| { |
| FuncDeclaration *fd = NULL; |
| for (Dsymbol *p = toParent2(); p; p = p->toParent2()) |
| { |
| if (AggregateDeclaration *adx = p->isAggregateDeclaration()) |
| { |
| if (adx->isNested()) |
| continue; |
| break; |
| } |
| if ((fd = p->isFuncDeclaration()) != NULL) |
| break; |
| } |
| |
| /* If the parent's purity is inferred, then this function's purity needs |
| * to be inferred first. |
| */ |
| if (fd && fd->isPureBypassingInference() >= PUREweak && |
| !isInstantiated()) |
| { |
| tf->purity = PUREfwdref; // default to pure |
| } |
| } |
| } |
| |
| if (tf->isref) sc->stc |= STCref; |
| if (tf->isscope) sc->stc |= STCscope; |
| if (tf->isnothrow) sc->stc |= STCnothrow; |
| if (tf->isnogc) sc->stc |= STCnogc; |
| if (tf->isproperty) sc->stc |= STCproperty; |
| if (tf->purity == PUREfwdref) sc->stc |= STCpure; |
| if (tf->trust != TRUSTdefault) |
| sc->stc &= ~(STCsafe | STCsystem | STCtrusted); |
| if (tf->trust == TRUSTsafe) sc->stc |= STCsafe; |
| if (tf->trust == TRUSTsystem) sc->stc |= STCsystem; |
| if (tf->trust == TRUSTtrusted) sc->stc |= STCtrusted; |
| |
| if (isCtorDeclaration()) |
| { |
| sc->flags |= SCOPEctor; |
| |
| Type *tret = ad->handleType(); |
| assert(tret); |
| tret = tret->addStorageClass(storage_class | sc->stc); |
| tret = tret->addMod(type->mod); |
| tf->next = tret; |
| |
| if (ad->isStructDeclaration()) |
| sc->stc |= STCref; |
| } |
| |
| // 'return' on a non-static class member function implies 'scope' as well |
| if (ad && ad->isClassDeclaration() && (tf->isreturn || sc->stc & STCreturn) && !(sc->stc & STCstatic)) |
| sc->stc |= STCscope; |
| |
| // If 'this' has no pointers, remove 'scope' as it has no meaning |
| if (sc->stc & STCscope && ad && ad->isStructDeclaration() && !ad->type->hasPointers()) |
| { |
| sc->stc &= ~STCscope; |
| tf->isscope = false; |
| } |
| |
| sc->linkage = linkage; |
| |
| if (!tf->isNaked() && !(isThis() || isNested())) |
| { |
| OutBuffer buf; |
| MODtoBuffer(&buf, tf->mod); |
| error("without 'this' cannot be %s", buf.peekString()); |
| tf->mod = 0; // remove qualifiers |
| } |
| |
| /* Apply const, immutable, wild and shared storage class |
| * to the function type. Do this before type semantic. |
| */ |
| StorageClass stc = storage_class; |
| if (type->isImmutable()) |
| stc |= STCimmutable; |
| if (type->isConst()) |
| stc |= STCconst; |
| if (type->isShared() || storage_class & STCsynchronized) |
| stc |= STCshared; |
| if (type->isWild()) |
| stc |= STCwild; |
| switch (stc & STC_TYPECTOR) |
| { |
| case STCimmutable: |
| case STCimmutable | STCconst: |
| case STCimmutable | STCwild: |
| case STCimmutable | STCwild | STCconst: |
| case STCimmutable | STCshared: |
| case STCimmutable | STCshared | STCconst: |
| case STCimmutable | STCshared | STCwild: |
| case STCimmutable | STCshared | STCwild | STCconst: |
| // Don't use immutableOf(), as that will do a merge() |
| type = type->makeImmutable(); |
| break; |
| |
| case STCconst: |
| type = type->makeConst(); |
| break; |
| |
| case STCwild: |
| type = type->makeWild(); |
| break; |
| |
| case STCwild | STCconst: |
| type = type->makeWildConst(); |
| break; |
| |
| case STCshared: |
| type = type->makeShared(); |
| break; |
| |
| case STCshared | STCconst: |
| type = type->makeSharedConst(); |
| break; |
| |
| case STCshared | STCwild: |
| type = type->makeSharedWild(); |
| break; |
| |
| case STCshared | STCwild | STCconst: |
| type = type->makeSharedWildConst(); |
| break; |
| |
| case 0: |
| break; |
| |
| default: |
| assert(0); |
| } |
| |
| type = type->semantic(loc, sc); |
| sc = sc->pop(); |
| } |
| if (type->ty != Tfunction) |
| { |
| if (type->ty != Terror) |
| { |
| error("%s must be a function instead of %s", toChars(), type->toChars()); |
| type = Type::terror; |
| } |
| errors = true; |
| return; |
| } |
| else |
| { |
| // Merge back function attributes into 'originalType'. |
| // It's used for mangling, ddoc, and json output. |
| TypeFunction *tfo = originalType->toTypeFunction(); |
| TypeFunction *tfx = type->toTypeFunction(); |
| tfo->mod = tfx->mod; |
| tfo->isscope = tfx->isscope; |
| tfo->isscopeinferred = tfx->isscopeinferred; |
| tfo->isref = tfx->isref; |
| tfo->isnothrow = tfx->isnothrow; |
| tfo->isnogc = tfx->isnogc; |
| tfo->isproperty = tfx->isproperty; |
| tfo->purity = tfx->purity; |
| tfo->trust = tfx->trust; |
| |
| storage_class &= ~(STC_TYPECTOR | STC_FUNCATTR); |
| } |
| |
| f = (TypeFunction *)type; |
| |
| if ((storage_class & STCauto) && !f->isref && !inferRetType) |
| error("storage class 'auto' has no effect if return type is not inferred"); |
| /* Functions can only be 'scope' if they have a 'this' |
| */ |
| if (f->isscope && !isNested() && !ad) |
| { |
| error("functions cannot be scope"); |
| } |
| |
| if (f->isreturn && !needThis() && !isNested()) |
| { |
| /* Non-static nested functions have a hidden 'this' pointer to which |
| * the 'return' applies |
| */ |
| error("static member has no 'this' to which 'return' can apply"); |
| } |
| |
| if (isAbstract() && !isVirtual()) |
| { |
| const char *sfunc; |
| if (isStatic()) |
| sfunc = "static"; |
| else if (protection.kind == PROTprivate || protection.kind == PROTpackage) |
| sfunc = protectionToChars(protection.kind); |
| else |
| sfunc = "non-virtual"; |
| error("%s functions cannot be abstract", sfunc); |
| } |
| |
| if (isOverride() && !isVirtual()) |
| { |
| PROTKIND kind = prot().kind; |
| if ((kind == PROTprivate || kind == PROTpackage) && isMember()) |
| error("%s method is not virtual and cannot override", protectionToChars(kind)); |
| else |
| error("cannot override a non-virtual function"); |
| } |
| |
| if (isAbstract() && isFinalFunc()) |
| error("cannot be both final and abstract"); |
| |
| id = parent->isInterfaceDeclaration(); |
| if (id) |
| { |
| storage_class |= STCabstract; |
| |
| if (isCtorDeclaration() || |
| isPostBlitDeclaration() || |
| isDtorDeclaration() || |
| isInvariantDeclaration() || |
| isNewDeclaration() || isDelete()) |
| error("constructors, destructors, postblits, invariants, new and delete functions are not allowed in interface %s", id->toChars()); |
| if (fbody && isVirtual()) |
| error("function body only allowed in final functions in interface %s", id->toChars()); |
| } |
| |
| if (UnionDeclaration *ud = parent->isUnionDeclaration()) |
| { |
| if (isPostBlitDeclaration() || |
| isDtorDeclaration() || |
| isInvariantDeclaration()) |
| 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()) |
| { |
| goto Ldone; |
| } |
| } |
| |
| if (ClassDeclaration *cd = parent->isClassDeclaration()) |
| { |
| if (isCtorDeclaration()) |
| { |
| goto Ldone; |
| } |
| |
| if (storage_class & STCabstract) |
| cd->isabstract = ABSyes; |
| |
| // if static function, do not put in vtbl[] |
| if (!isVirtual()) |
| { |
| //printf("\tnot virtual\n"); |
| goto Ldone; |
| } |
| // Suppress further errors if the return type is an error |
| if (type->nextOf() == Type::terror) |
| goto Ldone; |
| |
| bool may_override = false; |
| for (size_t i = 0; i < cd->baseclasses->dim; i++) |
| { |
| BaseClass *b = (*cd->baseclasses)[i]; |
| ClassDeclaration *cbd = b->type->toBasetype()->isClassHandle(); |
| if (!cbd) |
| continue; |
| for (size_t j = 0; j < cbd->vtbl.dim; j++) |
| { |
| FuncDeclaration *f2 = cbd->vtbl[j]->isFuncDeclaration(); |
| if (!f2 || f2->ident != ident) |
| continue; |
| if (cbd->parent && cbd->parent->isTemplateInstance()) |
| { |
| if (!f2->functionSemantic()) |
| goto Ldone; |
| } |
| may_override = true; |
| } |
| } |
| if (may_override && type->nextOf() == NULL) |
| { |
| /* If same name function exists in base class but 'this' is auto return, |
| * cannot find index of base class's vtbl[] to override. |
| */ |
| error("return type inference is not supported if may override base class function"); |
| } |
| |
| /* Find index of existing function in base class's vtbl[] to override |
| * (the index will be the same as in cd's current vtbl[]) |
| */ |
| int vi = cd->baseClass ? findVtblIndex((Dsymbols*)&cd->baseClass->vtbl, (int)cd->baseClass->vtbl.dim) |
| : -1; |
| |
| bool doesoverride = false; |
| switch (vi) |
| { |
| case -1: |
| Lintro: |
| /* Didn't find one, so |
| * This is an 'introducing' function which gets a new |
| * slot in the vtbl[]. |
| */ |
| |
| // Verify this doesn't override previous final function |
| if (cd->baseClass) |
| { |
| Dsymbol *s = cd->baseClass->search(loc, ident); |
| if (s) |
| { |
| FuncDeclaration *f2 = s->isFuncDeclaration(); |
| if (f2) |
| { |
| f2 = f2->overloadExactMatch(type); |
| if (f2 && f2->isFinalFunc() && f2->prot().kind != PROTprivate) |
| error("cannot override final function %s", f2->toPrettyChars()); |
| } |
| } |
| } |
| |
| /* These quirky conditions mimic what VC++ appears to do |
| */ |
| if (global.params.mscoff && cd->cpp && |
| cd->baseClass && cd->baseClass->vtbl.dim) |
| { |
| /* if overriding an interface function, then this is not |
| * introducing and don't put it in the class vtbl[] |
| */ |
| interfaceVirtual = overrideInterface(); |
| if (interfaceVirtual) |
| { |
| //printf("\tinterface function %s\n", toChars()); |
| cd->vtblFinal.push(this); |
| goto Linterfaces; |
| } |
| } |
| |
| if (isFinalFunc()) |
| { |
| // Don't check here, as it may override an interface function |
| //if (isOverride()) |
| //error("is marked as override, but does not override any function"); |
| cd->vtblFinal.push(this); |
| } |
| else |
| { |
| //printf("\tintroducing function %s\n", toChars()); |
| introducing = 1; |
| if (cd->cpp && Target::reverseCppOverloads) |
| { |
| // with dmc, overloaded functions are grouped and in reverse order |
| vtblIndex = (int)cd->vtbl.dim; |
| for (int i = 0; i < (int)cd->vtbl.dim; i++) |
| { |
| if (cd->vtbl[i]->ident == ident && cd->vtbl[i]->parent == parent) |
| { |
| vtblIndex = (int)i; |
| break; |
| } |
| } |
| // shift all existing functions back |
| for (int i = (int)cd->vtbl.dim; i > vtblIndex; i--) |
| { |
| FuncDeclaration *fd = cd->vtbl[i-1]->isFuncDeclaration(); |
| assert(fd); |
| fd->vtblIndex++; |
| } |
| cd->vtbl.insert(vtblIndex, this); |
| } |
| else |
| { |
| // Append to end of vtbl[] |
| vi = (int)cd->vtbl.dim; |
| cd->vtbl.push(this); |
| vtblIndex = vi; |
| } |
| } |
| break; |
| |
| case -2: |
| // can't determine because of forward references |
| return; |
| |
| default: |
| { |
| FuncDeclaration *fdv = cd->baseClass->vtbl[vi]->isFuncDeclaration(); |
| FuncDeclaration *fdc = cd->vtbl[vi]->isFuncDeclaration(); |
| // This function is covariant with fdv |
| |
| if (fdc == this) |
| { |
| doesoverride = true; |
| break; |
| } |
| |
| if (fdc->toParent() == parent) |
| { |
| //printf("vi = %d,\tthis = %p %s %s @ [%s]\n\tfdc = %p %s %s @ [%s]\n\tfdv = %p %s %s @ [%s]\n", |
| // vi, this, this->toChars(), this->type->toChars(), this->loc.toChars(), |
| // fdc, fdc ->toChars(), fdc ->type->toChars(), fdc ->loc.toChars(), |
| // fdv, fdv ->toChars(), fdv ->type->toChars(), fdv ->loc.toChars()); |
| |
| // fdc overrides fdv exactly, then this introduces new function. |
| if (fdc->type->mod == fdv->type->mod && this->type->mod != fdv->type->mod) |
| goto Lintro; |
| } |
| |
| // This function overrides fdv |
| if (fdv->isFinalFunc()) |
| error("cannot override final function %s", fdv->toPrettyChars()); |
| |
| if (!isOverride()) |
| { |
| if (fdv->isFuture()) |
| { |
| ::deprecation(loc, "@future base class method %s is being overridden by %s; rename the latter", |
| fdv->toPrettyChars(), toPrettyChars()); |
| // Treat 'this' as an introducing function, giving it a separate hierarchy in the vtbl[] |
| goto Lintro; |
| } |
| else |
| { |
| int vi2 = findVtblIndex(&cd->baseClass->vtbl, (int)cd->baseClass->vtbl.dim, false); |
| if (vi2 < 0) |
| // https://issues.dlang.org/show_bug.cgi?id=17349 |
| ::deprecation(loc, "cannot implicitly override base class method `%s` with `%s`; add `override` attribute", |
| fdv->toPrettyChars(), toPrettyChars()); |
| else |
| ::error(loc, "implicitly overriding base class method %s with %s deprecated; add 'override' attribute", |
| fdv->toPrettyChars(), toPrettyChars()); |
| } |
| } |
| |
| doesoverride = true; |
| if (fdc->toParent() == parent) |
| { |
| // If both are mixins, or both are not, then error. |
| // If either is not, the one that is not overrides the other. |
| bool thismixin = this->parent->isClassDeclaration() != NULL; |
| bool fdcmixin = fdc->parent->isClassDeclaration() != NULL; |
| if (thismixin == fdcmixin) |
| { |
| error("multiple overrides of same function"); |
| } |
| else if (!thismixin) // fdc overrides fdv |
| { |
| // this doesn't override any function |
| break; |
| } |
| } |
| cd->vtbl[vi] = this; |
| vtblIndex = vi; |
| |
| /* Remember which functions this overrides |
| */ |
| foverrides.push(fdv); |
| |
| /* This works by whenever this function is called, |
| * it actually returns tintro, which gets dynamically |
| * cast to type. But we know that tintro is a base |
| * of type, so we could optimize it by not doing a |
| * dynamic cast, but just subtracting the isBaseOf() |
| * offset if the value is != null. |
| */ |
| |
| if (fdv->tintro) |
| tintro = fdv->tintro; |
| else if (!type->equals(fdv->type)) |
| { |
| /* Only need to have a tintro if the vptr |
| * offsets differ |
| */ |
| int offset; |
| if (fdv->type->nextOf()->isBaseOf(type->nextOf(), &offset)) |
| { |
| tintro = fdv->type; |
| } |
| } |
| break; |
| } |
| } |
| |
| /* Go through all the interface bases. |
| * If this function is covariant with any members of those interface |
| * functions, set the tintro. |
| */ |
| Linterfaces: |
| for (size_t i = 0; i < cd->interfaces.length; i++) |
| { |
| BaseClass *b = cd->interfaces.ptr[i]; |
| vi = findVtblIndex((Dsymbols *)&b->sym->vtbl, (int)b->sym->vtbl.dim); |
| switch (vi) |
| { |
| case -1: |
| break; |
| |
| case -2: |
| // can't determine because of forward references |
| return; |
| |
| default: |
| { |
| FuncDeclaration *fdv = (FuncDeclaration *)b->sym->vtbl[vi]; |
| Type *ti = NULL; |
| |
| /* Remember which functions this overrides |
| */ |
| foverrides.push(fdv); |
| |
| /* Should we really require 'override' when implementing |
| * an interface function? |
| */ |
| //if (!isOverride()) |
| //warning(loc, "overrides base class function %s, but is not marked with 'override'", fdv->toPrettyChars()); |
| |
| if (fdv->tintro) |
| ti = fdv->tintro; |
| else if (!type->equals(fdv->type)) |
| { |
| /* Only need to have a tintro if the vptr |
| * offsets differ |
| */ |
| int offset; |
| if (fdv->type->nextOf()->isBaseOf(type->nextOf(), &offset)) |
| { |
| ti = fdv->type; |
| } |
| } |
| if (ti) |
| { |
| if (tintro) |
| { |
| if (!tintro->nextOf()->equals(ti->nextOf()) && |
| !tintro->nextOf()->isBaseOf(ti->nextOf(), NULL) && |
| !ti->nextOf()->isBaseOf(tintro->nextOf(), NULL)) |
| { |
| error("incompatible covariant types %s and %s", tintro->toChars(), ti->toChars()); |
| } |
| } |
| tintro = ti; |
| } |
| goto L2; |
| } |
| } |
| } |
| |
| if (!doesoverride && isOverride() && (type->nextOf() || !may_override)) |
| { |
| BaseClass *bc = NULL; |
| Dsymbol *s = NULL; |
| for (size_t i = 0; i < cd->baseclasses->dim; i++) |
| { |
| bc = (*cd->baseclasses)[i]; |
| s = bc->sym->search_correct(ident); |
| if (s) break; |
| } |
| |
| if (s) |
| error("does not override any function, did you mean to override '%s%s'?", |
| bc->sym->isCPPclass() ? "extern (C++) " : "", s->toPrettyChars()); |
| else |
| error("does not override any function"); |
| } |
| |
| L2: ; |
| |
| /* Go through all the interface bases. |
| * Disallow overriding any final functions in the interface(s). |
| */ |
| for (size_t i = 0; i < cd->interfaces.length; i++) |
| { |
| BaseClass *b = cd->interfaces.ptr[i]; |
| if (b->sym) |
| { |
| Dsymbol *s = search_function(b->sym, ident); |
| if (s) |
| { |
| FuncDeclaration *f2 = s->isFuncDeclaration(); |
| if (f2) |
| { |
| f2 = f2->overloadExactMatch(type); |
| if (f2 && f2->isFinalFunc() && f2->prot().kind != PROTprivate) |
| error("cannot override final function %s.%s", b->sym->toChars(), f2->toPrettyChars()); |
| } |
| } |
| } |
| } |
| |
| if (isOverride()) |
| { |
| if (storage_class & STCdisable) |
| deprecation("overridden functions cannot be annotated @disable"); |
| if (isDeprecated()) |
| deprecation("deprecated functions cannot be annotated @disable"); |
| } |
| } |
| else if (isOverride() && !parent->isTemplateInstance()) |
| error("override only applies to class member functions"); |
| |
| // Reflect this->type to f because it could be changed by findVtblIndex |
| f = type->toTypeFunction(); |
| |
| /* Do not allow template instances to add virtual functions |
| * to a class. |
| */ |
| if (isVirtual()) |
| { |
| TemplateInstance *ti = parent->isTemplateInstance(); |
| if (ti) |
| { |
| // Take care of nested templates |
| while (1) |
| { |
| TemplateInstance *ti2 = ti->tempdecl->parent->isTemplateInstance(); |
| if (!ti2) |
| break; |
| ti = ti2; |
| } |
| |
| // If it's a member template |
| ClassDeclaration *cd = ti->tempdecl->isClassMember(); |
| if (cd) |
| { |
| error("cannot use template to add virtual function to class '%s'", cd->toChars()); |
| } |
| } |
| } |
| |
| if (isMain()) |
| checkDmain(); // Check main() parameters and return type |
| |
| Ldone: |
| /* Purity and safety can be inferred for some functions by examining |
| * the function body. |
| */ |
| if (canInferAttributes(this, sc)) |
| initInferAttributes(this); |
| |
| Module::dprogress++; |
| semanticRun = PASSsemanticdone; |
| |
| /* Save scope for possible later use (if we need the |
| * function internals) |
| */ |
| _scope = sc->copy(); |
| _scope->setNoFree(); |
| |
| static bool printedMain = false; // semantic might run more than once |
| if (global.params.verbose && !printedMain) |
| { |
| const char *type = isMain() ? "main" : isWinMain() ? "winmain" : isDllMain() ? "dllmain" : (const char *)NULL; |
| Module *mod = sc->_module; |
| |
| if (type && mod) |
| { |
| printedMain = true; |
| const char *name = FileName::searchPath(global.path, mod->srcfile->toChars(), true); |
| message("entry %-10s\t%s", type, name); |
| } |
| } |
| |
| if (fbody && isMain() && sc->_module->isRoot()) |
| Compiler::genCmain(sc); |
| |
| assert(type->ty != Terror || errors); |
| } |
| |
| void FuncDeclaration::semantic2(Scope *sc) |
| { |
| if (semanticRun >= PASSsemantic2done) |
| return; |
| assert(semanticRun <= PASSsemantic2); |
| semanticRun = PASSsemantic2; |
| |
| objc()->setSelector(this, sc); |
| objc()->validateSelector(this); |
| |
| if (parent->isClassDeclaration()) |
| { |
| objc()->checkLinkage(this); |
| } |
| } |
| |
| /**************************************************** |
| * Determine whether an 'out' contract is declared inside |
| * the given function or any of its overrides. |
| * Params: |
| * fd = the function to search |
| * Returns: |
| * true found an 'out' contract |
| * false didn't find one |
| */ |
| static bool needsFensure(FuncDeclaration *fd) |
| { |
| if (fd->fensure) |
| return true; |
| |
| for (size_t i = 0; i < fd->foverrides.dim; i++) |
| { |
| FuncDeclaration *fdv = fd->foverrides[i]; |
| |
| if (fdv->fensure) |
| return true; |
| |
| if (needsFensure(fdv)) |
| return true; |
| } |
| return false; |
| } |
| |
| /**************************************************** |
| * Rewrite contracts as nested functions, then call them. Doing it as nested |
| * functions means that overriding functions can call them. |
| * Params: |
| * fd = the function to rewrite contracts for |
| */ |
| static void buildEnsureRequire(FuncDeclaration *fdx) |
| { |
| if (!fdx->isVirtual()) |
| return; |
| |
| TypeFunction *f = (TypeFunction *)fdx->type; |
| |
| if (fdx->frequire) |
| { |
| /* in { ... } |
| * becomes: |
| * void __require() { ... } |
| * __require(); |
| */ |
| Loc loc = fdx->frequire->loc; |
| TypeFunction *tf = new TypeFunction(NULL, Type::tvoid, 0, LINKd); |
| tf->isnothrow = f->isnothrow; |
| tf->isnogc = f->isnogc; |
| tf->purity = f->purity; |
| tf->trust = f->trust; |
| FuncDeclaration *fd = new FuncDeclaration(loc, loc, |
| Id::require, STCundefined, tf); |
| fd->fbody = fdx->frequire; |
| Statement *s1 = new ExpStatement(loc, fd); |
| Expression *e = new CallExp(loc, new VarExp(loc, fd, false), (Expressions *)NULL); |
| Statement *s2 = new ExpStatement(loc, e); |
| fdx->frequire = new CompoundStatement(loc, s1, s2); |
| 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) { ... } |
| * becomes: |
| * void __ensure(ref tret result) { ... } |
| * __ensure(result); |
| */ |
| Loc loc = fdx->fensure->loc; |
| Parameters *fparams = new Parameters(); |
| Parameter *p = NULL; |
| if (fdx->outId) |
| { |
| p = new Parameter(STCref | STCconst, f->nextOf(), fdx->outId, NULL); |
| fparams->push(p); |
| } |
| TypeFunction *tf = new TypeFunction(fparams, Type::tvoid, 0, LINKd); |
| tf->isnothrow = f->isnothrow; |
| tf->isnogc = f->isnogc; |
| tf->purity = f->purity; |
| tf->trust = f->trust; |
| FuncDeclaration *fd = new FuncDeclaration(loc, loc, |
| Id::ensure, STCundefined, tf); |
| fd->fbody = fdx->fensure; |
| Statement *s1 = new ExpStatement(loc, fd); |
| Expression *eresult = NULL; |
| if (fdx->outId) |
| eresult = new IdentifierExp(loc, fdx->outId); |
| Expression *e = new CallExp(loc, new VarExp(loc, fd, false), eresult); |
| Statement *s2 = new ExpStatement(loc, e); |
| fdx->fensure = new CompoundStatement(loc, s1, s2); |
| fdx->fdensure = fd; |
| } |
| } |
| |
| /* Determine if function should add `return 0;` |
| */ |
| static bool addReturn0(FuncDeclaration *funcdecl) |
| { |
| TypeFunction *f = (TypeFunction *)funcdecl->type; |
| |
| return f->next->ty == Tvoid && |
| (funcdecl->isMain() || (global.params.betterC && funcdecl->isCMain())); |
| } |
| |
| // Do the semantic analysis on the internals of the function. |
| |
| void FuncDeclaration::semantic3(Scope *sc) |
| { |
| VarDeclaration *_arguments = NULL; |
| |
| if (!parent) |
| { |
| if (global.errors) |
| return; |
| //printf("FuncDeclaration::semantic3(%s '%s', sc = %p)\n", kind(), toChars(), sc); |
| assert(0); |
| } |
| if (errors || isError(parent)) |
| { |
| errors = true; |
| return; |
| } |
| //printf("FuncDeclaration::semantic3('%s.%s', %p, sc = %p, loc = %s)\n", parent->toChars(), toChars(), this, sc, loc.toChars()); |
| //fflush(stdout); |
| //printf("storage class = x%x %x\n", sc->stc, storage_class); |
| //{ static int x; if (++x == 2) *(char*)0=0; } |
| //printf("\tlinkage = %d\n", sc->linkage); |
| |
| if (ident == Id::assign && !inuse) |
| { |
| if (storage_class & STCinference) |
| { |
| /* Bugzilla 15044: For generated opAssign function, any errors |
| * from its body need to be gagged. |
| */ |
| unsigned oldErrors = global.startGagging(); |
| ++inuse; |
| semantic3(sc); |
| --inuse; |
| if (global.endGagging(oldErrors)) // if errors happened |
| { |
| // Disable generated opAssign, because some members forbid identity assignment. |
| storage_class |= STCdisable; |
| fbody = NULL; // remove fbody which contains the error |
| semantic3Errors = false; |
| } |
| return; |
| } |
| } |
| |
| //printf(" sc->incontract = %d\n", (sc->flags & SCOPEcontract)); |
| if (semanticRun >= PASSsemantic3) |
| return; |
| semanticRun = PASSsemantic3; |
| semantic3Errors = false; |
| |
| if (!type || type->ty != Tfunction) |
| return; |
| TypeFunction *f = (TypeFunction *)type; |
| if (!inferRetType && f->next->ty == Terror) |
| return; |
| |
| if (!fbody && inferRetType && !f->next) |
| { |
| error("has no function body with return type inference"); |
| return; |
| } |
| |
| unsigned oldErrors = global.errors; |
| |
| if (frequire) |
| { |
| for (size_t i = 0; i < foverrides.dim; i++) |
| { |
| FuncDeclaration *fdv = foverrides[i]; |
| |
| if (fdv->fbody && !fdv->frequire) |
| { |
| error("cannot have an in contract when overriden function %s does not have an in contract", fdv->toPrettyChars()); |
| break; |
| } |
| } |
| } |
| |
| // Remember whether we need to generate an 'out' contract. |
| bool needEnsure = needsFensure(this); |
| |
| if (fbody || frequire || needEnsure) |
| { |
| /* Symbol table into which we place parameters and nested functions, |
| * solely to diagnose name collisions. |
| */ |
| localsymtab = new DsymbolTable(); |
| |
| // Establish function scope |
| ScopeDsymbol *ss = new ScopeDsymbol(); |
| // find enclosing scope symbol, might skip symbol-less CTFE and/or FuncExp scopes |
| for (Scope *scx = sc; ; scx = scx->enclosing) |
| { |
| if (scx->scopesym) |
| { |
| ss->parent = scx->scopesym; |
| break; |
| } |
| } |
| ss->loc = loc; |
| ss->endlinnum = endloc.linnum; |
| Scope *sc2 = sc->push(ss); |
| sc2->func = this; |
| sc2->parent = this; |
| sc2->callSuper = 0; |
| sc2->sbreak = NULL; |
| sc2->scontinue = NULL; |
| sc2->sw = NULL; |
| sc2->fes = fes; |
| sc2->linkage = LINKd; |
| sc2->stc &= ~(STCauto | STCscope | STCstatic | STCextern | STCabstract | |
| STCdeprecated | STCoverride | |
| STC_TYPECTOR | STCfinal | STCtls | STCgshared | STCref | STCreturn | |
| STCproperty | STCnothrow | STCpure | STCsafe | STCtrusted | STCsystem); |
| sc2->protection = Prot(PROTpublic); |
| sc2->explicitProtection = 0; |
| sc2->aligndecl = NULL; |
| if (this->ident != Id::require && this->ident != Id::ensure) |
| sc2->flags = sc->flags & ~SCOPEcontract; |
| sc2->flags &= ~SCOPEcompile; |
| sc2->tf = NULL; |
| sc2->os = NULL; |
| sc2->noctor = 0; |
| sc2->userAttribDecl = NULL; |
| if (sc2->intypeof == 1) sc2->intypeof = 2; |
| sc2->fieldinit = NULL; |
| sc2->fieldinit_dim = 0; |
| |
| /* Note: When a lambda is defined immediately under aggregate member |
| * scope, it should be contextless due to prevent interior pointers. |
| * e.g. |
| * // dg points 'this' - it's interior pointer |
| * class C { int x; void delegate() dg = (){ this.x = 1; }; } |
| * |
| * However, lambdas could be used inside typeof, in order to check |
| * some expressions varidity at compile time. For such case the lambda |
| * body can access aggregate instance members. |
| * e.g. |
| * class C { int x; static assert(is(typeof({ this.x = 1; }))); } |
| * |
| * To properly accept it, mark these lambdas as member functions - |
| * isThis() returns true and isNested() returns false. |
| */ |
| if (FuncLiteralDeclaration *fld = isFuncLiteralDeclaration()) |
| { |
| if (AggregateDeclaration *ad = isMember2()) |
| { |
| if (!sc->intypeof) |
| { |
| if (fld->tok == TOKdelegate) |
| error("cannot be %s members", ad->kind()); |
| else |
| fld->tok = TOKfunction; |
| } |
| else |
| { |
| if (fld->tok != TOKfunction) |
| fld->tok = TOKdelegate; |
| } |
| assert(!isNested()); |
| } |
| } |
| |
| // Declare 'this' |
| AggregateDeclaration *ad = isThis(); |
| vthis = declareThis(sc2, ad); |
| //printf("[%s] ad = %p vthis = %p\n", loc.toChars(), ad, vthis); |
| //if (vthis) printf("\tvthis->type = %s\n", vthis->type->toChars()); |
| |
| // Declare hidden variable _arguments[] and _argptr |
| if (f->varargs == 1) |
| { |
| if (f->linkage == LINKd) |
| { |
| // Variadic arguments depend on Typeinfo being defined |
| if (!global.params.useTypeInfo || !Type::dtypeinfo || !Type::typeinfotypelist) |
| { |
| if (!global.params.useTypeInfo) |
| error("D-style variadic functions cannot be used with -betterC"); |
| else if (!Type::typeinfotypelist) |
| error("`object.TypeInfo_Tuple` could not be found, but is implicitly used in D-style variadic functions"); |
| else |
| error("`object.TypeInfo` could not be found, but is implicitly used in D-style variadic functions"); |
| fatal(); |
| } |
| |
| // Declare _arguments[] |
| v_arguments = new VarDeclaration(Loc(), Type::typeinfotypelist->type, Id::_arguments_typeinfo, NULL); |
| v_arguments->storage_class |= STCtemp | STCparameter; |
| v_arguments->semantic(sc2); |
| sc2->insert(v_arguments); |
| v_arguments->parent = this; |
| |
| //Type *t = Type::typeinfo->type->constOf()->arrayOf(); |
| Type *t = Type::dtypeinfo->type->arrayOf(); |
| _arguments = new VarDeclaration(Loc(), t, Id::_arguments, NULL); |
| _arguments->storage_class |= STCtemp; |
| _arguments->semantic(sc2); |
| sc2->insert(_arguments); |
| _arguments->parent = this; |
| } |
| if (f->linkage == LINKd || (f->parameters && Parameter::dim(f->parameters))) |
| { |
| // Declare _argptr |
| Type *t = Type::tvalist; |
| v_argptr = new VarDeclaration(Loc(), t, Id::_argptr, NULL); |
| v_argptr->storage_class |= STCtemp; |
| v_argptr->semantic(sc2); |
| sc2->insert(v_argptr); |
| v_argptr->parent = this; |
| } |
| } |
| |
| /* Declare all the function parameters as variables |
| * and install them in parameters[] |
| */ |
| size_t nparams = Parameter::dim(f->parameters); |
| if (nparams) |
| { |
| /* parameters[] has all the tuples removed, as the back end |
| * doesn't know about tuples |
| */ |
| parameters = new VarDeclarations(); |
| parameters->reserve(nparams); |
| for (size_t i = 0; i < nparams; i++) |
| { |
| Parameter *fparam = Parameter::getNth(f->parameters, i); |
| Identifier *id = fparam->ident; |
| StorageClass stc = 0; |
| if (!id) |
| { |
| /* Generate identifier for un-named parameter, |
| * because we need it later on. |
| */ |
| fparam->ident = id = Identifier::generateId("_param_", i); |
| stc |= STCtemp; |
| } |
| Type *vtype = fparam->type; |
| VarDeclaration *v = new VarDeclaration(loc, vtype, id, NULL); |
| //printf("declaring parameter %s of type %s\n", v->toChars(), v->type->toChars()); |
| stc |= STCparameter; |
| if (f->varargs == 2 && i + 1 == nparams) |
| stc |= STCvariadic; |
| if (flags & FUNCFLAGinferScope && !(fparam->storageClass & STCscope)) |
| stc |= STCmaybescope; |
| stc |= fparam->storageClass & (STCin | STCout | STCref | STCreturn | STCscope | STClazy | STCfinal | STC_TYPECTOR | STCnodtor); |
| v->storage_class = stc; |
| v->semantic(sc2); |
| if (!sc2->insert(v)) |
| error("parameter %s.%s is already defined", toChars(), v->toChars()); |
| else |
| parameters->push(v); |
| localsymtab->insert(v); |
| v->parent = this; |
| } |
| } |
| |
| // Declare the tuple symbols and put them in the symbol table, |
| // but not in parameters[]. |
| if (f->parameters) |
| { |
| for (size_t i = 0; i < f->parameters->dim; i++) |
| { |
| Parameter *fparam = (*f->parameters)[i]; |
| |
| if (!fparam->ident) |
| continue; // never used, so ignore |
| if (fparam->type->ty == Ttuple) |
| { |
| TypeTuple *t = (TypeTuple *)fparam->type; |
| size_t dim = Parameter::dim(t->arguments); |
| Objects *exps = new Objects(); |
| exps->setDim(dim); |
| for (size_t j = 0; j < dim; j++) |
| { |
| Parameter *narg = Parameter::getNth(t->arguments, j); |
| assert(narg->ident); |
| VarDeclaration *v = sc2->search(Loc(), narg->ident, NULL)->isVarDeclaration(); |
| assert(v); |
| Expression *e = new VarExp(v->loc, v); |
| (*exps)[j] = e; |
| } |
| assert(fparam->ident); |
| TupleDeclaration *v = new TupleDeclaration(loc, fparam->ident, exps); |
| //printf("declaring tuple %s\n", v->toChars()); |
| v->isexp = true; |
| if (!sc2->insert(v)) |
| error("parameter %s.%s is already defined", toChars(), v->toChars()); |
| localsymtab->insert(v); |
| v->parent = this; |
| } |
| } |
| } |
| |
| // Precondition invariant |
| Statement *fpreinv = NULL; |
| if (addPreInvariant()) |
| { |
| Expression *e = addInvariant(loc, sc, ad, vthis, isDtorDeclaration() != NULL); |
| if (e) |
| fpreinv = new ExpStatement(Loc(), e); |
| } |
| |
| // Postcondition invariant |
| Statement *fpostinv = NULL; |
| if (addPostInvariant()) |
| { |
| Expression *e = addInvariant(loc, sc, ad, vthis, isCtorDeclaration() != NULL); |
| if (e) |
| fpostinv = new ExpStatement(Loc(), e); |
| } |
| |
| // Pre/Postcondition contract |
| if (!fbody) |
| buildEnsureRequire(this); |
| |
| Scope *scout = NULL; |
| if (needEnsure || addPostInvariant()) |
| { |
| if ((needEnsure && global.params.useOut) || fpostinv) |
| { |
| returnLabel = new LabelDsymbol(Id::returnLabel); |
| } |
| |
| // scope of out contract (need for vresult->semantic) |
| ScopeDsymbol *sym = new ScopeDsymbol(); |
| sym->parent = sc2->scopesym; |
| sym->loc = loc; |
| sym->endlinnum = endloc.linnum; |
| scout = sc2->push(sym); |
| } |
| |
| if (fbody) |
| { |
| ScopeDsymbol *sym = new ScopeDsymbol(); |
| sym->parent = sc2->scopesym; |
| sym->loc = loc; |
| sym->endlinnum = endloc.linnum; |
| sc2 = sc2->push(sym); |
| |
| AggregateDeclaration *ad2 = isMember2(); |
| |
| /* If this is a class constructor |
| */ |
| if (ad2 && isCtorDeclaration()) |
| { |
| allocFieldinit(sc2, ad2->fields.dim); |
| for (size_t i = 0; i < ad2->fields.dim; i++) |
| { |
| VarDeclaration *v = ad2->fields[i]; |
| v->ctorinit = 0; |
| } |
| } |
| |
| if (!inferRetType && retStyle(f) != RETstack) |
| nrvo_can = 0; |
| |
| bool inferRef = (f->isref && (storage_class & STCauto)); |
| |
| fbody = ::semantic(fbody, sc2); |
| if (!fbody) |
| fbody = new CompoundStatement(Loc(), new Statements()); |
| |
| if (naked) |
| { |
| fpreinv = NULL; // can't accommodate with no stack frame |
| fpostinv = NULL; |
| } |
| |
| assert(type == f || |
| (type->ty == Tfunction && |
| f->purity == PUREimpure && |
| ((TypeFunction *)type)->purity >= PUREfwdref)); |
| f = (TypeFunction *)type; |
| |
| if (inferRetType) |
| { |
| // If no return type inferred yet, then infer a void |
| if (!f->next) |
| f->next = Type::tvoid; |
| if (f->checkRetType(loc)) |
| fbody = new ErrorStatement(); |
| } |
| if (global.params.vcomplex && f->next != NULL) |
| f->next->checkComplexTransition(loc); |
| |
| if (returns && !fbody->isErrorStatement()) |
| { |
| for (size_t i = 0; i < returns->dim; ) |
| { |
| Expression *exp = (*returns)[i]->exp; |
| if (exp->op == TOKvar && ((VarExp *)exp)->var == vresult) |
| { |
| if (addReturn0(this)) |
| exp->type = Type::tint32; |
| else |
| exp->type = f->next; |
| // Remove `return vresult;` from returns |
| returns->remove(i); |
| continue; |
| } |
| if (inferRef && f->isref && !exp->type->constConv(f->next)) // Bugzilla 13336 |
| f->isref = false; |
| i++; |
| } |
| } |
| if (f->isref) // Function returns a reference |
| { |
| if (storage_class & STCauto) |
| storage_class &= ~STCauto; |
| } |
| if (retStyle(f) != RETstack) |
| nrvo_can = 0; |
| |
| if (fbody->isErrorStatement()) |
| ; |
| else if (isStaticCtorDeclaration()) |
| { |
| /* It's a static constructor. Ensure that all |
| * ctor consts were initialized. |
| */ |
| ScopeDsymbol *pd = toParent()->isScopeDsymbol(); |
| for (size_t i = 0; i < pd->members->dim; i++) |
| { |
| Dsymbol *s = (*pd->members)[i]; |
| s->checkCtorConstInit(); |
| } |
| } |
| else if (ad2 && isCtorDeclaration()) |
| { |
| ClassDeclaration *cd = ad2->isClassDeclaration(); |
| |
| // Verify that all the ctorinit fields got initialized |
| if (!(sc2->callSuper & CSXthis_ctor)) |
| { |
| for (size_t i = 0; i < ad2->fields.dim; i++) |
| { |
| VarDeclaration *v = ad2->fields[i]; |
| if (v->isThisDeclaration()) |
| continue; |
| if (v->ctorinit == 0) |
| { |
| /* Current bugs in the flow analysis: |
| * 1. union members should not produce error messages even if |
| * not assigned to |
| * 2. structs should recognize delegating opAssign calls as well |
| * as delegating calls to other constructors |
| */ |
| if (v->isCtorinit() && !v->type->isMutable() && cd) |
| error("missing initializer for %s field %s", MODtoChars(v->type->mod), v->toChars()); |
| else if (v->storage_class & STCnodefaultctor) |
| ::error(loc, "field %s must be initialized in constructor", v->toChars()); |
| else if (v->type->needsNested()) |
| ::error(loc, "field %s must be initialized in constructor, because it is nested struct", v->toChars()); |
| } |
| else |
| { |
| bool mustInit = (v->storage_class & STCnodefaultctor || |
| v->type->needsNested()); |
| if (mustInit && !(sc2->fieldinit[i] & CSXthis_ctor)) |
| { |
| error("field %s must be initialized but skipped", v->toChars()); |
| } |
| } |
| } |
| } |
| freeFieldinit(sc2); |
| |
| if (cd && |
| !(sc2->callSuper & CSXany_ctor) && |
| cd->baseClass && cd->baseClass->ctor) |
| { |
| sc2->callSuper = 0; |
| |
| // Insert implicit super() at start of fbody |
| FuncDeclaration *fd = resolveFuncCall(Loc(), sc2, cd->baseClass->ctor, NULL, vthis->type, NULL, 1); |
| if (!fd) |
| { |
| error("no match for implicit super() call in constructor"); |
| } |
| else if (fd->storage_class & STCdisable) |
| { |
| error("cannot call super() implicitly because it is annotated with @disable"); |
| } |
| else |
| { |
| Expression *e1 = new SuperExp(Loc()); |
| Expression *e = new CallExp(Loc(), e1); |
| e = ::semantic(e, sc2); |
| |
| Statement *s = new ExpStatement(Loc(), e); |
| fbody = new CompoundStatement(Loc(), s, fbody); |
| } |
| } |
| //printf("callSuper = x%x\n", sc2->callSuper); |
| } |
| |
| /* https://issues.dlang.org/show_bug.cgi?id=17502 |
| * Wait until after the return type has been inferred before |
| * generating the contracts for this function, and merging contracts |
| * from overrides. |
| * |
| * https://issues.dlang.org/show_bug.cgi?id=17893 |
| * However should take care to generate this before inferered |
| * function attributes are applied, such as 'nothrow'. |
| * |
| * This was originally at the end of the first semantic pass, but |
| * required a fix-up to be done here for the '__result' variable |
| * type of __ensure() inside auto functions, but this didn't work |
| * if the out parameter was implicit. |
| */ |
| buildEnsureRequire(this); |
| |
| int blockexit = BEnone; |
| if (!fbody->isErrorStatement()) |
| { |
| // Check for errors related to 'nothrow'. |
| unsigned int nothrowErrors = global.errors; |
| blockexit = blockExit(fbody, this, f->isnothrow); |
| if (f->isnothrow && (global.errors != nothrowErrors)) |
| ::error(loc, "nothrow %s '%s' may throw", kind(), toPrettyChars()); |
| if (flags & FUNCFLAGnothrowInprocess) |
| { |
| if (type == f) f = (TypeFunction *)f->copy(); |
| f->isnothrow = !(blockexit & BEthrow); |
| } |
| } |
| |
| if (fbody->isErrorStatement()) |
| ; |
| else if (ad2 && isCtorDeclaration()) |
| { |
| /* Append: |
| * return this; |
| * to function body |
| */ |
| if (blockexit & BEfallthru) |
| { |
| Statement *s = new ReturnStatement(loc, NULL); |
| s = ::semantic(s, sc2); |
| fbody = new CompoundStatement(loc, fbody, s); |
| hasReturnExp |= (hasReturnExp & 1 ? 16 : 1); |
| } |
| } |
| else if (fes) |
| { |
| // For foreach(){} body, append a return 0; |
| if (blockexit & BEfallthru) |
| { |
| Expression *e = new IntegerExp(0); |
| Statement *s = new ReturnStatement(Loc(), e); |
| fbody = new CompoundStatement(Loc(), fbody, s); |
| hasReturnExp |= (hasReturnExp & 1 ? 16 : 1); |
| } |
| assert(!returnLabel); |
| } |
| else |
| { |
| const bool inlineAsm = (hasReturnExp & 8) != 0; |
| if ((blockexit & BEfallthru) && f->next->ty != Tvoid && !inlineAsm) |
| { |
| Expression *e; |
| if (!hasReturnExp) |
| error("has no return statement, but is expected to return a value of type %s", f->next->toChars()); |
| else |
| error("no return exp; or assert(0); at end of function"); |
| if (global.params.useAssert && |
| !global.params.useInline) |
| { |
| /* Add an assert(0, msg); where the missing return |
| * should be. |
| */ |
| e = new AssertExp( |
| endloc, |
| new IntegerExp(0), |
| new StringExp(loc, const_cast<char *>("missing return expression")) |
| ); |
| } |
| else |
| e = new HaltExp(endloc); |
| e = new CommaExp(Loc(), e, f->next->defaultInit()); |
| e = ::semantic(e, sc2); |
| Statement *s = new ExpStatement(Loc(), e); |
| fbody = new CompoundStatement(Loc(), fbody, s); |
| } |
| } |
| |
| if (returns) |
| { |
| bool implicit0 = addReturn0(this); |
| Type *tret = implicit0 ? Type::tint32 : f->next; |
| assert(tret->ty != Tvoid); |
| if (vresult || returnLabel) |
| buildResultVar(scout ? scout : sc2, tret); |
| |
| /* Cannot move this loop into NrvoWalker, because |
| * returns[i] may be in the nested delegate for foreach-body. |
| */ |
| for (size_t i = 0; i < returns->dim; i++) |
| { |
| ReturnStatement *rs = (*returns)[i]; |
| Expression *exp = rs->exp; |
| if (exp->op == TOKerror) |
| continue; |
| if (tret->ty == Terror) |
| { |
| // Bugzilla 13702 |
| exp = checkGC(sc2, exp); |
| continue; |
| } |
| |
| if (!exp->implicitConvTo(tret) && |
| parametersIntersect(exp->type)) |
| { |
| if (exp->type->immutableOf()->implicitConvTo(tret)) |
| exp = exp->castTo(sc2, exp->type->immutableOf()); |
| else if (exp->type->wildOf()->implicitConvTo(tret)) |
| exp = exp->castTo(sc2, exp->type->wildOf()); |
| } |
| exp = exp->implicitCastTo(sc2, tret); |
| |
| if (f->isref) |
| { |
| // Function returns a reference |
| exp = exp->toLvalue(sc2, exp); |
| checkReturnEscapeRef(sc2, exp, false); |
| } |
| else |
| { |
| exp = exp->optimize(WANTvalue); |
| |
| /* Bugzilla 10789: |
| * If NRVO is not possible, all returned lvalues should call their postblits. |
| */ |
| if (!nrvo_can) |
| exp = doCopyOrMove(sc2, exp); |
| |
| if (tret->hasPointers()) |
| checkReturnEscape(sc2, exp, false); |
| } |
| |
| exp = checkGC(sc2, exp); |
| |
| if (vresult) |
| { |
| // Create: return vresult = exp; |
| exp = new BlitExp(rs->loc, vresult, exp); |
| exp->type = vresult->type; |
| |
| if (rs->caseDim) |
| exp = Expression::combine(exp, new IntegerExp(rs->caseDim)); |
| } |
| else if (tintro && !tret->equals(tintro->nextOf())) |
| { |
| exp = exp->implicitCastTo(sc2, tintro->nextOf()); |
| } |
| rs->exp = exp; |
| } |
| } |
| if (nrvo_var || returnLabel) |
| { |
| NrvoWalker nw; |
| nw.fd = this; |
| nw.sc = sc2; |
| nw.visitStmt(fbody); |
| } |
| |
| sc2 = sc2->pop(); |
| } |
| |
| frequire = mergeFrequire(frequire); |
| fensure = mergeFensure(fensure, outId); |
| |
| Statement *freq = frequire; |
| Statement *fens = fensure; |
| |
| /* Do the semantic analysis on the [in] preconditions and |
| * [out] postconditions. |
| */ |
| if (freq) |
| { |
| /* frequire is composed of the [in] contracts |
| */ |
| ScopeDsymbol *sym = new ScopeDsymbol(); |
| sym->parent = sc2->scopesym; |
| sym->loc = loc; |
| sym->endlinnum = endloc.linnum; |
| sc2 = sc2->push(sym); |
| sc2->flags = (sc2->flags & ~SCOPEcontract) | SCOPErequire; |
| |
| // BUG: need to error if accessing out parameters |
| // BUG: need to treat parameters as const |
| // BUG: need to disallow returns and throws |
| // BUG: verify that all in and ref parameters are read |
| freq = ::semantic(freq, sc2); |
| blockExit(freq, this, false); |
| |
| sc2 = sc2->pop(); |
| |
| if (!global.params.useIn) |
| freq = NULL; |
| } |
| |
| if (fens) |
| { |
| /* fensure is composed of the [out] contracts |
| */ |
| if (f->next->ty == Tvoid && outId) |
| error("void functions have no result"); |
| |
| sc2 = scout; //push |
| sc2->flags = (sc2->flags & ~SCOPEcontract) | SCOPEensure; |
| |
| // BUG: need to treat parameters as const |
| // BUG: need to disallow returns and throws |
| if (fensure && f->next->ty != Tvoid) |
| buildResultVar(scout, f->next); |
| |
| fens = ::semantic(fens, sc2); |
| blockExit(fens, this, false); |
| |
| sc2 = sc2->pop(); |
| |
| if (!global.params.useOut) |
| fens = NULL; |
| } |
| |
| if (fbody && fbody->isErrorStatement()) |
| ; |
| else |
| { |
| Statements *a = new Statements(); |
| |
| // Merge in initialization of 'out' parameters |
| if (parameters) |
| { |
| for (size_t i = 0; i < parameters->dim; i++) |
| { |
| VarDeclaration *v = (*parameters)[i]; |
| if (v->storage_class & STCout) |
| { |
| assert(v->_init); |
| ExpInitializer *ie = v->_init->isExpInitializer(); |
| assert(ie); |
| if (ie->exp->op == TOKconstruct) |
| ie->exp->op = TOKassign; // construction occured in parameter processing |
| a->push(new ExpStatement(Loc(), ie->exp)); |
| } |
| } |
| } |
| |
| if (v_argptr) |
| { |
| // Handled in FuncDeclaration::toObjFile |
| v_argptr->_init = new VoidInitializer(loc); |
| } |
| |
| if (_arguments) |
| { |
| /* Advance to elements[] member of TypeInfo_Tuple with: |
| * _arguments = v_arguments.elements; |
| */ |
| Expression *e = new VarExp(Loc(), v_arguments); |
| e = new DotIdExp(Loc(), e, Id::elements); |
| e = new ConstructExp(Loc(), _arguments, e); |
| e = ::semantic(e, sc2); |
| |
| _arguments->_init = new ExpInitializer(Loc(), e); |
| DeclarationExp *de = new DeclarationExp(Loc(), _arguments); |
| a->push(new ExpStatement(Loc(), de)); |
| } |
| |
| // Merge contracts together with body into one compound statement |
| |
| if (freq || fpreinv) |
| { |
| if (!freq) |
| freq = fpreinv; |
| else if (fpreinv) |
| freq = new CompoundStatement(Loc(), freq, fpreinv); |
| |
| a->push(freq); |
| } |
| |
| if (fbody) |
| a->push(fbody); |
| |
| if (fens || fpostinv) |
| { |
| if (!fens) |
| fens = fpostinv; |
| else if (fpostinv) |
| fens = new CompoundStatement(Loc(), fpostinv, fens); |
| |
| LabelStatement *ls = new LabelStatement(Loc(), Id::returnLabel, fens); |
| returnLabel->statement = ls; |
| a->push(returnLabel->statement); |
| |
| if (f->next->ty != Tvoid && vresult) |
| { |
| // Create: return vresult; |
| Expression *e = new VarExp(Loc(), vresult); |
| if (tintro) |
| { |
| e = e->implicitCastTo(sc, tintro->nextOf()); |
| e = ::semantic(e, sc); |
| } |
| ReturnStatement *s = new ReturnStatement(Loc(), e); |
| a->push(s); |
| } |
| } |
| if (addReturn0(this)) |
| { |
| // Add a return 0; statement |
| Statement *s = new ReturnStatement(Loc(), new IntegerExp(0)); |
| a->push(s); |
| } |
| |
| Statement *sbody = new CompoundStatement(Loc(), a); |
| /* Append destructor calls for parameters as finally blocks. |
| */ |
| if (parameters) |
| { |
| for (size_t i = 0; i < parameters->dim; i++) |
| { |
| VarDeclaration *v = (*parameters)[i]; |
| |
| if (v->storage_class & (STCref | STCout | STClazy)) |
| continue; |
| |
| if (v->needsScopeDtor()) |
| { |
| // same with ExpStatement.scopeCode() |
| Statement *s = new DtorExpStatement(Loc(), v->edtor, v); |
| v->storage_class |= STCnodtor; |
| |
| s = ::semantic(s, sc2); |
| |
| bool isnothrow = f->isnothrow & !(flags & FUNCFLAGnothrowInprocess); |
| int blockexit = blockExit(s, this, isnothrow); |
| if (f->isnothrow && isnothrow && blockexit & BEthrow) |
| ::error(loc, "nothrow %s '%s' may throw", kind(), toPrettyChars()); |
| if (flags & FUNCFLAGnothrowInprocess && blockexit & BEthrow) |
| f->isnothrow = false; |
| if (blockExit(sbody, this, f->isnothrow) == BEfallthru) |
| sbody = new CompoundStatement(Loc(), sbody, s); |
| else |
| sbody = new TryFinallyStatement(Loc(), sbody, s); |
| } |
| } |
| } |
| // from this point on all possible 'throwers' are checked |
| flags &= ~FUNCFLAGnothrowInprocess; |
| |
| if (isSynchronized()) |
| { |
| /* Wrap the entire function body in a synchronized statement |
| */ |
| ClassDeclaration *cd = isThis() ? isThis()->isClassDeclaration() : parent->isClassDeclaration(); |
| |
| if (cd) |
| { |
| if (!global.params.is64bit && |
| global.params.isWindows && |
| !isStatic() && !sbody->usesEH() && !global.params.trace) |
| { |
| /* The back end uses the "jmonitor" hack for syncing; |
| * no need to do the sync at this level. |
| */ |
| } |
| else |
| { |
| Expression *vsync; |
| if (isStatic()) |
| { |
| // The monitor is in the ClassInfo |
| vsync = new DotIdExp(loc, resolve(loc, sc2, cd, false), Id::classinfo); |
| } |
| else |
| { |
| // 'this' is the monitor |
| vsync = new VarExp(loc, vthis); |
| } |
| sbody = new PeelStatement(sbody); // don't redo semantic() |
| sbody = new SynchronizedStatement(loc, vsync, sbody); |
| sbody = ::semantic(sbody, sc2); |
| } |
| } |
| else |
| { |
| error("synchronized function %s must be a member of a class", toChars()); |
| } |
| } |
| |
| // If declaration has no body, don't set sbody to prevent incorrect codegen. |
| InterfaceDeclaration *id = parent->isInterfaceDeclaration(); |
| if (fbody || (id && (fdensure || fdrequire) && isVirtual())) |
| fbody = sbody; |
| } |
| |
| // Fix up forward-referenced gotos |
| if (gotos) |
| { |
| for (size_t i = 0; i < gotos->dim; ++i) |
| { |
| (*gotos)[i]->checkLabel(); |
| } |
| } |
| |
| if (naked && (fensure || frequire)) |
| error("naked assembly functions with contracts are not supported"); |
| |
| sc2->callSuper = 0; |
| sc2->pop(); |
| } |
| |
| if (checkClosure()) |
| { |
| // We should be setting errors here instead of relying on the global error count. |
| //errors = true; |
| } |
| |
| /* If function survived being marked as impure, then it is pure |
| */ |
| if (flags & FUNCFLAGpurityInprocess) |
| { |
| flags &= ~FUNCFLAGpurityInprocess; |
| if (type == f) |
| f = (TypeFunction *)f->copy(); |
| f->purity = PUREfwdref; |
| } |
| |
| if (flags & FUNCFLAGsafetyInprocess) |
| { |
| flags &= ~FUNCFLAGsafetyInprocess; |
| if (type == f) |
| f = (TypeFunction *)f->copy(); |
| f->trust = TRUSTsafe; |
| } |
| |
| if (flags & FUNCFLAGnogcInprocess) |
| { |
| flags &= ~FUNCFLAGnogcInprocess; |
| if (type == f) |
| f = (TypeFunction *)f->copy(); |
| f->isnogc = true; |
| } |
| |
| if (flags & FUNCFLAGreturnInprocess) |
| { |
| flags &= ~FUNCFLAGreturnInprocess; |
| if (storage_class & STCreturn) |
| { |
| if (type == f) |
| f = (TypeFunction *)f->copy(); |
| f->isreturn = true; |
| } |
| } |
| |
| flags &= ~FUNCFLAGinferScope; |
| |
| // Infer STCscope |
| if (parameters) |
| { |
| size_t nfparams = Parameter::dim(f->parameters); |
| assert(nfparams == parameters->dim); |
| for (size_t u = 0; u < parameters->dim; u++) |
| { |
| VarDeclaration *v = (*parameters)[u]; |
| if (v->storage_class & STCmaybescope) |
| { |
| //printf("Inferring scope for %s\n", v->toChars()); |
| Parameter *p = Parameter::getNth(f->parameters, u); |
| v->storage_class &= ~STCmaybescope; |
| v->storage_class |= STCscope | STCscopeinferred; |
| p->storageClass |= STCscope | STCscopeinferred; |
| assert(!(p->storageClass & STCmaybescope)); |
| } |
| } |
| } |
| |
| if (vthis && vthis->storage_class & STCmaybescope) |
| { |
| vthis->storage_class &= ~STCmaybescope; |
| vthis->storage_class |= STCscope | STCscopeinferred; |
| f->isscope = true; |
| f->isscopeinferred = true; |
| } |
| |
| // reset deco to apply inference result to mangled name |
| if (f != type) |
| f->deco = NULL; |
| |
| // Do semantic type AFTER pure/nothrow inference. |
| if (!f->deco && ident != Id::xopEquals && ident != Id::xopCmp) |
| { |
| sc = sc->push(); |
| if (isCtorDeclaration()) // Bugzilla #15665 |
| sc->flags |= SCOPEctor; |
| sc->stc = 0; |
| sc->linkage = linkage; // Bugzilla 8496 |
| type = f->semantic(loc, sc); |
| sc = sc->pop(); |
| } |
| |
| /* If this function had instantiated with gagging, error reproduction will be |
| * done by TemplateInstance::semantic. |
| * Otherwise, error gagging should be temporarily ungagged by functionSemantic3. |
| */ |
| semanticRun = PASSsemantic3done; |
| semantic3Errors = (global.errors != oldErrors) || (fbody && fbody->isErrorStatement()); |
| if (type->ty == Terror) |
| errors = true; |
| //printf("-FuncDeclaration::semantic3('%s.%s', sc = %p, loc = %s)\n", parent->toChars(), toChars(), sc, loc.toChars()); |
| //fflush(stdout); |
| } |
| |
| /**************************************************** |
| * Resolve forward reference of function signature - |
| * parameter types, return type, and attributes. |
| * Returns false if any errors exist in the signature. |
| */ |
| bool FuncDeclaration::functionSemantic() |
| { |
| if (!_scope) |
| return !errors; |
| |
| if (!originalType) // semantic not yet run |
| { |
| TemplateInstance *spec = isSpeculative(); |
| unsigned olderrs = global.errors; |
| unsigned oldgag = global.gag; |
| if (global.gag && !spec) |
| global.gag = 0; |
| semantic(_scope); |
| global.gag = oldgag; |
| if (spec && global.errors != olderrs) |
| spec->errors = (global.errors - olderrs != 0); |
| if (olderrs != global.errors) // if errors compiling this function |
| return false; |
| } |
| |
| // if inferring return type, sematic3 needs to be run |
| // - When the function body contains any errors, we cannot assume |
| // the inferred return type is valid. |
| // So, the body errors should become the function signature error. |
| if (inferRetType && type && !type->nextOf()) |
| return functionSemantic3(); |
| |
| TemplateInstance *ti; |
| if (isInstantiated() && !isVirtualMethod() && |
| ((ti = parent->isTemplateInstance()) == NULL || ti->isTemplateMixin() || ti->tempdecl->ident == ident)) |
| { |
| AggregateDeclaration *ad = isMember2(); |
| if (ad && ad->sizeok != SIZEOKdone) |
| { |
| /* Currently dmd cannot resolve forward references per methods, |
| * then setting SIZOKfwd is too conservative and would break existing code. |
| * So, just stop method attributes inference until ad->semantic() done. |
| */ |
| //ad->sizeok = SIZEOKfwd; |
| } |
| else |
| return functionSemantic3() || !errors; |
| } |
| |
| if (storage_class & STCinference) |
| return functionSemantic3() || !errors; |
| |
| return !errors; |
| } |
| |
| /**************************************************** |
| * Resolve forward reference of function body. |
| * Returns false if any errors exist in the body. |
| */ |
| bool FuncDeclaration::functionSemantic3() |
| { |
| if (semanticRun < PASSsemantic3 && _scope) |
| { |
| /* Forward reference - we need to run semantic3 on this function. |
| * If errors are gagged, and it's not part of a template instance, |
| * we need to temporarily ungag errors. |
| */ |
| TemplateInstance *spec = isSpeculative(); |
| unsigned olderrs = global.errors; |
| unsigned oldgag = global.gag; |
| if (global.gag && !spec) |
| global.gag = 0; |
| semantic3(_scope); |
| global.gag = oldgag; |
| |
| // If it is a speculatively-instantiated template, and errors occur, |
| // we need to mark the template as having errors. |
| if (spec && global.errors != olderrs) |
| spec->errors = (global.errors - olderrs != 0); |
| if (olderrs != global.errors) // if errors compiling this function |
| return false; |
| } |
| |
| return !errors && !semantic3Errors; |
| } |
| |
| /**************************************************** |
| * Check that this function type is properly resolved. |
| * If not, report "forward reference error" and return true. |
| */ |
| bool FuncDeclaration::checkForwardRef(Loc loc) |
| { |
| if (!functionSemantic()) |
| return true; |
| |
| /* No deco means the functionSemantic() call could not resolve |
| * forward referenes in the type of this function. |
| */ |
| if (!type->deco) |
| { |
| bool inSemantic3 = (inferRetType && semanticRun >= PASSsemantic3); |
| ::error(loc, "forward reference to %s'%s'", |
| (inSemantic3 ? "inferred return type of function " : ""), |
| toChars()); |
| return true; |
| } |
| return false; |
| } |
| |
| VarDeclaration *FuncDeclaration::declareThis(Scope *sc, AggregateDeclaration *ad) |
| { |
| if (ad) |
| { |
| VarDeclaration *v; |
| { |
| //printf("declareThis() %s\n", toChars()); |
| Type *thandle = ad->handleType(); |
| assert(thandle); |
| thandle = thandle->addMod(type->mod); |
| thandle = thandle->addStorageClass(storage_class); |
| v = new ThisDeclaration(loc, thandle); |
| v->storage_class |= STCparameter; |
| if (thandle->ty == Tstruct) |
| { |
| v->storage_class |= STCref; |
| |
| // if member function is marked 'inout', then 'this' is 'return ref' |
| if (type->ty == Tfunction && ((TypeFunction *)type)->iswild & 2) |
| v->storage_class |= STCreturn; |
| } |
| if (type->ty == Tfunction) |
| { |
| TypeFunction *tf = (TypeFunction *)type; |
| if (tf->isreturn) |
| v->storage_class |= STCreturn; |
| if (tf->isscope) |
| v->storage_class |= STCscope; |
| } |
| if (flags & FUNCFLAGinferScope && !(v->storage_class & STCscope)) |
| v->storage_class |= STCmaybescope; |
| |
| v->semantic(sc); |
| if (!sc->insert(v)) |
| assert(0); |
| v->parent = this; |
| return v; |
| } |
| } |
| else if (isNested()) |
| { |
| /* The 'this' for a nested function is the link to the |
| * enclosing function's stack frame. |
| * Note that nested functions and member functions are disjoint. |
| */ |
| VarDeclaration *v = new ThisDeclaration(loc, Type::tvoid->pointerTo()); |
| v->storage_class |= STCparameter; |
| if (type->ty == Tfunction) |
| { |
| TypeFunction *tf = (TypeFunction *)type; |
| if (tf->isreturn) |
| v->storage_class |= STCreturn; |
| if (tf->isscope) |
| v->storage_class |= STCscope; |
| } |
| if (flags & FUNCFLAGinferScope && !(v->storage_class & STCscope)) |
| v->storage_class |= STCmaybescope; |
| |
| v->semantic(sc); |
| if (!sc->insert(v)) |
| assert(0); |
| v->parent = this; |
| return v; |
| } |
| |
| return NULL; |
| } |
| |
| bool FuncDeclaration::equals(RootObject *o) |
| { |
| if (this == o) |
| return true; |
| |
| Dsymbol *s = isDsymbol(o); |
| if (s) |
| { |
| FuncDeclaration *fd1 = this; |
| FuncDeclaration *fd2 = s->isFuncDeclaration(); |
| if (!fd2) |
| return false; |
| |
| FuncAliasDeclaration *fa1 = fd1->isFuncAliasDeclaration(); |
| FuncAliasDeclaration *fa2 = fd2->isFuncAliasDeclaration(); |
| if (fa1 && fa2) |
| { |
| return fa1->toAliasFunc()->equals(fa2->toAliasFunc()) && |
| fa1->hasOverloads == fa2->hasOverloads; |
| } |
| |
| if (fa1 && (fd1 = fa1->toAliasFunc())->isUnique() && !fa1->hasOverloads) |
| fa1 = NULL; |
| if (fa2 && (fd2 = fa2->toAliasFunc())->isUnique() && !fa2->hasOverloads) |
| fa2 = NULL; |
| if ((fa1 != NULL) != (fa2 != NULL)) |
| return false; |
| |
| return fd1->toParent()->equals(fd2->toParent()) && |
| fd1->ident->equals(fd2->ident) && fd1->type->equals(fd2->type); |
| } |
| return false; |
| } |
| |
| /**************************************************** |
| * Declare result variable lazily. |
| */ |
| |
| void FuncDeclaration::buildResultVar(Scope *sc, Type *tret) |
| { |
| if (!vresult) |
| { |
| Loc loc = fensure ? fensure->loc : this->loc; |
| |
| /* If inferRetType is true, tret may not be a correct return type yet. |
| * 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; |
| if (!isVirtual()) |
| vresult->storage_class |= STCconst; |
| vresult->storage_class |= STCresult; |
| |
| // set before the semantic() for checkNestedReference() |
| vresult->parent = this; |
| } |
| |
| if (sc && vresult->semanticRun == PASSinit) |
| { |
| TypeFunction *tf = type->toTypeFunction(); |
| if (tf->isref) |
| vresult->storage_class |= STCref; |
| vresult->type = tret; |
| |
| vresult->semantic(sc); |
| |
| if (!sc->insert(vresult)) |
| error("out result %s is already defined", vresult->toChars()); |
| assert(vresult->parent == this); |
| } |
| } |
| |
| /**************************************************** |
| * Merge into this function the 'in' contracts of all it overrides. |
| * 'in's are OR'd together, i.e. only one of them needs to pass. |
| */ |
| |
| Statement *FuncDeclaration::mergeFrequire(Statement *sf) |
| { |
| /* If a base function and its override both have an IN contract, then |
| * only one of them needs to succeed. This is done by generating: |
| * |
| * void derived.in() { |
| * try { |
| * base.in(); |
| * } |
| * catch () { |
| * ... body of derived.in() ... |
| * } |
| * } |
| * |
| * So if base.in() doesn't throw, derived.in() need not be executed, and the contract is valid. |
| * If base.in() throws, then derived.in()'s body is executed. |
| */ |
| |
| /* Implementing this is done by having the overriding function call |
| * nested functions (the fdrequire functions) nested inside the overridden |
| * function. This requires that the stack layout of the calling function's |
| * parameters and 'this' pointer be in the same place (as the nested |
| * function refers to them). |
| * This is easy for the parameters, as they are all on the stack in the same |
| * place by definition, since it's an overriding function. The problem is |
| * getting the 'this' pointer in the same place, since it is a local variable. |
| * We did some hacks in the code generator to make this happen: |
| * 1. always generate exception handler frame, or at least leave space for it |
| * in the frame (Windows 32 SEH only) |
| * 2. always generate an EBP style frame |
| * 3. since 'this' is passed in a register that is subsequently copied into |
| * a stack local, allocate that local immediately following the exception |
| * handler block, so it is always at the same offset from EBP. |
| */ |
| for (size_t i = 0; i < foverrides.dim; i++) |
| { |
| FuncDeclaration *fdv = foverrides[i]; |
| |
| /* The semantic pass on the contracts of the overridden functions must |
| * be completed before code generation occurs. |
| * https://issues.dlang.org/show_bug.cgi?id=3602 |
| */ |
| if (fdv->frequire && fdv->semanticRun != PASSsemantic3done) |
| { |
| assert(fdv->_scope); |
| Scope *sc = fdv->_scope->push(); |
| sc->stc &= ~STCoverride; |
| fdv->semantic3(sc); |
| sc->pop(); |
| } |
| |
| sf = fdv->mergeFrequire(sf); |
| if (sf && fdv->fdrequire) |
| { |
| //printf("fdv->frequire: %s\n", fdv->frequire->toChars()); |
| /* Make the call: |
| * try { __require(); } |
| * catch (Throwable) { frequire; } |
| */ |
| Expression *eresult = NULL; |
| Expression *e = new CallExp(loc, new VarExp(loc, fdv->fdrequire, false), eresult); |
| Statement *s2 = new ExpStatement(loc, e); |
| |
| Catch *c = new Catch(loc, getThrowable(), NULL, sf); |
| c->internalCatch = true; |
| Catches *catches = new Catches(); |
| catches->push(c); |
| sf = new TryCatchStatement(loc, s2, catches); |
| } |
| else |
| return NULL; |
| } |
| return sf; |
| } |
| |
| /**************************************************** |
| * Merge into this function the 'out' contracts of all it overrides. |
| * 'out's are AND'd together, i.e. all of them need to pass. |
| */ |
| |
| Statement *FuncDeclaration::mergeFensure(Statement *sf, Identifier *oid) |
| { |
| /* Same comments as for mergeFrequire(), except that we take care |
| * of generating a consistent reference to the 'result' local by |
| * explicitly passing 'result' to the nested function as a reference |
| * argument. |
| * This won't work for the 'this' parameter as it would require changing |
| * the semantic code for the nested function so that it looks on the parameter |
| * list for the 'this' pointer, something that would need an unknown amount |
| * of tweaking of various parts of the compiler that I'd rather leave alone. |
| */ |
| for (size_t i = 0; i < foverrides.dim; i++) |
| { |
| FuncDeclaration *fdv = foverrides[i]; |
| |
| /* The semantic pass on the contracts of the overridden functions must |
| * be completed before code generation occurs. |
| * https://issues.dlang.org/show_bug.cgi?id=3602 and |
| * https://issues.dlang.org/show_bug.cgi?id=5230 |
| */ |
| if (needsFensure(fdv) && fdv->semanticRun != PASSsemantic3done) |
| { |
| assert(fdv->_scope); |
| Scope *sc = fdv->_scope->push(); |
| sc->stc &= ~STCoverride; |
| fdv->semantic3(sc); |
| sc->pop(); |
| } |
| |
| sf = fdv->mergeFensure(sf, oid); |
| if (fdv->fdensure) |
| { |
| //printf("fdv->fensure: %s\n", fdv->fensure->toChars()); |
| // Make the call: __ensure(result) |
| Expression *eresult = NULL; |
| if (outId) |
| { |
| eresult = new IdentifierExp(loc, oid); |
| |
| Type *t1 = fdv->type->nextOf()->toBasetype(); |
| Type *t2 = this->type->nextOf()->toBasetype(); |
| if (t1->isBaseOf(t2, NULL)) |
| { |
| /* Making temporary reference variable is necessary |
| * in covariant return. |
| * See bugzilla 5204 and 10479. |
| */ |
| ExpInitializer *ei = new ExpInitializer(Loc(), eresult); |
| VarDeclaration *v = new VarDeclaration(Loc(), t1, Identifier::generateId("__covres"), ei); |
| v->storage_class |= STCtemp; |
| DeclarationExp *de = new DeclarationExp(Loc(), v); |
| VarExp *ve = new VarExp(Loc(), v); |
| eresult = new CommaExp(Loc(), de, ve); |
| } |
| } |
| Expression *e = new CallExp(loc, new VarExp(loc, fdv->fdensure, false), eresult); |
| Statement *s2 = new ExpStatement(loc, e); |
| |
| if (sf) |
| { |
| sf = new CompoundStatement(sf->loc, s2, sf); |
| } |
| else |
| sf = s2; |
| } |
| } |
| return sf; |
| } |
| |
| /**************************************************** |
| * Determine if 'this' overrides fd. |
| * Return !=0 if it does. |
| */ |
| |
| int FuncDeclaration::overrides(FuncDeclaration *fd) |
| { int result = 0; |
| |
| if (fd->ident == ident) |
| { |
| int cov = type->covariant(fd->type); |
| if (cov) |
| { ClassDeclaration *cd1 = toParent()->isClassDeclaration(); |
| ClassDeclaration *cd2 = fd->toParent()->isClassDeclaration(); |
| |
| if (cd1 && cd2 && cd2->isBaseOf(cd1, NULL)) |
| result = 1; |
| } |
| } |
| return result; |
| } |
| |
| /************************************************* |
| * Find index of function in vtbl[0..dim] that |
| * this function overrides. |
| * Prefer an exact match to a covariant one. |
| * Params: |
| * fix17349 = enable fix https://issues.dlang.org/show_bug.cgi?id=17349 |
| * Returns: |
| * -1 didn't find one |
| * -2 can't determine because of forward references |
| */ |
| |
| int FuncDeclaration::findVtblIndex(Dsymbols *vtbl, int dim, bool fix17349) |
| { |
| //printf("findVtblIndex() %s\n", toChars()); |
| FuncDeclaration *mismatch = NULL; |
| StorageClass mismatchstc = 0; |
| int mismatchvi = -1; |
| int exactvi = -1; |
| int bestvi = -1; |
| for (int vi = 0; vi < dim; vi++) |
| { |
| FuncDeclaration *fdv = (*vtbl)[vi]->isFuncDeclaration(); |
| if (fdv && fdv->ident == ident) |
| { |
| if (type->equals(fdv->type)) // if exact match |
| { |
| if (fdv->parent->isClassDeclaration()) |
| { |
| if (fdv->isFuture()) |
| { |
| bestvi = vi; |
| continue; // keep looking |
| } |
| return vi; // no need to look further |
| } |
| |
| if (exactvi >= 0) |
| { |
| error("cannot determine overridden function"); |
| return exactvi; |
| } |
| exactvi = vi; |
| |
| bestvi = vi; |
| continue; |
| } |
| |
| StorageClass stc = 0; |
| int cov = type->covariant(fdv->type, &stc, fix17349); |
| //printf("\tbaseclass cov = %d\n", cov); |
| switch (cov) |
| { |
| case 0: // types are distinct |
| break; |
| |
| case 1: |
| bestvi = vi; // covariant, but not identical |
| break; // keep looking for an exact match |
| |
| case 2: |
| mismatchvi = vi; |
| mismatchstc = stc; |
| mismatch = fdv; // overrides, but is not covariant |
| break; // keep looking for an exact match |
| |
| case 3: |
| return -2; // forward references |
| |
| default: |
| assert(0); |
| } |
| } |
| } |
| if (bestvi == -1 && mismatch) |
| { |
| //type->print(); |
| //mismatch->type->print(); |
| //printf("%s %s\n", type->deco, mismatch->type->deco); |
| //printf("stc = %llx\n", mismatchstc); |
| if (mismatchstc) |
| { // Fix it by modifying the type to add the storage classes |
| type = type->addStorageClass(mismatchstc |