/**
 * Contains semantic routines specific to ImportC
 *
 * Specification: C11
 *
 * Copyright:   Copyright (C) 2021-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/importc.d, _importc.d)
 * Documentation:  https://dlang.org/phobos/dmd_importc.html
 * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/compiler/src/dmd/importc.d
 */

module dmd.importc;

import core.stdc.stdio;

import dmd.astenums;
import dmd.dcast;
import dmd.denum;
import dmd.declaration;
import dmd.dscope;
import dmd.dsymbol;
import dmd.dsymbolsem;
import dmd.dinterpret : ctfeInterpret;
import dmd.errors;
import dmd.expression;
import dmd.expressionsem;
import dmd.identifier;
import dmd.id : Id;
import dmd.init;
import dmd.intrange : IntRange;
import dmd.mtype;
import dmd.optimize : optimize;
import dmd.rootobject : DYNCAST;
import dmd.tokens;
import dmd.typesem;

/**************************************
 * C11 does not allow array or function parameters.
 * Hence, adjust those types per C11 6.7.6.3 rules.
 * Params:
 *      t = parameter type to adjust
 *      sc = context
 * Returns:
 *      adjusted type
 */
Type cAdjustParamType(Type t, Scope* sc)
{
    if (!sc.inCfile)
        return t;

    Type tb = t.toBasetype();

    /* C11 6.7.6.3-7 array of T is converted to pointer to T
     */
    if (auto ta = tb.isTypeDArray())
    {
        t = ta.next.pointerTo();
    }
    else if (auto ts = tb.isTypeSArray())
    {
        t = ts.next.pointerTo();
    }
    /* C11 6.7.6.3-8 function is converted to pointer to function
     */
    else if (tb.isTypeFunction())
    {
        t = tb.pointerTo();
    }
    return t;
}

/***********************************************
 * C11 6.3.2.1-3 Convert expression that is an array of type to a pointer to type.
 * C11 6.3.2.1-4 Convert expression that is a function to a pointer to a function.
 * Params:
 *  e = ImportC expression to possibly convert
 *  sc = context
 * Returns:
 *  converted expression
 */
Expression arrayFuncConv(Expression e, Scope* sc)
{
    //printf("arrayFuncConv() %s\n", e.toChars());
    if (!sc.inCfile)
        return e;

    auto t = e.type.toBasetype();
    if (auto ta = t.isTypeDArray())
    {
        if (!checkAddressable(e, sc, "take address of"))
            return ErrorExp.get();
        e = e.castTo(sc, ta.next.pointerTo());
    }
    else if (auto ts = t.isTypeSArray())
    {
        if (!checkAddressable(e, sc, "take address of"))
            return ErrorExp.get();
        e = e.castTo(sc, ts.next.pointerTo());
    }
    else if (t.isTypeFunction())
    {
        e = new AddrExp(e.loc, e);
    }
    else
        return e;
    return e.expressionSemantic(sc);
}

/****************************************
 * Run semantic on `e`.
 * Expression `e` evaluates to an instance of a struct.
 * Look up `ident` as a field of that struct.
 * Params:
 *   e = evaluates to an instance of a struct
 *   sc = context
 *   id = identifier of a field in that struct
 *   arrow = -> was used
 * Returns:
 *   if successful `e.ident`
 *   if not then `ErrorExp` and message is printed
 */
Expression fieldLookup(Expression e, Scope* sc, Identifier id, bool arrow)
{
    e = e.expressionSemantic(sc);
    if (e.isErrorExp())
        return e;
    if (arrow)
    {
        e = arrayFuncConv(e, sc);
        if (e.isErrorExp())
            return e;
    }

    auto t = e.type;
    if (t.isTypePointer())
    {
        t = t.isTypePointer().next;
        auto pe = e.toChars();
        if (!arrow)
            error(e.loc, "since `%s` is a pointer, use `%s->%s` instead of `%s.%s`", pe, pe, id.toChars(), pe, id.toChars());
        e = new PtrExp(e.loc, e);
    }
    Dsymbol s;
    if (auto ts = t.isTypeStruct())
        s = ts.sym.search(e.loc, id, 0);
    if (!s)
    {
        error(e.loc, "`%s` is not a member of `%s`", id.toChars(), t.toChars());
        return ErrorExp.get();
    }
    Expression ef = new DotVarExp(e.loc, e, s.isDeclaration());
    return ef.expressionSemantic(sc);
}

/****************************************
 * C11 6.5.2.1-2
 * Apply C semantics to `E[I]` expression.
 * E1[E2] is lowered to *(E1 + E2)
 * Params:
 *      ae = ArrayExp to run semantics on
 *      sc = context
 * Returns:
 *      Expression if this was a C expression with completed semantic, null if not
 */
