/* 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/scope.c */ #include "root/dsystem.h" // strlen() #include "root/root.h" #include "root/rmem.h" #include "root/speller.h" #include "mars.h" #include "init.h" #include "identifier.h" #include "scope.h" #include "attrib.h" #include "dsymbol.h" #include "declaration.h" #include "statement.h" #include "aggregate.h" #include "module.h" #include "id.h" #include "template.h" Scope *Scope::freelist = NULL; void allocFieldinit(Scope *sc, size_t dim) { sc->fieldinit = (unsigned *)mem.xcalloc(sizeof(unsigned), dim); sc->fieldinit_dim = dim; } void freeFieldinit(Scope *sc) { if (sc->fieldinit) mem.xfree(sc->fieldinit); sc->fieldinit = NULL; sc->fieldinit_dim = 0; } Scope *Scope::alloc() { if (freelist) { Scope *s = freelist; freelist = s->enclosing; //printf("freelist %p\n", s); assert(s->flags & SCOPEfree); s->flags &= ~SCOPEfree; return s; } return new Scope(); } Scope::Scope() { // Create root scope //printf("Scope::Scope() %p\n", this); this->_module = NULL; this->scopesym = NULL; this->sds = NULL; this->enclosing = NULL; this->parent = NULL; this->sw = NULL; this->tf = NULL; this->os = NULL; this->tinst = NULL; this->minst = NULL; this->sbreak = NULL; this->scontinue = NULL; this->fes = NULL; this->callsc = NULL; this->aligndecl = NULL; this->func = NULL; this->slabel = NULL; this->linkage = LINKd; this->cppmangle = CPPMANGLEdefault; this->inlining = PINLINEdefault; this->protection = Prot(PROTpublic); this->explicitProtection = 0; this->stc = 0; this->depdecl = NULL; this->inunion = 0; this->nofree = 0; this->noctor = 0; this->intypeof = 0; this->lastVar = NULL; this->callSuper = 0; this->fieldinit = NULL; this->fieldinit_dim = 0; this->flags = 0; this->lastdc = NULL; this->anchorCounts = NULL; this->prevAnchor = NULL; this->userAttribDecl = NULL; } Scope *Scope::copy() { Scope *sc = Scope::alloc(); *sc = *this; // memcpy /* Bugzilla 11777: The copied scope should not inherit fieldinit. */ sc->fieldinit = NULL; return sc; } Scope *Scope::createGlobal(Module *_module) { Scope *sc = Scope::alloc(); *sc = Scope(); // memset sc->aligndecl = NULL; sc->linkage = LINKd; sc->inlining = PINLINEdefault; sc->protection = Prot(PROTpublic); sc->_module = _module; sc->tinst = NULL; sc->minst = _module; sc->scopesym = new ScopeDsymbol(); sc->scopesym->symtab = new DsymbolTable(); // Add top level package as member of this global scope Dsymbol *m = _module; while (m->parent) m = m->parent; m->addMember(NULL, sc->scopesym); m->parent = NULL; // got changed by addMember() // Create the module scope underneath the global scope sc = sc->push(_module); sc->parent = _module; return sc; } Scope *Scope::push() { Scope *s = copy(); //printf("Scope::push(this = %p) new = %p\n", this, s); assert(!(flags & SCOPEfree)); s->scopesym = NULL; s->sds = NULL; s->enclosing = this; s->slabel = NULL; s->nofree = 0; s->fieldinit = saveFieldInit(); s->flags = (flags & (SCOPEcontract | SCOPEdebug | SCOPEctfe | SCOPEcompile | SCOPEconstraint | SCOPEnoaccesscheck | SCOPEignoresymbolvisibility)); s->lastdc = NULL; assert(this != s); return s; } Scope *Scope::push(ScopeDsymbol *ss) { //printf("Scope::push(%s)\n", ss->toChars()); Scope *s = push(); s->scopesym = ss; return s; } Scope *Scope::pop() { //printf("Scope::pop() %p nofree = %d\n", this, nofree); Scope *enc = enclosing; if (enclosing) { enclosing->callSuper |= callSuper; if (fieldinit) { if (enclosing->fieldinit) { assert(fieldinit != enclosing->fieldinit); size_t dim = fieldinit_dim; for (size_t i = 0; i < dim; i++) enclosing->fieldinit[i] |= fieldinit[i]; } freeFieldinit(this); } } if (!nofree) { enclosing = freelist; freelist = this; flags |= SCOPEfree; } return enc; } Scope *Scope::startCTFE() { Scope *sc = this->push(); sc->flags = this->flags | SCOPEctfe; return sc; } Scope *Scope::endCTFE() { assert(flags & SCOPEctfe); return pop(); } void Scope::mergeCallSuper(Loc loc, unsigned cs) { // This does a primitive flow analysis to support the restrictions // regarding when and how constructors can appear. // It merges the results of two paths. // The two paths are callSuper and cs; the result is merged into callSuper. if (cs != callSuper) { // Have ALL branches called a constructor? int aAll = (cs & (CSXthis_ctor | CSXsuper_ctor)) != 0; int bAll = (callSuper & (CSXthis_ctor | CSXsuper_ctor)) != 0; // Have ANY branches called a constructor? bool aAny = (cs & CSXany_ctor) != 0; bool bAny = (callSuper & CSXany_ctor) != 0; // Have any branches returned? bool aRet = (cs & CSXreturn) != 0; bool bRet = (callSuper & CSXreturn) != 0; // Have any branches halted? bool aHalt = (cs & CSXhalt) != 0; bool bHalt = (callSuper & CSXhalt) != 0; bool ok = true; if (aHalt && bHalt) { callSuper = CSXhalt; } else if ((!aHalt && aRet && !aAny && bAny) || (!bHalt && bRet && !bAny && aAny)) { // If one has returned without a constructor call, there must be never // have been ctor calls in the other. ok = false; } else if (aHalt || (aRet && aAll)) { // If one branch has called a ctor and then exited, anything the // other branch has done is OK (except returning without a // ctor call, but we already checked that). callSuper |= cs & (CSXany_ctor | CSXlabel); } else if (bHalt || (bRet && bAll)) { callSuper = cs | (callSuper & (CSXany_ctor | CSXlabel)); } else { // Both branches must have called ctors, or both not. ok = (aAll == bAll); // If one returned without a ctor, we must remember that // (Don't bother if we've already found an error) if (ok && aRet && !aAny) callSuper |= CSXreturn; callSuper |= cs & (CSXany_ctor | CSXlabel); } if (!ok) error(loc, "one path skips constructor"); } } unsigned *Scope::saveFieldInit() { unsigned *fi = NULL; if (fieldinit) // copy { size_t dim = fieldinit_dim; fi = (unsigned *)mem.xmalloc(sizeof(unsigned) * dim); for (size_t i = 0; i < dim; i++) fi[i] = fieldinit[i]; } return fi; } static bool mergeFieldInit(unsigned &fieldInit, unsigned fi, bool mustInit) { if (fi != fieldInit) { // Have any branches returned? bool aRet = (fi & CSXreturn) != 0; bool bRet = (fieldInit & CSXreturn) != 0; // Have any branches halted? bool aHalt = (fi & CSXhalt) != 0; bool bHalt = (fieldInit & CSXhalt) != 0; bool ok; if (aHalt && bHalt) { ok = true; fieldInit = CSXhalt; } else if (!aHalt && aRet) { ok = !mustInit || (fi & CSXthis_ctor); fieldInit = fieldInit; } else if (!bHalt && bRet) { ok = !mustInit || (fieldInit & CSXthis_ctor); fieldInit = fi; } else if (aHalt) { ok = !mustInit || (fieldInit & CSXthis_ctor); fieldInit = fieldInit; } else if (bHalt) { ok = !mustInit || (fi & CSXthis_ctor); fieldInit = fi; } else { ok = !mustInit || !((fieldInit ^ fi) & CSXthis_ctor); fieldInit |= fi; } return ok; } return true; } void Scope::mergeFieldInit(Loc loc, unsigned *fies) { if (fieldinit && fies) { FuncDeclaration *f = func; if (fes) f = fes->func; AggregateDeclaration *ad = f->isMember2(); assert(ad); for (size_t i = 0; i < ad->fields.dim; i++) { VarDeclaration *v = ad->fields[i]; bool mustInit = (v->storage_class & STCnodefaultctor || v->type->needsNested()); if (!::mergeFieldInit(fieldinit[i], fies[i], mustInit)) { ::error(loc, "one path skips field %s", ad->fields[i]->toChars()); } } } } Module *Scope::instantiatingModule() { // TODO: in speculative context, returning 'module' is correct? return minst ? minst : _module; } static Dsymbol *searchScopes(Scope *scope, Loc loc, Identifier *ident, Dsymbol **pscopesym, int flags) { for (Scope *sc = scope; sc; sc = sc->enclosing) { assert(sc != sc->enclosing); if (!sc->scopesym) continue; //printf("\tlooking in scopesym '%s', kind = '%s', flags = x%x\n", sc->scopesym->toChars(), sc->scopesym->kind(), flags); if (sc->scopesym->isModule()) flags |= SearchUnqualifiedModule; // tell Module.search() that SearchLocalsOnly is to be obeyed if (Dsymbol *s = sc->scopesym->search(loc, ident, flags)) { if (!(flags & (SearchImportsOnly | IgnoreErrors)) && ident == Id::length && sc->scopesym->isArrayScopeSymbol() && sc->enclosing && sc->enclosing->search(loc, ident, NULL, flags)) { warning(s->loc, "array 'length' hides other 'length' name in outer scope"); } if (pscopesym) *pscopesym = sc->scopesym; return s; } // Stop when we hit a module, but keep going if that is not just under the global scope if (sc->scopesym->isModule() && !(sc->enclosing && !sc->enclosing->enclosing)) break; } return NULL; } /************************************ * Perform unqualified name lookup by following the chain of scopes up * until found. * * Params: * loc = location to use for error messages * ident = name to look up * pscopesym = if supplied and name is found, set to scope that ident was found in * flags = modify search based on flags * * Returns: * symbol if found, null if not */ Dsymbol *Scope::search(Loc loc, Identifier *ident, Dsymbol **pscopesym, int flags) { // This function is called only for unqualified lookup assert(!(flags & (SearchLocalsOnly | SearchImportsOnly))); /* If ident is "start at module scope", only look at module scope */ if (ident == Id::empty) { // Look for module scope for (Scope *sc = this; sc; sc = sc->enclosing) { assert(sc != sc->enclosing); if (!sc->scopesym) continue; if (Dsymbol *s = sc->scopesym->isModule()) { if (pscopesym) *pscopesym = sc->scopesym; return s; } } return NULL; } if (this->flags & SCOPEignoresymbolvisibility) flags |= IgnoreSymbolVisibility; Dsymbol *sold = NULL; if (global.params.bug10378 || global.params.check10378) { sold = searchScopes(this, loc, ident, pscopesym, flags | IgnoreSymbolVisibility); if (!global.params.check10378) return sold; if (ident == Id::dollar) // Bugzilla 15825 return sold; // Search both ways } // First look in local scopes Dsymbol *s = searchScopes(this, loc, ident, pscopesym, flags | SearchLocalsOnly); if (!s) { // Second look in imported modules s = searchScopes(this, loc, ident, pscopesym, flags | SearchImportsOnly); /** Still find private symbols, so that symbols that weren't access * checked by the compiler remain usable. Once the deprecation is over, * this should be moved to search_correct instead. */ if (!s && !(flags & IgnoreSymbolVisibility)) { s = searchScopes(this, loc, ident, pscopesym, flags | SearchLocalsOnly | IgnoreSymbolVisibility); if (!s) s = searchScopes(this, loc, ident, pscopesym, flags | SearchImportsOnly | IgnoreSymbolVisibility); if (s && !(flags & IgnoreErrors)) ::deprecation(loc, "%s is not visible from module %s", s->toPrettyChars(), _module->toChars()); } } if (global.params.check10378) { Dsymbol *snew = s; if (sold != snew) deprecation10378(loc, sold, snew); if (global.params.bug10378) s = sold; } return s; } Dsymbol *Scope::insert(Dsymbol *s) { if (VarDeclaration *vd = s->isVarDeclaration()) { if (lastVar) vd->lastVar = lastVar; lastVar = vd; } else if (WithScopeSymbol *ss = s->isWithScopeSymbol()) { if (VarDeclaration *vd = ss->withstate->wthis) { if (lastVar) vd->lastVar = lastVar; lastVar = vd; } return NULL; } for (Scope *sc = this; sc; sc = sc->enclosing) { //printf("\tsc = %p\n", sc); if (sc->scopesym) { //printf("\t\tsc->scopesym = %p\n", sc->scopesym); if (!sc->scopesym->symtab) sc->scopesym->symtab = new DsymbolTable(); return sc->scopesym->symtabInsert(s); } } assert(0); return NULL; } /******************************************** * Search enclosing scopes for ClassDeclaration. */ ClassDeclaration *Scope::getClassScope() { for (Scope *sc = this; sc; sc = sc->enclosing) { if (!sc->scopesym) continue; ClassDeclaration *cd = sc->scopesym->isClassDeclaration(); if (cd) return cd; } return NULL; } /******************************************** * Search enclosing scopes for ClassDeclaration. */ AggregateDeclaration *Scope::getStructClassScope() { for (Scope *sc = this; sc; sc = sc->enclosing) { if (!sc->scopesym) continue; AggregateDeclaration *ad = sc->scopesym->isClassDeclaration(); if (ad) return ad; ad = sc->scopesym->isStructDeclaration(); if (ad) return ad; } return NULL; } /******************************************* * For TemplateDeclarations, we need to remember the Scope * where it was declared. So mark the Scope as not * to be free'd. */ void Scope::setNoFree() { //int i = 0; //printf("Scope::setNoFree(this = %p)\n", this); for (Scope *sc = this; sc; sc = sc->enclosing) { //printf("\tsc = %p\n", sc); sc->nofree = 1; assert(!(flags & SCOPEfree)); //assert(sc != sc->enclosing); //assert(!sc->enclosing || sc != sc->enclosing->enclosing); //if (++i == 10) //assert(0); } } structalign_t Scope::alignment() { if (aligndecl) return aligndecl->getAlignment(this); else return STRUCTALIGN_DEFAULT; } /************************************************ * Given the failed search attempt, try to find * one with a close spelling. */ void *scope_search_fp(void *arg, const char *seed, int* cost) { //printf("scope_search_fp('%s')\n", seed); /* If not in the lexer's string table, it certainly isn't in the symbol table. * Doing this first is a lot faster. */ size_t len = strlen(seed); if (!len) return NULL; Identifier *id = Identifier::lookup(seed, len); if (!id) return NULL; Scope *sc = (Scope *)arg; Module::clearCache(); Dsymbol *scopesym = NULL; Dsymbol *s = sc->search(Loc(), id, &scopesym, IgnoreErrors); if (s) { for (*cost = 0; sc; sc = sc->enclosing, (*cost)++) if (sc->scopesym == scopesym) break; if (scopesym != s->parent) { (*cost)++; // got to the symbol through an import if (s->prot().kind == PROTprivate) return NULL; } } return (void*)s; } void Scope::deprecation10378(Loc loc, Dsymbol *sold, Dsymbol *snew) { // Bugzilla 15857 // // The overloadset found via the new lookup rules is either // equal or a subset of the overloadset found via the old // lookup rules, so it suffices to compare the dimension to // check for equality. OverloadSet *osold = NULL; OverloadSet *osnew = NULL; if (sold && (osold = sold->isOverloadSet()) != NULL && snew && (osnew = snew->isOverloadSet()) != NULL && osold->a.dim == osnew->a.dim) return; OutBuffer buf; buf.writestring("local import search method found "); if (osold) buf.printf("%s %s (%d overloads)", sold->kind(), sold->toPrettyChars(), (int)osold->a.dim); else if (sold) buf.printf("%s %s", sold->kind(), sold->toPrettyChars()); else buf.writestring("nothing"); buf.writestring(" instead of "); if (osnew) buf.printf("%s %s (%d overloads)", snew->kind(), snew->toPrettyChars(), (int)osnew->a.dim); else if (snew) buf.printf("%s %s", snew->kind(), snew->toPrettyChars()); else buf.writestring("nothing"); deprecation(loc, "%s", buf.peekString()); } Dsymbol *Scope::search_correct(Identifier *ident) { if (global.gag) return NULL; // don't do it for speculative compiles; too time consuming return (Dsymbol *)speller(ident->toChars(), &scope_search_fp, this, idchars); } /************************************ * Maybe `ident` was a C or C++ name. Check for that, * and suggest the D equivalent. * Params: * ident = unknown identifier * Returns: * D identifier string if found, null if not */ const char *Scope::search_correct_C(Identifier *ident) { TOK tok; if (ident == Id::C_NULL) tok = TOKnull; else if (ident == Id::C_TRUE) tok = TOKtrue; else if (ident == Id::C_FALSE) tok = TOKfalse; else if (ident == Id::C_unsigned) tok = TOKuns32; else if (ident == Id::C_wchar_t) tok = global.params.isWindows ? TOKwchar : TOKdchar; else return NULL; return Token::toChars(tok); }