/**
 * Manage flow analysis for constructors.
 *
 * Copyright:   Copyright (C) 1999-2022 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/ctorflow.d, _ctorflow.d)
 * Documentation:  https://dlang.org/phobos/dmd_ctorflow.html
 * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/ctorflow.d
 */

module dmd.ctorflow;

import core.stdc.stdio;

import dmd.root.rmem;
import dmd.globals : Loc;

enum CSX : ushort
{
    none            = 0,
    this_ctor       = 0x01,     /// called this()
    super_ctor      = 0x02,     /// called super()
    label           = 0x04,     /// seen a label
    return_         = 0x08,     /// seen a return statement
    any_ctor        = 0x10,     /// either this() or super() was called
    halt            = 0x20,     /// assert(0)
}

/// Individual field in the Ctor with information about its callees and location.
struct FieldInit
{
    CSX csx; /// information about the field's callees
    Loc loc; /// location of the field initialization
}

/***********
 * Primitive flow analysis for constructors
 */
struct CtorFlow
{
    CSX callSuper;      /// state of calling other constructors

    FieldInit[] fieldinit;    /// state of field initializations

    void allocFieldinit(size_t dim)
    {
        fieldinit = (cast(FieldInit*)mem.xcalloc(FieldInit.sizeof, dim))[0 .. dim];
    }

    void freeFieldinit()
    {
        if (fieldinit.ptr)
            mem.xfree(fieldinit.ptr);

        fieldinit = null;
    }

    /***********************
     * Create a deep copy of `this`
     * Returns:
     *  a copy
     */
    CtorFlow clone()
    {
        return CtorFlow(callSuper, fieldinit.arraydup);
    }

    /**********************************
     * Set CSX bits in flow analysis state
     * Params:
     *  csx = bits to set
     */
    void orCSX(CSX csx) nothrow pure
    {
        callSuper |= csx;
        foreach (ref u; fieldinit)
            u.csx |= csx;
    }

    /******************************
     * OR CSX bits to `this`
     * Params:
     *  ctorflow = bits to OR in
     */
    void OR(const ref CtorFlow ctorflow) pure nothrow
    {
        callSuper |= ctorflow.callSuper;
        if (fieldinit.length && ctorflow.fieldinit.length)
        {
            assert(fieldinit.length == ctorflow.fieldinit.length);
            foreach (i, u; ctorflow.fieldinit)
            {
                auto fi = &fieldinit[i];
                fi.csx |= u.csx;
                if (fi.loc is Loc.init)
                    fi.loc = u.loc;
            }
        }
    }
}


/****************************************
 * Merge `b` flow analysis results into `a`.
 * Params:
 *      a = the path to merge `b` into
 *      b = the other path
 * Returns:
 *      false means one of the paths skips construction
 */
bool mergeCallSuper(ref CSX a, const CSX b) pure nothrow
{
    // 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 `a` and `b`; the result is merged into `a`.
    if (b == a)
        return true;

    // Have ALL branches called a constructor?
    const aAll = (a & (CSX.this_ctor | CSX.super_ctor)) != 0;
    const bAll = (b & (CSX.this_ctor | CSX.super_ctor)) != 0;
    // Have ANY branches called a constructor?
    const aAny = (a & CSX.any_ctor) != 0;
    const bAny = (b & CSX.any_ctor) != 0;
    // Have any branches returned?
    const aRet = (a & CSX.return_) != 0;
    const bRet = (b & CSX.return_) != 0;
    // Have any branches halted?
    const aHalt = (a & CSX.halt) != 0;
    const bHalt = (b & CSX.halt) != 0;
    if (aHalt && bHalt)
    {
        a = CSX.halt;
    }
    else if ((!bHalt && bRet && !bAny && aAny) || (!aHalt && aRet && !aAny && bAny))
    {
        // If one has returned without a constructor call, there must not
        // be ctor calls in the other.
        return false;
    }
    else if (bHalt || bRet && bAll)
    {
        // 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).
        a |= b & (CSX.any_ctor | CSX.label);
    }
    else if (aHalt || aRet && aAll)
    {
        a = cast(CSX)(b | (a & (CSX.any_ctor | CSX.label)));
    }
    else if (aAll != bAll) // both branches must have called ctors, or both not
        return false;
    else
    {
        // If one returned without a ctor, remember that
        if (bRet && !bAny)
            a |= CSX.return_;
        a |= b & (CSX.any_ctor | CSX.label);
    }
    return true;
}


/****************************************
 * Merge `b` flow analysis results into `a`.
 * Params:
 *      a = the path to merge `b` into
 *      b = the other path
 * Returns:
 *      false means either `a` or `b` skips initialization
 */
bool mergeFieldInit(ref CSX a, const CSX b) pure nothrow
{
    if (b == a)
        return true;

    // Have any branches returned?
    const aRet = (a & CSX.return_) != 0;
    const bRet = (b & CSX.return_) != 0;
    // Have any branches halted?
    const aHalt = (a & CSX.halt) != 0;
    const bHalt = (b & CSX.halt) != 0;

    if (aHalt && bHalt)
    {
        a = CSX.halt;
        return true;
    }

    // The logic here is to prefer the branch that neither halts nor returns.
    bool ok;
    if (!bHalt && bRet)
    {
        // Branch b returns, no merging required.
        ok = (b & CSX.this_ctor);
    }
    else if (!aHalt && aRet)
    {
        // Branch a returns, but b doesn't, b takes precedence.
        ok = (a & CSX.this_ctor);
        a = b;
    }
    else if (bHalt)
    {
        // Branch b halts, no merging required.
        ok = (a & CSX.this_ctor);
    }
    else if (aHalt)
    {
        // Branch a halts, but b doesn't, b takes precedence.
        ok = (b & CSX.this_ctor);
        a = b;
    }
    else
    {
        // Neither branch returns nor halts, merge flags.
        ok = !((a ^ b) & CSX.this_ctor);
        a |= b;
    }
    return ok;
}