Expression carraySemantic(ArrayExp ae, Scope* sc)
{
    if (!sc.inCfile)
        return null;

    auto e1 = ae.e1.expressionSemantic(sc);

    assert(ae.arguments.length == 1);
    Expression e2 = (*ae.arguments)[0];

    /* CTFE cannot do pointer arithmetic, but it can index arrays.
     * So, rewrite as an IndexExp if we can.
     */
    auto t1 = e1.type.toBasetype();
    if (t1.isStaticOrDynamicArray())
    {
        e2 = e2.expressionSemantic(sc).arrayFuncConv(sc);
        // C doesn't do array bounds checking, so `true` turns it off
        return new IndexExp(ae.loc, e1, e2, true).expressionSemantic(sc);
    }

    e1 = e1.arrayFuncConv(sc);   // e1 might still be a function call
    e2 = e2.expressionSemantic(sc);
    auto t2 = e2.type.toBasetype();
    if (t2.isStaticOrDynamicArray())
    {
        return new IndexExp(ae.loc, e2, e1, true).expressionSemantic(sc); // swap operands
    }

    e2 = e2.arrayFuncConv(sc);
    auto ep = new PtrExp(ae.loc, new AddExp(ae.loc, e1, e2));
    return ep.expressionSemantic(sc);
}

/******************************************
 * Determine default initializer for const global symbol.
 */
void addDefaultCInitializer(VarDeclaration dsym)
{
    //printf("addDefaultCInitializer() %s\n", dsym.toChars());
    if (!(dsym.storage_class & (STC.static_ | STC.gshared)))
        return;
    if (dsym.storage_class & (STC.extern_ | STC.field | STC.in_ | STC.foreach_ | STC.parameter | STC.result))
        return;

    Type t = dsym.type;
    if (t.isTypeSArray() && t.isTypeSArray().isIncomplete())
    {
        dsym._init = new VoidInitializer(dsym.loc);
        return; // incomplete arrays will be diagnosed later
    }

    if (t.isMutable())
        return;

    auto e = dsym.type.defaultInit(dsym.loc, true);
    dsym._init = new ExpInitializer(dsym.loc, e);
}

/********************************************
 * Implement the C11 notion of function equivalence,
 * which allows prototyped functions to match K+R functions,
 * even though they are different.
 * Params:
 *      tf1 = type of first function
 *      tf2 = type of second function
 * Returns:
 *      true if C11 considers them equivalent
 */

bool cFuncEquivalence(TypeFunction tf1, TypeFunction tf2)
{
    //printf("cFuncEquivalence()\n  %s\n  %s\n", tf1.toChars(), tf2.toChars());
    if (tf1.equals(tf2))
        return true;

    if (tf1.linkage != tf2.linkage)
        return false;

    // Allow func(void) to match func()
    if (tf1.parameterList.length == 0 && tf2.parameterList.length == 0)
        return true;

    if (!cTypeEquivalence(tf1.next, tf2.next))
        return false;   // function return types don't match

    if (tf1.parameterList.length != tf2.parameterList.length)
        return false;

    if (!tf1.parameterList.hasIdentifierList && !tf2.parameterList.hasIdentifierList) // if both are prototyped
    {
        if (tf1.parameterList.varargs != tf2.parameterList.varargs)
            return false;
    }

    foreach (i, fparam ; tf1.parameterList)
    {
        Type t1 = fparam.type;
        Type t2 = tf2.parameterList[i].type;

        /* Strip off head const.
         * Not sure if this is C11, but other compilers treat
         * `void fn(int)` and `fn(const int x)`
         * as equivalent.
         */
        t1 = t1.mutableOf();
        t2 = t2.mutableOf();

        if (!t1.equals(t2))
            return false;
    }

    //printf("t1: %s\n", tf1.toChars());
    //printf("t2: %s\n", tf2.toChars());
    return true;
}

/*******************************
 * Types haven't been merged yet, because we haven't done
 * semantic() yet.
 * But we still need to see if t1 and t2 are the same type.
 * Params:
 *      t1 = first type
 *      t2 = second type
 * Returns:
 *      true if they are equivalent types
 */
bool cTypeEquivalence(Type t1, Type t2)
{
    if (t1.equals(t2))
        return true;    // that was easy

    if (t1.ty != t2.ty || t1.mod != t2.mod)
        return false;

    if (auto tp = t1.isTypePointer())
        return cTypeEquivalence(tp.next, t2.nextOf());

    if (auto ta = t1.isTypeSArray())
        // Bug: should check array dimension
        return cTypeEquivalence(ta.next, t2.nextOf());

    if (auto ts = t1.isTypeStruct())
        return ts.sym is t2.isTypeStruct().sym;

    if (auto te = t1.isTypeEnum())
        return te.sym is t2.isTypeEnum().sym;

    if (auto tf = t1.isTypeFunction())
        return cFuncEquivalence(tf, tf.isTypeFunction());

    return false;
}

