<commit_before>//------------------------------------------------------------------------------
// Expression.cpp
// Expression creation and analysis
//
// File is under the MIT license; see LICENSE for details
//------------------------------------------------------------------------------
#include "slang/binding/Expression.h"

#include "slang/binding/SystemSubroutine.h"
#include "slang/compilation/Compilation.h"
#include "slang/diagnostics/ExpressionsDiags.h"
#include "slang/diagnostics/LookupDiags.h"
#include "slang/symbols/ASTSerializer.h"
#include "slang/symbols/ASTVisitor.h"
#include "slang/syntax/AllSyntax.h"
#include "slang/syntax/SyntaxVisitor.h"
#include "slang/types/Type.h"

namespace {

using namespace slang;

struct EvalVisitor {
    template<typename T>
    ConstantValue visit(const T& expr, EvalContext& context) {
        if (expr.bad())
            return nullptr;

        if (expr.constant)
            return *expr.constant;

        ConstantValue cv = expr.evalImpl(context);
        if (cv && context.cacheResults()) {
            expr.constant = context.compilation.allocConstant(std::move(cv));
            return *expr.constant;
        }

        return cv;
    }

    ConstantValue visitInvalid(const Expression&, EvalContext&) { return nullptr; }
};

class LValueVisitor {
    template<typename T, typename Arg>
    using evalLValue_t = decltype(std::declval<T>().evalLValueImpl(std::declval<Arg>()));

public:
    template<typename T>
    LValue visit(const T& expr, EvalContext& context) {
        if constexpr (is_detected_v<evalLValue_t, T, EvalContext&>) {
            if (expr.bad())
                return nullptr;

            return expr.evalLValueImpl(context);
        }
        else {
            (void)expr;
            (void)context;
            THROW_UNREACHABLE;
        }
    }

    LValue visitInvalid(const Expression&, EvalContext&) { return nullptr; }
};

struct VerifyVisitor {
    template<typename T>
    bool visit(const T& expr, EvalContext& context) {
        if (expr.bad())
            return false;

        return expr.verifyConstantImpl(context);
    }

    bool visitInvalid(const Expression&, EvalContext&) { return false; }
};

class EffectiveWidthVisitor {
    template<typename T>
    using getEffectiveWidth_t = decltype(std::declval<T>().getEffectiveWidthImpl());

public:
    template<typename T>
    optional<bitwidth_t> visit(const T& expr) {
        if constexpr (is_detected_v<getEffectiveWidth_t, T>) {
            if (expr.bad())
                return std::nullopt;

            return expr.getEffectiveWidthImpl();
        }
        else {
            return expr.type->getBitWidth();
        }
    }

    optional<bitwidth_t> visitInvalid(const Expression&) { return std::nullopt; }
};

} // namespace

namespace slang {

// This visitor handles inserting implicit conversions into an expression
// tree where necessary. SystemVerilog has an additional weird feature where
// the type of one branch of an expression tree can bubble up and then propagate
// back down a different branch, which is also implemented here.
struct Expression::PropagationVisitor {
    template<typename T, typename... Args>
    using propagate_t = decltype(std::declval<T>().propagateType(std::declval<Args>()...));

    const BindContext& context;
    const Type& newType;
    SourceLocation assignmentLoc;

    PropagationVisitor(const BindContext& context, const Type& newType,
                       SourceLocation assignmentLoc) :
        context(context),
        newType(newType), assignmentLoc(assignmentLoc) {}

