blob: 4f0d4e5f53f23cac683e6b9f9fade4f2329d715a [file] [log] [blame]
/**
* Does the semantic passes on enums.
*
* Copyright: Copyright (C) 1999-2025 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/compiler/src/dmd/enumsem.d, _enumsem.d)
* Documentation: https://dlang.org/phobos/dmd_enumsem.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/compiler/src/dmd/enumsem.d
*/
module dmd.enumsem;
import core.stdc.stdio;
import core.stdc.string;
import dmd.aggregate;
import dmd.aliasthis;
import dmd.arraytypes;
import dmd.astcodegen;
import dmd.astenums;
import dmd.attrib;
import dmd.blockexit;
import dmd.clone;
import dmd.cond;
import dmd.compiler;
import dmd.dcast;
import dmd.dclass;
import dmd.declaration;
import dmd.denum;
import dmd.dimport;
import dmd.dinterpret;
import dmd.dmodule;
import dmd.dscope;
import dmd.dstruct;
import dmd.dsymbol;
import dmd.dsymbolsem;
import dmd.dtemplate;
import dmd.dversion;
import dmd.errors;
import dmd.escape;
import dmd.expression;
import dmd.expressionsem;
import dmd.func;
import dmd.funcsem;
import dmd.globals;
import dmd.id;
import dmd.identifier;
import dmd.importc;
import dmd.init;
import dmd.initsem;
import dmd.intrange;
import dmd.hdrgen;
import dmd.location;
import dmd.mtype;
import dmd.mustuse;
import dmd.nogc;
import dmd.nspace;
import dmd.objc;
import dmd.opover;
import dmd.optimize;
import dmd.parse;
import dmd.root.array;
import dmd.root.filename;
import dmd.common.outbuffer;
import dmd.root.rmem;
import dmd.rootobject;
import dmd.root.utf;
import dmd.semantic2;
import dmd.semantic3;
import dmd.sideeffect;
import dmd.statementsem;
import dmd.staticassert;
import dmd.tokens;
import dmd.utils;
import dmd.statement;
import dmd.target;
import dmd.templateparamsem;
import dmd.typesem;
import dmd.visitor;
/*********************************
* Perform semantic analysis on enum declaration `em`
*/
void enumSemantic(Scope* sc, EnumDeclaration ed)
{
//printf("EnumDeclaration::semantic(sd = %p, '%s') %s\n", sc.scopesym, sc.scopesym.toChars(), ed.toChars());
//printf("EnumDeclaration::semantic() %p %s\n", ed, ed.toChars());
if (ed.semanticRun >= PASS.semanticdone)
return; // semantic() already completed
if (ed.semanticRun == PASS.semantic)
{
assert(ed.memtype);
error(ed.loc, "circular reference to enum base type `%s`", ed.memtype.toChars());
ed.errors = true;
ed.semanticRun = PASS.semanticdone;
return;
}
Scope* scx = null;
if (ed._scope)
{
sc = ed._scope;
scx = ed._scope; // save so we don't make redundant copies
ed._scope = null;
}
if (!sc)
return;
ed.parent = sc.parent;
ed.type = ed.type.typeSemantic(ed.loc, sc);
ed.visibility = sc.visibility;
if (sc.stc & STC.deprecated_)
ed.isdeprecated = true;
ed.userAttribDecl = sc.userAttribDecl;
ed.cppnamespace = sc.namespace;
ed.semanticRun = PASS.semantic;
checkGNUABITag(ed, sc.linkage);
checkMustUseReserved(ed);
if (!ed.members && !ed.memtype) // enum ident;
{
ed.semanticRun = PASS.semanticdone;
return;
}
if (!ed.symtab)
ed.symtab = new DsymbolTable();
/* The separate, and distinct, cases are:
* 1. enum { ... }
* 2. enum : memtype { ... }
* 3. enum ident { ... }
* 4. enum ident : memtype { ... }
* 5. enum ident : memtype;
* 6. enum ident;
*/
if (ed.memtype)
{
ed.memtype = ed.memtype.typeSemantic(ed.loc, sc);
/* Check to see if memtype is forward referenced
*/
if (auto te = ed.memtype.isTypeEnum())
{
auto sym = te.toDsymbol(sc).isEnumDeclaration();
// Special enums like __c_[u]long[long] are fine to forward reference
// see https://issues.dlang.org/show_bug.cgi?id=20599
if (!sym.isSpecial() && (!sym.memtype || !sym.members || !sym.symtab || sym._scope))
{
// memtype is forward referenced, so try again later
deferDsymbolSemantic(sc, ed, scx);
//printf("\tdeferring %s\n", toChars());
ed.semanticRun = PASS.initial;
return;
}
else
// Ensure that semantic is run to detect. e.g. invalid forward references
sym.dsymbolSemantic(sc);
}
if (ed.memtype.ty == Tvoid)
{
.error(ed.loc, "%s `%s` base type must not be `void`", ed.kind, ed.toPrettyChars);
ed.memtype = Type.terror;
}
if (ed.memtype.ty == Terror)
{
ed.errors = true;
// poison all the members
ed.members.foreachDsymbol( (s) { s.errors = true; } );
ed.semanticRun = PASS.semanticdone;
return;
}
}
if (!ed.members) // enum ident : memtype;
{
ed.semanticRun = PASS.semanticdone;
return;
}
if (ed.members.length == 0)
{
.error(ed.loc, "%s `%s` enum `%s` must have at least one member", ed.kind, ed.toPrettyChars, ed.toChars());
ed.errors = true;
ed.semanticRun = PASS.semanticdone;
return;
}
if (!sc.inCfile) // C enum remains incomplete until members are done
ed.semanticRun = PASS.semanticdone;
version (none)
{
// @@@DEPRECATED_2.110@@@ https://dlang.org/deprecate.html#scope%20as%20a%20type%20constraint
// Deprecated in 2.100
// Make an error in 2.110
if (sc.stc & STC.scope_)
deprecation(ed.loc, "`scope` as a type constraint is deprecated. Use `scope` at the usage site.");
}
Scope* sce;
if (ed.isAnonymous())
sce = sc;
else
{
sce = sc.push(ed);
sce.parent = ed;
}
sce = sce.startCTFE();
sce.setNoFree(); // needed for getMaxMinValue()
/* Each enum member gets the sce scope
*/
ed.members.foreachDsymbol( (s)
{
if (EnumMember em = s.isEnumMember())
em._scope = sce;
});
/* addMember() is not called when the EnumDeclaration appears as a function statement,
* so we have to do what addMember() does and install the enum members in the right symbol
* table
*/
addEnumMembersToSymtab(ed, sc, sc.getScopesym());
if (sc.inCfile)
{
/* C11 6.7.2.2
*/
Type commonType = ed.memtype;
if (!commonType)
commonType = Type.tint32;
ulong nextValue = 0; // C11 6.7.2.2-3 first member value defaults to 0
// C11 6.7.2.2-2 value must be representable as an int.
// The sizemask represents all values that int will fit into,
// from 0..uint.max. We want to cover int.min..uint.max.
IntRange ir = IntRange.fromType(commonType);
void emSemantic(EnumMember em, ref ulong nextValue)
{
static void errorReturn(EnumMember em)
{
em.value = ErrorExp.get();
em.errors = true;
em.semanticRun = PASS.semanticdone;
}
em.semanticRun = PASS.semantic;
em.type = commonType;
em._linkage = LINK.c;
em.storage_class |= STC.manifest;
if (em.value)
{
Expression e = em.value;
assert(e.dyncast() == DYNCAST.expression);
/* To merge the type of e with commonType, add 0 of type commonType
*/
if (!ed.memtype)
e = new AddExp(em.loc, e, new IntegerExp(em.loc, 0, commonType));
e = e.expressionSemantic(sc);
e = resolveProperties(sc, e);
e = e.integralPromotions(sc);
e = e.ctfeInterpret();
if (e.op == EXP.error)
return errorReturn(em);
auto ie = e.isIntegerExp();
if (!ie)
{
// C11 6.7.2.2-2
.error(em.loc, "%s `%s` enum member must be an integral constant expression, not `%s` of type `%s`", em.kind, em.toPrettyChars, e.toChars(), e.type.toChars());
return errorReturn(em);
}
if (ed.memtype && !ir.contains(getIntRange(ie)))
{
// C11 6.7.2.2-2
.error(em.loc, "%s `%s` enum member value `%s` does not fit in `%s`", em.kind, em.toPrettyChars, e.toChars(), commonType.toChars());
return errorReturn(em);
}
nextValue = ie.toInteger();
if (!ed.memtype)
commonType = e.type;
em.value = new IntegerExp(em.loc, nextValue, commonType);
}
else
{
// C11 6.7.2.2-3 add 1 to value of previous enumeration constant
bool first = (em == (*em.ed.members)[0]);
if (!first)
{
Expression max = getProperty(commonType, null, em.loc, Id.max, 0);
if (nextValue == max.toInteger())
{
.error(em.loc, "%s `%s` initialization with `%s+1` causes overflow for type `%s`", em.kind, em.toPrettyChars, max.toChars(), commonType.toChars());
return errorReturn(em);
}
nextValue += 1;
}
em.value = new IntegerExp(em.loc, nextValue, commonType);
}
em.type = commonType;
em.semanticRun = PASS.semanticdone;
}
ed.members.foreachDsymbol( (s)
{
if (EnumMember em = s.isEnumMember())
emSemantic(em, nextValue);
});
if (!ed.memtype)
{
// cast all members to commonType
ed.members.foreachDsymbol( (s)
{
if (EnumMember em = s.isEnumMember())
{
em.type = commonType;
// optimize out the cast so that other parts of the compiler can
// assume that an integral enum's members are `IntegerExp`s.
// https://issues.dlang.org/show_bug.cgi?id=24504
em.value = em.value.castTo(sc, commonType).optimize(WANTvalue);
}
});
}
ed.memtype = commonType;
ed.semanticRun = PASS.semanticdone;
return;
}
ed.members.foreachDsymbol( (s)
{
if (EnumMember em = s.isEnumMember())
em.dsymbolSemantic(em._scope);
});
//printf("ed.defaultval = %lld\n", ed.defaultval);
//if (ed.defaultval) printf("ed.defaultval: %s %s\n", ed.defaultval.toChars(), ed.defaultval.type.toChars());
//printf("members = %s\n", members.toChars());
}
Expression getDefaultValue(EnumDeclaration ed, Loc loc)
{
Expression handleErrors(){
ed.defaultval = ErrorExp.get();
return ed.defaultval;
}
//printf("EnumDeclaration::getDefaultValue() %p %s\n", this, toChars());
// https://issues.dlang.org/show_bug.cgi?id=23904
// Return ed.defaultval only if it is not ErrorExp.
// A speculative context may set ed.defaultval to ErrorExp;
// subsequent non-speculative contexts need to be able
// to print the error.
if (ed.defaultval && !ed.defaultval.isErrorExp())
return ed.defaultval;
if (ed.isCsymbol())
return ed.memtype.defaultInit(loc, true);
if (ed._scope)
dsymbolSemantic(ed, ed._scope);
if (ed.errors)
return handleErrors();
if (!ed.members)
{
if (ed.isSpecial())
{
/* Allow these special enums to not need a member list
*/
return ed.defaultval = ed.memtype.defaultInit(loc);
}
error(loc, "%s `%s` is opaque and has no default initializer", ed.kind, ed.toPrettyChars);
return handleErrors();
}
foreach (const i; 0 .. ed.members.length)
{
if (EnumMember em = (*ed.members)[i].isEnumMember())
{
if (em.semanticRun < PASS.semanticdone)
{
error(loc, "%s `%s` forward reference of `%s.init`", ed.kind, ed.toPrettyChars, ed.toChars());
return handleErrors();
}
ed.defaultval = em.value;
return ed.defaultval;
}
}
return handleErrors();
}
Type getMemtype(EnumDeclaration ed, Loc loc)
{
if (ed._scope)
{
/* Enum is forward referenced. We don't need to resolve the whole thing,
* just the base type
*/
if (ed.memtype)
{
Loc locx = loc.isValid() ? loc : ed.loc;
ed.memtype = ed.memtype.typeSemantic(locx, ed._scope);
}
else
{
// Run semantic to get the type from a possible first member value
dsymbolSemantic(ed, ed._scope);
}
}
if (!ed.memtype)
{
if (!ed.isAnonymous() && (ed.members || ed.semanticRun >= PASS.semanticdone))
ed.memtype = Type.tint32;
else
{
Loc locx = loc.isValid() ? loc : ed.loc;
error(locx, "is forward referenced looking for base type");
return Type.terror;
}
}
return ed.memtype;
}
/*********************************
* Perform semantic analysis on enum member `em`
*/
void enumMemberSemantic(Scope* sc, EnumMember em)
{
//printf("EnumMember::semantic() %s\n", em.toChars());
void errorReturn()
{
em.errors = true;
em.semanticRun = PASS.semanticdone;
}
if (em.errors || em.semanticRun >= PASS.semanticdone)
return;
if (em.semanticRun == PASS.semantic)
{
.error(em.loc, "%s `%s` circular reference to `enum` member", em.kind, em.toPrettyChars);
return errorReturn();
}
assert(em.ed);
em.ed.dsymbolSemantic(sc);
if (em.ed.errors)
return errorReturn();
if (em.errors || em.semanticRun >= PASS.semanticdone)
return;
if (em._scope)
sc = em._scope;
if (!sc)
return;
em.semanticRun = PASS.semantic;
em.visibility = em.ed.isAnonymous() ? em.ed.visibility : Visibility(Visibility.Kind.public_);
em._linkage = LINK.d;
em.storage_class |= STC.manifest;
// https://issues.dlang.org/show_bug.cgi?id=9701
if (em.ed.isAnonymous())
{
if (em.userAttribDecl)
em.userAttribDecl.userAttribDecl = em.ed.userAttribDecl;
else
em.userAttribDecl = em.ed.userAttribDecl;
}
// Eval UDA in this same scope. Issues 19344, 20835, 21122
if (em.userAttribDecl)
{
// Set scope but avoid extra sc.uda attachment inside setScope()
auto inneruda = em.userAttribDecl.userAttribDecl;
em.userAttribDecl.setScope(sc);
em.userAttribDecl.userAttribDecl = inneruda;
em.userAttribDecl.dsymbolSemantic(sc);
}
// The first enum member is special
bool first = (em == (*em.ed.members)[0]);
if (em.origType)
{
em.origType = em.origType.typeSemantic(em.loc, sc);
em.type = em.origType;
assert(em.value); // "type id;" is not a valid enum member declaration
}
if (em.value)
{
Expression e = em.value;
assert(e.dyncast() == DYNCAST.expression);
if (em.ed.memtype)
e = inferType(e, em.ed.memtype);
e = e.expressionSemantic(sc);
e = resolveProperties(sc, e);
e = e.ctfeInterpret();
if (e.op == EXP.error)
return errorReturn();
if (first && !em.ed.memtype && !em.ed.isAnonymous())
{
em.ed.memtype = e.type;
if (em.ed.memtype.ty == Terror)
{
em.ed.errors = true;
return errorReturn();
}
if (em.ed.memtype.ty != Terror)
{
/* https://issues.dlang.org/show_bug.cgi?id=11746
* All of named enum members should have same type
* with the first member. If the following members were referenced
* during the first member semantic, their types should be unified.
*/
em.ed.members.foreachDsymbol( (s)
{
EnumMember enm = s.isEnumMember();
if (!enm || enm == em || enm.semanticRun < PASS.semanticdone || enm.origType)
return;
//printf("[%d] em = %s, em.semanticRun = %d\n", i, toChars(), em.semanticRun);
Expression ev = enm.value;
ev = ev.implicitCastTo(sc, em.ed.memtype);
ev = ev.ctfeInterpret();
ev = ev.castTo(sc, em.ed.type);
if (ev.op == EXP.error)
em.ed.errors = true;
enm.value = ev;
});
if (em.ed.errors)
{
em.ed.memtype = Type.terror;
return errorReturn();
}
}
}
if (em.ed.memtype && !em.origType)
{
e = e.implicitCastTo(sc, em.ed.memtype);
e = e.ctfeInterpret();
// save origValue for better json output
em.origValue = e;
if (!em.ed.isAnonymous())
{
e = e.castTo(sc, em.ed.type.addMod(e.type.mod)); // https://issues.dlang.org/show_bug.cgi?id=12385
e = e.ctfeInterpret();
}
}
else if (em.origType)
{
e = e.implicitCastTo(sc, em.origType);
e = e.ctfeInterpret();
assert(em.ed.isAnonymous());
// save origValue for better json output
em.origValue = e;
}
em.value = e;
// https://issues.dlang.org/show_bug.cgi?id=24311
// First enum member is .init value, which gets put into static segment
if (first)
lowerStaticAAs(e, sc);
}
else if (first)
{
Type t;
if (em.ed.memtype)
t = em.ed.memtype;
else
{
t = Type.tint32;
if (!em.ed.isAnonymous())
em.ed.memtype = t;
}
const errors = global.startGagging();
Expression e = new IntegerExp(em.loc, 0, t);
e = e.ctfeInterpret();
if (global.endGagging(errors))
{
error(em.loc, "cannot generate 0 value of type `%s` for `%s`",
t.toChars(), em.toChars());
}
// save origValue for better json output
em.origValue = e;
if (!em.ed.isAnonymous())
{
e = e.castTo(sc, em.ed.type);
e = e.ctfeInterpret();
}
em.value = e;
}
else
{
/* Find the previous enum member,
* and set this to be the previous value + 1
*/
EnumMember emprev = null;
em.ed.members.foreachDsymbol( (s)
{
if (auto enm = s.isEnumMember())
{
if (enm == em)
return 1; // found
emprev = enm;
}
return 0; // continue
});
assert(emprev);
if (emprev.semanticRun < PASS.semanticdone) // if forward reference
emprev.dsymbolSemantic(emprev._scope); // resolve it
if (emprev.errors)
return errorReturn();
auto errors = global.startGagging();
Expression eprev = emprev.value;
assert(eprev);
// .toHeadMutable() due to https://issues.dlang.org/show_bug.cgi?id=18645
Type tprev = eprev.type.toHeadMutable().equals(em.ed.type.toHeadMutable())
? em.ed.memtype
: eprev.type;
/*
https://issues.dlang.org/show_bug.cgi?id=20777
Previously this used getProperty, which doesn't consider anything user defined,
this construct does do that and thus fixes the bug.
*/
Expression emax = DotIdExp.create(em.ed.loc, new TypeExp(em.ed.loc, tprev), Id.max);
emax = emax.expressionSemantic(sc);
emax = emax.ctfeInterpret();
// check that (eprev != emax)
Expression e = new EqualExp(EXP.equal, em.loc, eprev, emax);
e = e.expressionSemantic(sc);
e = e.ctfeInterpret();
if (global.endGagging(errors))
{
// display an introductory error before showing what actually failed
error(em.loc, "cannot check `%s` value for overflow", em.toPrettyChars());
// rerun to show errors
Expression e2 = DotIdExp.create(em.ed.loc, new TypeExp(em.ed.loc, tprev), Id.max);
e2 = e2.expressionSemantic(sc);
e2 = e2.ctfeInterpret();
e2 = new EqualExp(EXP.equal, em.loc, eprev, e2);
e2 = e2.expressionSemantic(sc);
e2 = e2.ctfeInterpret();
}
// now any errors are for generating a value
if (e.toInteger())
{
auto mt = em.ed.memtype;
if (!mt)
mt = eprev.type;
.error(em.loc, "%s `%s` initialization with `%s.%s+1` causes overflow for type `%s`", em.kind, em.toPrettyChars,
emprev.ed.toChars(), emprev.toChars(), mt.toChars());
return errorReturn();
}
errors = global.startGagging();
// Now set e to (eprev + 1)
e = new AddExp(em.loc, eprev, IntegerExp.literal!1);
e = e.expressionSemantic(sc);
e = e.castTo(sc, eprev.type);
e = e.ctfeInterpret();
if (global.endGagging(errors))
{
error(em.loc, "cannot generate value for `%s`", em.toPrettyChars());
// rerun to show errors
Expression e2 = new AddExp(em.loc, eprev, IntegerExp.literal!1);
e2 = e2.expressionSemantic(sc);
e2 = e2.castTo(sc, eprev.type);
e2 = e2.ctfeInterpret();
}
// save origValue (without cast) for better json output
if (e.op != EXP.error) // avoid duplicate diagnostics
{
assert(emprev.origValue);
em.origValue = new AddExp(em.loc, emprev.origValue, IntegerExp.literal!1);
em.origValue = em.origValue.expressionSemantic(sc);
em.origValue = em.origValue.ctfeInterpret();
}
if (e.op == EXP.error)
return errorReturn();
if (e.type.isFloating())
{
// Check that e != eprev (not always true for floats)
Expression etest = new EqualExp(EXP.equal, em.loc, e, eprev);
etest = etest.expressionSemantic(sc);
etest = etest.ctfeInterpret();
if (etest.toInteger())
{
.error(em.loc, "%s `%s` has inexact value due to loss of precision", em.kind, em.toPrettyChars);
return errorReturn();
}
}
em.value = e;
}
if (!em.origType)
em.type = em.value.type;
assert(em.origValue);
em.semanticRun = PASS.semanticdone;
}