/**********************************************
 * ImportC tag symbols sit in a parallel symbol table,
 * so that this C code works:
 * ---
 * struct S { a; };
 * int S;
 * struct S s;
 * ---
 * But there are relatively few such tag symbols, so that would be
 * a waste of memory and complexity. An additional problem is we'd like the D side
 * to find the tag symbols with ordinary lookup, not lookup in both
 * tables, if the tag symbol is not conflicting with an ordinary symbol.
 * The solution is to put the tag symbols that conflict into an associative
 * array, indexed by the address of the ordinary symbol that conflicts with it.
 * C has no modules, so this associative array is tagSymTab[] in ModuleDeclaration.
 * A side effect of our approach is that D code cannot access a tag symbol that is
 * hidden by an ordinary symbol. This is more of a theoretical problem, as nobody
 * has mentioned it when importing C headers. If someone wants to do it,
 * too bad so sad. Change the C code.
 * This function fixes up the symbol table when faced with adding a new symbol
 * `s` when there is an existing symbol `s2` with the same name.
 * C also allows forward and prototype declarations of tag symbols,
 * this function merges those.
 * Params:
 *      sc = context
 *      s = symbol to add to symbol table
 *      s2 = existing declaration
 *      sds = symbol table
 * Returns:
 *      if s and s2 are successfully put in symbol table then return the merged symbol,
 *      null if they conflict
 */
Dsymbol handleTagSymbols(ref Scope sc, Dsymbol s, Dsymbol s2, ScopeDsymbol sds)
{
    enum log = false;
    if (log) printf("handleTagSymbols('%s') add %p existing %p\n", s.toChars(), s, s2);
    if (log) printf("  add %s %s, existing %s %s\n", s.kind(), s.toChars(), s2.kind(), s2.toChars());
    auto sd = s.isScopeDsymbol(); // new declaration
    auto sd2 = s2.isScopeDsymbol(); // existing declaration

    static if (log) void print(EnumDeclaration sd)
    {
        printf("members: %p\n", sd.members);
        printf("symtab: %p\n", sd.symtab);
        printf("endlinnum: %d\n", sd.endlinnum);
        printf("type: %s\n", sd.type.toChars());
        printf("memtype: %s\n", sd.memtype.toChars());
    }

    if (!sd2)
    {
        /* Look in tag table
         */
        if (log) printf(" look in tag table\n");
        if (auto p = cast(void*)s2 in sc._module.tagSymTab)
        {
            Dsymbol s2tag = *p;
            sd2 = s2tag.isScopeDsymbol();
            assert(sd2);        // only tags allowed in tag symbol table
        }
    }

    if (sd && sd2) // `s` is a tag, `sd2` is the same tag
    {
        if (log) printf(" tag is already defined\n");

        if (sd.kind() != sd2.kind())  // being enum/struct/union must match
            return null;              // conflict

        /* Not a redeclaration if one is a forward declaration.
         * Move members to the first declared type, which is sd2.
         */
        if (sd2.members)
        {
            if (!sd.members)
                return sd2;  // ignore the sd redeclaration
        }
        else if (sd.members)
        {
            sd2.members = sd.members; // transfer definition to sd2
            sd.members = null;
            if (auto ed2 = sd2.isEnumDeclaration())
            {
                auto ed = sd.isEnumDeclaration();
                if (ed.memtype != ed2.memtype)
                    return null;        // conflict

                // transfer ed's members to sd2
                ed2.members.foreachDsymbol( (s)
                {
                    if (auto em = s.isEnumMember())
                        em.ed = ed2;
                });

                ed2.type = ed.type;
                ed2.memtype = ed.memtype;
                ed2.added = false;
            }
            return sd2;
        }
        else
            return sd2; // ignore redeclaration
    }
    else if (sd) // `s` is a tag, `s2` is not
    {
        if (log) printf(" s is tag, s2 is not\n");
        /* add `s` as tag indexed by s2
         */
        sc._module.tagSymTab[cast(void*)s2] = s;
        return s;
    }
    else if (s2 is sd2) // `s2` is a tag, `s` is not
    {
        if (log) printf(" s2 is tag, s is not\n");
        /* replace `s2` in symbol table with `s`,
         * then add `s2` as tag indexed by `s`
         */
        sds.symtab.update(s);
        sc._module.tagSymTab[cast(void*)s] = s2;
        return s;
    }
    // neither s2 nor s is a tag
    if (log) printf(" collision\n");
    return null;
}


