blob: ab422fd0cf6024d71c02b58ba92478459139075a [file] [log] [blame]
/**
* A scope as defined by curly braces `{}`.
*
* Not to be confused with the `scope` storage class.
*
* Copyright: Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
* Authors: $(LINK2 https://www.digitalmars.com, Walter Bright)
* License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
* Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/dscope.d, _dscope.d)
* Documentation: https://dlang.org/phobos/dmd_dscope.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/dscope.d
*/
module dmd.dscope;
import core.stdc.stdio;
import core.stdc.string;
import dmd.aggregate;
import dmd.arraytypes;
import dmd.astenums;
import dmd.attrib;
import dmd.ctorflow;
import dmd.dclass;
import dmd.declaration;
import dmd.dmodule;
import dmd.doc;
import dmd.dsymbol;
import dmd.dsymbolsem;
import dmd.dtemplate;
import dmd.expression;
import dmd.errors;
import dmd.func;
import dmd.globals;
import dmd.id;
import dmd.identifier;
import dmd.location;
import dmd.common.outbuffer;
import dmd.root.rmem;
import dmd.root.speller;
import dmd.statement;
import dmd.target;
import dmd.tokens;
//version=LOGSEARCH;
// List of flags that can be applied to this `Scope`
enum SCOPE
{
ctor = 0x0001, /// constructor type
noaccesscheck = 0x0002, /// don't do access checks
condition = 0x0004, /// inside static if/assert condition
debug_ = 0x0008, /// inside debug conditional
constraint = 0x0010, /// inside template constraint
invariant_ = 0x0020, /// inside invariant code
require = 0x0040, /// inside in contract code
ensure = 0x0060, /// inside out contract code
contract = 0x0060, /// [mask] we're inside contract code
ctfe = 0x0080, /// inside a ctfe-only expression
compile = 0x0100, /// inside __traits(compile)
ignoresymbolvisibility = 0x0200, /// ignore symbol visibility
/// https://issues.dlang.org/show_bug.cgi?id=15907
Cfile = 0x0800, /// C semantics apply
free = 0x8000, /// is on free list
fullinst = 0x10000, /// fully instantiate templates
ctfeBlock = 0x20000, /// inside a `if (__ctfe)` block
}
/// Flags that are carried along with a scope push()
private enum PersistentFlags =
SCOPE.contract | SCOPE.debug_ | SCOPE.ctfe | SCOPE.compile | SCOPE.constraint |
SCOPE.noaccesscheck | SCOPE.ignoresymbolvisibility |
SCOPE.Cfile | SCOPE.ctfeBlock;
extern (C++) struct Scope
{
Scope* enclosing; /// enclosing Scope
Module _module; /// Root module
ScopeDsymbol scopesym; /// current symbol
FuncDeclaration func; /// function we are in
VarDeclaration varDecl; /// variable we are in during semantic2
Dsymbol parent; /// parent to use
LabelStatement slabel; /// enclosing labelled statement
SwitchStatement sw; /// enclosing switch statement
Statement tryBody; /// enclosing _body of TryCatchStatement or TryFinallyStatement
TryFinallyStatement tf; /// enclosing try finally statement
ScopeGuardStatement os; /// enclosing scope(xxx) statement
Statement sbreak; /// enclosing statement that supports "break"
Statement scontinue; /// enclosing statement that supports "continue"
ForeachStatement fes; /// if nested function for ForeachStatement, this is it
Scope* callsc; /// used for __FUNCTION__, __PRETTY_FUNCTION__ and __MODULE__
Dsymbol inunion; /// != null if processing members of a union
bool nofree; /// true if shouldn't free it
bool inLoop; /// true if inside a loop (where constructor calls aren't allowed)
int intypeof; /// in typeof(exp)
VarDeclaration lastVar; /// Previous symbol used to prevent goto-skips-init
/* If minst && !tinst, it's in definitely non-speculative scope (eg. module member scope).
* If !minst && !tinst, it's in definitely speculative scope (eg. template constraint).
* If minst && tinst, it's in instantiated code scope without speculation.
* If !minst && tinst, it's in instantiated code scope with speculation.
*/
Module minst; /// root module where the instantiated templates should belong to
TemplateInstance tinst; /// enclosing template instance
CtorFlow ctorflow; /// flow analysis for constructors
/// alignment for struct members
AlignDeclaration aligndecl;
/// C++ namespace this symbol is in
CPPNamespaceDeclaration namespace;
/// linkage for external functions
LINK linkage = LINK.d;
/// mangle type
CPPMANGLE cppmangle = CPPMANGLE.def;
/// inlining strategy for functions
PragmaDeclaration inlining;
/// visibility for class members
Visibility visibility = Visibility(Visibility.Kind.public_);
int explicitVisibility; /// set if in an explicit visibility attribute
StorageClass stc; /// storage class
DeprecatedDeclaration depdecl; /// customized deprecation message
uint flags;
// user defined attributes
UserAttributeDeclaration userAttribDecl;
DocComment* lastdc; /// documentation comment for last symbol at this scope
uint[void*] anchorCounts; /// lookup duplicate anchor name count
Identifier prevAnchor; /// qualified symbol name of last doc anchor
AliasDeclaration aliasAsg; /// if set, then aliasAsg is being assigned a new value,
/// do not set wasRead for it
extern (D) __gshared Scope* freelist;
extern (D) static Scope* alloc()
{
if (freelist)
{
Scope* s = freelist;
freelist = s.enclosing;
//printf("freelist %p\n", s);
assert(s.flags & SCOPE.free);
s.flags &= ~SCOPE.free;
return s;
}
return new Scope();
}
extern (D) static Scope* createGlobal(Module _module)
{
Scope* sc = Scope.alloc();
*sc = Scope.init;
sc._module = _module;
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()
if (_module.filetype == FileType.c)
sc.flags |= SCOPE.Cfile;
// Create the module scope underneath the global scope
sc = sc.push(_module);
sc.parent = _module;
return sc;
}
extern (D) Scope* copy()
{
Scope* sc = Scope.alloc();
*sc = this;
/* https://issues.dlang.org/show_bug.cgi?id=11777
* The copied scope should not inherit fieldinit.
*/
sc.ctorflow.fieldinit = null;
return sc;
}
extern (D) Scope* push()
{
Scope* s = copy();
//printf("Scope::push(this = %p) new = %p\n", this, s);
assert(!(flags & SCOPE.free));
s.scopesym = null;
s.enclosing = &this;
debug
{
if (enclosing)
assert(!(enclosing.flags & SCOPE.free));
if (s == enclosing)
{
printf("this = %p, enclosing = %p, enclosing.enclosing = %p\n", s, &this, enclosing);
}
assert(s != enclosing);
}
s.slabel = null;
s.nofree = false;
s.ctorflow.fieldinit = ctorflow.fieldinit.arraydup;
s.flags = (flags & PersistentFlags);
s.lastdc = null;
assert(&this != s);
return s;
}
extern (D) Scope* push(ScopeDsymbol ss)
{
//printf("Scope::push(%s)\n", ss.toChars());
Scope* s = push();
s.scopesym = ss;
return s;
}
extern (D) Scope* pop()
{
//printf("Scope::pop() %p nofree = %d\n", this, nofree);
if (enclosing)
enclosing.ctorflow.OR(ctorflow);
ctorflow.freeFieldinit();
Scope* enc = enclosing;
if (!nofree)
{
if (mem.isGCEnabled)
this = this.init;
enclosing = freelist;
freelist = &this;
flags |= SCOPE.free;
}
return enc;
}
/*************************
* Similar to pop(), but the results in `this` are not folded
* into `enclosing`.
*/
extern (D) void detach()
{
ctorflow.freeFieldinit();
enclosing = null;
pop();
}
extern (D) Scope* startCTFE()
{
Scope* sc = this.push();
sc.flags = this.flags | SCOPE.ctfe;
version (none)
{
/* TODO: Currently this is not possible, because we need to
* unspeculative some types and symbols if they are necessary for the
* final executable. Consider:
*
* struct S(T) {
* string toString() const { return "instantiated"; }
* }
* enum x = S!int();
* void main() {
* // To call x.toString in runtime, compiler should unspeculative S!int.
* assert(x.toString() == "instantiated");
* }
*
* This results in an undefined reference to `RTInfoImpl`:
* class C { int a,b,c; int* p,q; }
* void test() { C c = new C(); }
*/
// If a template is instantiated from CT evaluated expression,
// compiler can elide its code generation.
sc.tinst = null;
sc.minst = null;
}
return sc;
}
extern (D) Scope* endCTFE()
{
assert(flags & SCOPE.ctfe);
return pop();
}
/*******************************
* Merge results of `ctorflow` into `this`.
* Params:
* loc = for error messages
* ctorflow = flow results to merge in
*/
extern (D) void merge(const ref Loc loc, const ref CtorFlow ctorflow)
{
if (!mergeCallSuper(this.ctorflow.callSuper, ctorflow.callSuper))
error(loc, "one path skips constructor");
const fies = ctorflow.fieldinit;
if (this.ctorflow.fieldinit.length && fies.length)
{
FuncDeclaration f = func;
if (fes)
f = fes.func;
auto ad = f.isMemberDecl();
assert(ad);
foreach (i, v; ad.fields)
{
bool mustInit = (v.storage_class & STC.nodefaultctor || v.type.needsNested());
auto fieldInit = &this.ctorflow.fieldinit[i];
const fiesCurrent = fies[i];
if (fieldInit.loc is Loc.init)
fieldInit.loc = fiesCurrent.loc;
if (!mergeFieldInit(this.ctorflow.fieldinit[i].csx, fiesCurrent.csx) && mustInit)
{
error(loc, "one path skips field `%s`", v.toChars());
}
}
}
}
/************************************
* 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
*/
extern (C++) Dsymbol search(const ref Loc loc, Identifier ident, Dsymbol* pscopesym, int flags = IgnoreNone)
{
version (LOGSEARCH)
{
printf("Scope.search(%p, '%s' flags=x%x)\n", &this, ident.toChars(), flags);
// Print scope chain
for (Scope* sc = &this; sc; sc = sc.enclosing)
{
if (!sc.scopesym)
continue;
printf("\tscope %s\n", sc.scopesym.toChars());
}
static void printMsg(string txt, Dsymbol s)
{
printf("%.*s %s.%s, kind = '%s'\n", cast(int)txt.length, txt.ptr,
s.parent ? s.parent.toChars() : "", s.toChars(), s.kind());
}
}
// 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())
{
//printMsg("\tfound", s);
if (pscopesym)
*pscopesym = sc.scopesym;
return s;
}
}
return null;
}
Dsymbol checkAliasThis(AggregateDeclaration ad, Identifier ident, int flags, Expression* exp)
{
import dmd.mtype;
if (!ad || !ad.aliasthis)
return null;
Declaration decl = ad.aliasthis.sym.isDeclaration();
if (!decl)
return null;
Type t = decl.type;
ScopeDsymbol sds;
TypeClass tc;
TypeStruct ts;
switch(t.ty)
{
case Tstruct:
ts = cast(TypeStruct)t;
sds = ts.sym;
break;
case Tclass:
tc = cast(TypeClass)t;
sds = tc.sym;
break;
case Tinstance:
sds = (cast(TypeInstance)t).tempinst;
break;
case Tenum:
sds = (cast(TypeEnum)t).sym;
break;
default: break;
}
if (!sds)
return null;
Dsymbol ret = sds.search(loc, ident, flags);
if (ret)
{
*exp = new DotIdExp(loc, *exp, ad.aliasthis.ident);
*exp = new DotIdExp(loc, *exp, ident);
return ret;
}
if (!ts && !tc)
return null;
Dsymbol s;
*exp = new DotIdExp(loc, *exp, ad.aliasthis.ident);
if (ts && !(ts.att & AliasThisRec.tracing))
{
ts.att = cast(AliasThisRec)(ts.att | AliasThisRec.tracing);
s = checkAliasThis(sds.isAggregateDeclaration(), ident, flags, exp);
ts.att = cast(AliasThisRec)(ts.att & ~AliasThisRec.tracing);
}
else if(tc && !(tc.att & AliasThisRec.tracing))
{
tc.att = cast(AliasThisRec)(tc.att | AliasThisRec.tracing);
s = checkAliasThis(sds.isAggregateDeclaration(), ident, flags, exp);
tc.att = cast(AliasThisRec)(tc.att & ~AliasThisRec.tracing);
}
return s;
}
Dsymbol searchScopes(int flags)
{
for (Scope* sc = &this; 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
else if (sc.flags & SCOPE.Cfile && sc.scopesym.isStructDeclaration())
continue; // C doesn't have struct scope
if (Dsymbol s = sc.scopesym.search(loc, ident, flags))
{
if (flags & TagNameSpace)
{
// ImportC: if symbol is not a tag, look for it in tag table
if (!s.isScopeDsymbol())
{
auto ps = cast(void*)s in sc._module.tagSymTab;
if (!ps)
goto NotFound;
s = *ps;
}
}
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");
}
//printMsg("\tfound local", s);
if (pscopesym)
*pscopesym = sc.scopesym;
return s;
}
NotFound:
if (global.params.fixAliasThis)
{
Expression exp = new ThisExp(loc);
Dsymbol aliasSym = checkAliasThis(sc.scopesym.isAggregateDeclaration(), ident, flags, &exp);
if (aliasSym)
{
//printf("found aliassym: %s\n", aliasSym.toChars());
if (pscopesym)
*pscopesym = new ExpressionDsymbol(exp);
return aliasSym;
}
}
// 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;
}
if (this.flags & SCOPE.ignoresymbolvisibility)
flags |= IgnoreSymbolVisibility;
// First look in local scopes
Dsymbol s = searchScopes(flags | SearchLocalsOnly);
version (LOGSEARCH) if (s) printMsg("-Scope.search() found local", s);
if (!s)
{
// Second look in imported modules
s = searchScopes(flags | SearchImportsOnly);
version (LOGSEARCH) if (s) printMsg("-Scope.search() found import", s);
}
return s;
}
extern (D) Dsymbol search_correct(Identifier ident)
{
if (global.gag)
return null; // don't do it for speculative compiles; too time consuming
/************************************************
* Given the failed search attempt, try to find
* one with a close spelling.
* Params:
* seed = identifier to search for
* cost = set to the cost, which rises with each outer scope
* Returns:
* Dsymbol if found, null if not
*/
extern (D) Dsymbol scope_search_fp(const(char)[] seed, out 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.
*/
if (!seed.length)
return null;
Identifier id = Identifier.lookup(seed);
if (!id)
return null;
Scope* sc = &this;
Module.clearCache();
Dsymbol scopesym = null;
Dsymbol s = sc.search(Loc.initial, id, &scopesym, IgnoreErrors);
if (!s)
return null;
// Do not show `@disable`d declarations
if (auto decl = s.isDeclaration())
if (decl.storage_class & STC.disable)
return null;
// Or `deprecated` ones if we're not in a deprecated scope
if (s.isDeprecated() && !sc.isDeprecated())
return null;
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.visible().kind == Visibility.Kind.private_)
return null;
}
return s;
}
Dsymbol scopesym = null;
// search for exact name first
if (auto s = search(Loc.initial, ident, &scopesym, IgnoreErrors))
return s;
return speller!scope_search_fp(ident.toString());
}
/************************************
* 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
*/
extern (D) static const(char)* search_correct_C(Identifier ident)
{
import dmd.astenums : Twchar;
TOK tok;
if (ident == Id.NULL)
tok = TOK.null_;
else if (ident == Id.TRUE)
tok = TOK.true_;
else if (ident == Id.FALSE)
tok = TOK.false_;
else if (ident == Id.unsigned)
tok = TOK.uns32;
else if (ident == Id.wchar_t)
tok = target.c.wchar_tsize == 2 ? TOK.wchar_ : TOK.dchar_;
else
return null;
return Token.toChars(tok);
}
/***************************
* Find the innermost scope with a symbol table.
* Returns:
* innermost scope, null if none
*/
extern (D) Scope* inner() return
{
for (Scope* sc = &this; sc; sc = sc.enclosing)
{
if (sc.scopesym)
return sc;
}
return null;
}
/******************************
* Add symbol s to innermost symbol table.
* Params:
* s = symbol to insert
* Returns:
* null if already in table, `s` if not
*/
extern (D) Dsymbol insert(Dsymbol s)
{
//printf("insert() %s\n", s.toChars());
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;
}
auto scopesym = inner().scopesym;
//printf("\t\tscopesym = %p\n", scopesym);
if (!scopesym.symtab)
scopesym.symtab = new DsymbolTable();
if (!(flags & SCOPE.Cfile))
return scopesym.symtabInsert(s);
// ImportC insert
if (!scopesym.symtabInsert(s)) // if already in table
{
Dsymbol s2 = scopesym.symtabLookup(s, s.ident); // s2 is existing entry
return handleTagSymbols(this, s, s2, scopesym);
}
return s; // inserted
}
/********************************************
* Search enclosing scopes for ScopeDsymbol.
*/
extern (D) ScopeDsymbol getScopesym()
{
for (Scope* sc = &this; sc; sc = sc.enclosing)
{
if (sc.scopesym)
return sc.scopesym;
}
return null; // not found
}
/********************************************
* Search enclosing scopes for ClassDeclaration.
*/
extern (D) ClassDeclaration getClassScope()
{
for (Scope* sc = &this; sc; sc = sc.enclosing)
{
if (!sc.scopesym)
continue;
if (ClassDeclaration cd = sc.scopesym.isClassDeclaration())
return cd;
}
return null;
}
/********************************************
* Search enclosing scopes for ClassDeclaration or StructDeclaration.
*/
extern (D) AggregateDeclaration getStructClassScope()
{
for (Scope* sc = &this; sc; sc = sc.enclosing)
{
if (!sc.scopesym)
continue;
if (AggregateDeclaration ad = sc.scopesym.isClassDeclaration())
return ad;
if (AggregateDeclaration ad = sc.scopesym.isStructDeclaration())
return ad;
}
return null;
}
/********************************************
* Find the lexically enclosing function (if any).
*
* This function skips through generated FuncDeclarations,
* e.g. rewritten foreach bodies.
*
* Returns: the function or null
*/
extern (D) inout(FuncDeclaration) getEnclosingFunction() inout
{
if (!this.func)
return null;
auto fd = cast(FuncDeclaration) this.func;
// Look through foreach bodies rewritten as delegates
while (fd.fes)
{
assert(fd.fes.func);
fd = fd.fes.func;
}
return cast(inout(FuncDeclaration)) fd;
}
/*******************************************
* For TemplateDeclarations, we need to remember the Scope
* where it was declared. So mark the Scope as not
* to be free'd.
*/
extern (D) void 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 = true;
assert(!(flags & SCOPE.free));
//assert(sc != sc.enclosing);
//assert(!sc.enclosing || sc != sc.enclosing.enclosing);
//if (++i == 10)
// assert(0);
}
}
/******************************
*/
extern (D) structalign_t alignment()
{
if (aligndecl)
{
auto ad = aligndecl.getAlignment(&this);
return ad.salign;
}
else
{
structalign_t sa;
sa.setDefault();
return sa;
}
}
@safe @nogc pure nothrow const:
/**********************************
* Checks whether the current scope (or any of its parents) is deprecated.
*
* Returns: `true` if this or any parent scope is deprecated, `false` otherwise`
*/
extern (D) bool isDeprecated()
{
for (const(Dsymbol)* sp = &(this.parent); *sp; sp = &(sp.parent))
{
if (sp.isDeprecated())
return true;
}
for (const(Scope)* sc2 = &this; sc2; sc2 = sc2.enclosing)
{
if (sc2.scopesym && sc2.scopesym.isDeprecated())
return true;
// If inside a StorageClassDeclaration that is deprecated
if (sc2.stc & STC.deprecated_)
return true;
}
if (_module.md && _module.md.isdeprecated)
{
return true;
}
return false;
}
/**
* dmd relies on mutation of state during semantic analysis, however
* sometimes semantic is being performed in a speculative context that should
* not have any visible effect on the rest of the compilation: for example when compiling
* a typeof() or __traits(compiles).
*
* Returns: `true` if this `Scope` is known to be from one of these speculative contexts
*/
extern (D) bool isFromSpeculativeSemanticContext() scope
{
return this.intypeof || this.flags & SCOPE.compile;
}
}