    template<typename T>
    Expression& visit(T& expr) {
        if (expr.bad())
            return expr;

        if (newType.isError()) {
            expr.type = &newType;
            return expr;
        }

        // If the new type is equivalent to the old type, there's no need for a
        // conversion. Otherwise if both types are integral or both are real, we have to
        // check if the conversion should be pushed further down the tree. Otherwise we
        // should insert the implicit conversion here.
        bool needConversion = !newType.isEquivalent(*expr.type);
        if constexpr (is_detected_v<propagate_t, T, const BindContext&, const Type&>) {
            if ((newType.isFloating() && expr.type->isFloating()) ||
                (newType.isIntegral() && expr.type->isIntegral()) || newType.isString() ||
                expr.kind == ExpressionKind::OpenRange) {

                if (expr.propagateType(context, newType))
                    needConversion = false;
            }
        }

        Expression* result = &expr;
        if (needConversion) {
            if (assignmentLoc) {
                result = &ConversionExpression::makeImplicit(
                    context, newType, ConversionKind::Implicit, expr, assignmentLoc);
            }
            else {
                result = &ConversionExpression::makeImplicit(
                    context, newType, ConversionKind::Propagated, expr, expr.sourceRange.start());
            }
        }

        return *result;
    }

    Expression& visitInvalid(Expression& expr) { return expr; }
};

const InvalidExpression InvalidExpression::Instance(nullptr, ErrorType::Instance);

const Expression& Expression::bind(const ExpressionSyntax& syntax, const BindContext& context,
                                   bitmask<BindFlags> extraFlags) {
    const Expression& result =
        selfDetermined(context.scope.getCompilation(), syntax, context, extraFlags);
    return checkBindFlags(result, context.resetFlags(extraFlags));
}

const Expression& Expression::bindLValue(const ExpressionSyntax& lhs, const Type& rhs,
                                         SourceLocation location, const BindContext& context,
                                         bool isInout) {
    Compilation& comp = context.scope.getCompilation();

    // Create a placeholder expression that will carry the type of the rhs.
    // Nothing will ever actually look at this expression, it's there only
    // to fill the space in the created AssignmentExpression.
    SourceRange rhsRange{ location, location };
    auto rhsExpr = comp.emplace<EmptyArgumentExpression>(rhs, rhsRange);
    if (rhsExpr->bad())
        return badExpr(comp, nullptr);

    Expression* lhsExpr = (lhs.kind == SyntaxKind::StreamingConcatenationExpression && !isInout &&
                           !(context.instance && !context.instance->arrayPath.empty()))
                              ? &selfDetermined(comp, lhs, context, BindFlags::StreamingAllowed)
                              : &create(comp, lhs, context, BindFlags::None, rhsExpr->type);
    selfDetermined(context, lhsExpr);

    SourceRange lhsRange = lhs.sourceRange();
    auto& result = AssignmentExpression::fromComponents(
        comp, std::nullopt, /* nonBlocking */ false, *lhsExpr, *rhsExpr, lhsRange.start(),
        /* timingControl */ nullptr, lhsRange, context);

    return checkBindFlags(result, context);
}

const Expression& Expression::bindRValue(const Type& lhs, const ExpressionSyntax& rhs,
                                         SourceLocation location, const BindContext& context) {
    Compilation& comp = context.scope.getCompilation();

    bitmask<BindFlags> extraFlags = context.instance && !context.instance->arrayPath.empty()
                                        ? BindFlags::None
                                        : BindFlags::StreamingAllowed;
    Expression& expr = create(comp, rhs, context, extraFlags, &lhs);

    const Expression& result = convertAssignment(context, lhs, expr, location);
    return checkBindFlags(result, context);
}

const Expression& Expression::bindRefArg(const Type& lhs, bool isConstRef,
                                         const ExpressionSyntax& rhs, SourceLocation location,
                                         const BindContext& context) {
    Compilation& comp = context.scope.getCompilation();
    Expression& expr = selfDetermined(comp, rhs, context);

    if (!expr.canConnectToRefArg(isConstRef)) {
        // If we can't bind to ref but we can bind to 'const ref', issue a more
        // specific error about constness.
        DiagCode code = diag::InvalidRefArg;
        if (!isConstRef && expr.canConnectToRefArg(true))
            code = diag::ConstVarToRef;

        context.addDiag(code, location) << expr.sourceRange;
        return badExpr(comp, &expr);
    }<commit_msg>ref binding: Check for error types before reporting more errors<commit_after>//------------------------------------------------------------------------------
// Expression.cpp
// Expression creation and analysis
//
// File is under the MIT license; see LICENSE for details
//------------------------------------------------------------------------------
#include "slang/binding/Expression.h"

#include "slang/binding/SystemSubroutine.h"
#include "slang/compilation/Compilation.h"
#include "slang/diagnostics/ExpressionsDiags.h"
#include "slang/diagnostics/LookupDiags.h"
#include "slang/symbols/ASTSerializer.h"
#include "slang/symbols/ASTVisitor.h"
#include "slang/syntax/AllSyntax.h"
#include "slang/syntax/SyntaxVisitor.h"
#include "slang/types/Type.h"

namespace {

using namespace slang;

struct EvalVisitor {
    template<typename T>
    ConstantValue visit(const T& expr, EvalContext& context) {
        if (expr.bad())
            return nullptr;

        if (expr.constant)
            return *expr.constant;

        ConstantValue cv = expr.evalImpl(context);
        if (cv && context.cacheResults()) {
            expr.constant = context.compilation.allocConstant(std::move(cv));
            return *expr.constant;
        }

        return cv;
    }

    ConstantValue visitInvalid(const Expression&, EvalContext&) { return nullptr; }
};

class LValueVisitor {
    template<typename T, typename Arg>
    using evalLValue_t = decltype(std::declval<T>().evalLValueImpl(std::declval<Arg>()));

public:
    template<typename T>
    LValue visit(const T& expr, EvalContext& context) {
        if constexpr (is_detected_v<evalLValue_t, T, EvalContext&>) {
            if (expr.bad())
                return nullptr;

            return expr.evalLValueImpl(context);
        }
        else {
            (void)expr;
            (void)context;
            THROW_UNREACHABLE;
        }
    }

    LValue visitInvalid(const Expression&, EvalContext&) { return nullptr; }
};

struct VerifyVisitor {
    template<typename T>
    bool visit(const T& expr, EvalContext& context) {
        if (expr.bad())
            return false;

        return expr.verifyConstantImpl(context);
    }

    bool visitInvalid(const Expression&, EvalContext&) { return false; }
};

class EffectiveWidthVisitor {
    template<typename T>
    using getEffectiveWidth_t = decltype(std::declval<T>().getEffectiveWidthImpl());

public:
    template<typename T>
    optional<bitwidth_t> visit(const T& expr) {
        if constexpr (is_detected_v<getEffectiveWidth_t, T>) {
            if (expr.bad())
                return std::nullopt;

            return expr.getEffectiveWidthImpl();
        }
        else {
            return expr.type->getBitWidth();
        }
    }

    optional<bitwidth_t> visitInvalid(const Expression&) { return std::nullopt; }
};

} // namespace

namespace slang {

// This visitor handles inserting implicit conversions into an expression
// tree where necessary. SystemVerilog has an additional weird feature where
// the type of one branch of an expression tree can bubble up and then propagate
// back down a different branch, which is also implemented here.
struct Expression::PropagationVisitor {
    template<typename T, typename... Args>
    using propagate_t = decltype(std::declval<T>().propagateType(std::declval<Args>()...));

    const BindContext& context;
    const Type& newType;
    SourceLocation assignmentLoc;

    PropagationVisitor(const BindContext& context, const Type& newType,
                       SourceLocation assignmentLoc) :
        context(context),
        newType(newType), assignmentLoc(assignmentLoc) {}

    template<typename T>
    Expression& visit(T& expr) {
        if (expr.bad())
            return expr;

        if (newType.isError()) {
            expr.type = &newType;
            return expr;
        }

        // If the new type is equivalent to the old type, there's no need for a
        // conversion. Otherwise if both types are integral or both are real, we have to
        // check if the conversion should be pushed further down the tree. Otherwise we
        // should insert the implicit conversion here.
        bool needConversion = !newType.isEquivalent(*expr.type);
        if constexpr (is_detected_v<propagate_t, T, const BindContext&, const Type&>) {
            if ((newType.isFloating() && expr.type->isFloating()) ||
                (newType.isIntegral() && expr.type->isIntegral()) || newType.isString() ||
                expr.kind == ExpressionKind::OpenRange) {

                if (expr.propagateType(context, newType))
                    needConversion = false;
            }
        }

        Expression* result = &expr;
        if (needConversion) {
            if (assignmentLoc) {
                result = &ConversionExpression::makeImplicit(
                    context, newType, ConversionKind::Implicit, expr, assignmentLoc);
            }
            else {
                result = &ConversionExpression::makeImplicit(
                    context, newType, ConversionKind::Propagated, expr, expr.sourceRange.start());
            }
        }

        return *result;
    }

    Expression& visitInvalid(Expression& expr) { return expr; }
};

const InvalidExpression InvalidExpression::Instance(nullptr, ErrorType::Instance);

const Expression& Expression::bind(const ExpressionSyntax& syntax, const BindContext& context,
                                   bitmask<BindFlags> extraFlags) {
    const Expression& result =
        selfDetermined(context.scope.getCompilation(), syntax, context, extraFlags);
    return checkBindFlags(result, context.resetFlags(extraFlags));
}

const Expression& Expression::bindLValue(const ExpressionSyntax& lhs, const Type& rhs,
                                         SourceLocation location, const BindContext& context,
                                         bool isInout) {
    Compilation& comp = context.scope.getCompilation();

    // Create a placeholder expression that will carry the type of the rhs.
    // Nothing will ever actually look at this expression, it's there only
    // to fill the space in the created AssignmentExpression.
    SourceRange rhsRange{ location, location };
    auto rhsExpr = comp.emplace<EmptyArgumentExpression>(rhs, rhsRange);
    if (rhsExpr->bad())
        return badExpr(comp, nullptr);

    Expression* lhsExpr = (lhs.kind == SyntaxKind::StreamingConcatenationExpression && !isInout &&
                           !(context.instance && !context.instance->arrayPath.empty()))
                              ? &selfDetermined(comp, lhs, context, BindFlags::StreamingAllowed)
                              : &create(comp, lhs, context, BindFlags::None, rhsExpr->type);
    selfDetermined(context, lhsExpr);

    SourceRange lhsRange = lhs.sourceRange();
    auto& result = AssignmentExpression::fromComponents(
        comp, std::nullopt, /* nonBlocking */ false, *lhsExpr, *rhsExpr, lhsRange.start(),
        /* timingControl */ nullptr, lhsRange, context);

    return checkBindFlags(result, context);
}

const Expression& Expression::bindRValue(const Type& lhs, const ExpressionSyntax& rhs,
                                         SourceLocation location, const BindContext& context) {
    Compilation& comp = context.scope.getCompilation();

    bitmask<BindFlags> extraFlags = context.instance && !context.instance->arrayPath.empty()
                                        ? BindFlags::None
                                        : BindFlags::StreamingAllowed;
    Expression& expr = create(comp, rhs, context, extraFlags, &lhs);

    const Expression& result = convertAssignment(context, lhs, expr, location);
    return checkBindFlags(result, context);
}

const Expression& Expression::bindRefArg(const Type& lhs, bool isConstRef,
                                         const ExpressionSyntax& rhs, SourceLocation location,
                                         const BindContext& context) {
    Compilation& comp = context.scope.getCompilation();
    Expression& expr = selfDetermined(comp, rhs, context);
    if (expr.bad())
        return expr;

    if (lhs.isError())
        return badExpr(comp, &expr);

    if (!expr.canConnectToRefArg(isConstRef)) {
        // If we can't bind to ref but we can bind to 'const ref', issue a more
        // specific error about constness.
        DiagCode code = diag::InvalidRefArg;
        if (!isConstRef && expr.canConnectToRefArg(true))
            code = diag::ConstVarToRef;

        context.addDiag(code, location) << expr.sourceRange;
        return badExpr(comp, &expr);
    }
{'fraction_non_alphanumeric': 0.11977275389481082, 'fraction_numerical': 0.0, 'mean_word_length': 2.3946322067594434, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 59, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 30, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2859872', 'n_tokens_mistral': 4492, 'n_tokens_neox': 4387, 'n_words': 1182}