/**********************************************
 * ImportC allows redeclarations of C variables, functions and typedefs.
 *    extern int x;
 *    int x = 3;
 * and:
 *    extern void f();
 *    void f() { }
 * Attempt to merge them.
 * Params:
 *      sc = context
 *      s = symbol to add to symbol table
 *      s2 = existing declaration
 *      sds = symbol table
 * Returns:
 *      if s and s2 are successfully put in symbol table then return the merged symbol,
 *      null if they conflict
 */
Dsymbol handleSymbolRedeclarations(ref Scope sc, Dsymbol s, Dsymbol s2, ScopeDsymbol sds)
{
    enum log = false;
    if (log) printf("handleSymbolRedeclarations('%s')\n", s.toChars());
    if (log) printf("  add %s %s, existing %s %s\n", s.kind(), s.toChars(), s2.kind(), s2.toChars());

    static Dsymbol collision()
    {
        if (log) printf(" collision\n");
        return null;
    }
    /*
    Handle merging declarations with asm("foo") and their definitions
    */
    static void mangleWrangle(Declaration oldDecl, Declaration newDecl)
    {
        if (oldDecl && newDecl)
        {
            newDecl.mangleOverride = oldDecl.mangleOverride ? oldDecl.mangleOverride : null;
        }
    }

    // Don't let macros shadow real symbols
    if (auto td = s.isTemplateDeclaration())
    {
        if (td.isCmacro) return s2;
    }

    auto vd = s.isVarDeclaration(); // new declaration
    auto vd2 = s2.isVarDeclaration(); // existing declaration

    if (vd && vd.isCmacro)
        return s2;

    assert(!(vd2 && vd2.isCmacro));

    if (vd && vd2)
    {
        /* if one is `static` and the other isn't, the result is undefined
         * behavior, C11 6.2.2.7
         */
        if ((vd.storage_class ^ vd2.storage_class) & STC.static_)
            return collision();

        const i1 =  vd._init && ! vd._init.isVoidInitializer();
        const i2 = vd2._init && !vd2._init.isVoidInitializer();

        if (i1 && i2)
            return collision();         // can't both have initializers

        mangleWrangle(vd2, vd);

        if (i1)                         // vd is the definition
        {
            vd2.storage_class |= STC.extern_;  // so toObjFile() won't emit it
            sds.symtab.update(vd);      // replace vd2 with the definition
            return vd;
        }

        /* BUG: the types should match, which needs semantic() to be run on it
         *    extern int x;
         *    int x;  // match
         *    typedef int INT;
         *    INT x;  // match
         *    long x; // collision
         * We incorrectly ignore these collisions
         */
        return vd2;
    }

    auto fd = s.isFuncDeclaration(); // new declaration
    auto fd2 = s2.isFuncDeclaration(); // existing declaration
    if (fd && fd2)
    {
        /* if one is `static` and the other isn't, the result is undefined
         * behavior, C11 6.2.2.7
         * However, match what gcc allows:
         *    static int sun1(); int sun1() { return 0; }
         * and:
         *    static int sun2() { return 0; } int sun2();
         * Both produce a static function.
         *
         * Both of these should fail:
         *    int sun3(); static int sun3() { return 0; }
         * and:
         *    int sun4() { return 0; } static int sun4();
         */
        // if adding `static`
        if (   fd.storage_class & STC.static_ &&
            !(fd2.storage_class & STC.static_))
        {
            return collision();
        }

        if (fd.fbody && fd2.fbody)
            return collision();         // can't both have bodies

        mangleWrangle(fd2, fd);

        if (fd.fbody)                   // fd is the definition
        {
            if (log) printf(" replace existing with new\n");
            sds.symtab.update(fd);      // replace fd2 in symbol table with fd
            fd.overnext = fd2;

            /* If fd2 is covering a tag symbol, then fd has to cover the same one
             */
            auto ps = cast(void*)fd2 in sc._module.tagSymTab;
            if (ps)
                sc._module.tagSymTab[cast(void*)fd] = *ps;

            return fd;
        }

        /* Just like with VarDeclaration, the types should match, which needs semantic() to be run on it.
         * FuncDeclaration::semantic() detects this, but it relies on .overnext being set.
         */
        fd2.overloadInsert(fd);

        return fd2;
    }

    auto td  = s.isAliasDeclaration();  // new declaration
    auto td2 = s2.isAliasDeclaration(); // existing declaration
    if (td && td2)
    {
        /* BUG: just like with variables and functions, the types should match, which needs semantic() to be run on it.
         * FuncDeclaration::semantic2() can detect this, but it relies overnext being set.
         */
        return td2;
    }

    return collision();
}

/*********************************
 * ImportC-specific semantic analysis on enum declaration `ed`
 */
void cEnumSemantic(Scope* sc, EnumDeclaration ed)
{
    // 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;
    // Set semantic2done to mark C enums as fully processed
    // Prevents issues with final switch statements that reference C enums
    ed.semanticRun = PASS.semantic2done;
    return;
}
