/**
 * Does the semantic passes on enums.
 *
 * Copyright:   Copyright (C) 1999-2024 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/enumsem.d, _enumsem.d)
 * Documentation:  https://dlang.org/phobos/dmd_enumsem.html
 * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/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.dmangle;
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;
    UserAttributeDeclaration.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.flags & SCOPE.Cfile))  // 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)
    {
        EnumMember em = s.isEnumMember();
        if (em)
            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.flags & SCOPE.Cfile)
    {
        /* 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, const ref 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)
    {
        EnumMember em = (*ed.members)[i].isEnumMember();
        if (em)
        {
            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, const ref 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;
}
