// See the file "COPYING" in the main distribution directory for copyright.

#include "zeek/Expr.h"

#include "zeek/zeek-config.h"

#include "zeek/DebugLogger.h"
#include "zeek/Desc.h"
#include "zeek/Event.h"
#include "zeek/EventRegistry.h"
#include "zeek/Frame.h"
#include "zeek/Func.h"
#include "zeek/Hash.h"
#include "zeek/IPAddr.h"
#include "zeek/RE.h"
#include "zeek/RunState.h"
#include "zeek/Scope.h"
#include "zeek/Stmt.h"
#include "zeek/Traverse.h"
#include "zeek/Trigger.h"
#include "zeek/broker/Data.h"
#include "zeek/digest.h"
#include "zeek/module_util.h"
#include "zeek/script_opt/ExprOptInfo.h"

namespace zeek::detail
	{

static bool init_tag_check(const Expr* expr, const char* name, TypeTag expect_tag, TypeTag init_tag)
	{
	if ( expect_tag == init_tag )
		return true;

	auto msg = util::fmt("unexpected use of %s in '%s' initialization", name, type_name(init_tag));
	expr->Error(msg);
	return false;
	}

const char* expr_name(BroExprTag t)
	{
	static const char* expr_names[int(NUM_EXPRS)] = {
		"name",
		"const",
		"(*)",
		"++",
		"--",
		"!",
		"~",
		"+",
		"-",
		"+",
		"-",
		"+=",
		"-=",
		"*",
		"/",
		"%",
		"&",
		"|",
		"^",
		"&&",
		"||",
		"<",
		"<=",
		"==",
		"!=",
		">=",
		">",
		"?:",
		"ref",
		"=",
		"[]",
		"$",
		"?$",
		"[=]",
		"table()",
		"set()",
		"vector()",
		"$=",
		"in",
		"<<>>",
		"()",
		"function()",
		"event",
		"schedule",
		"coerce",
		"record_coerce",
		"table_coerce",
		"vector_coerce",
		"sizeof",
		"cast",
		"is",
		"[:]=",
		"inline()",
		"[]=",
		"$=",
		"vec+=",
		"to_any_coerce",
		"from_any_coerce",
		"from_any_vec_coerce",
		"any[]",
		"nop",

	};

	if ( int(t) >= NUM_EXPRS )
		{
		static char errbuf[512];

		// This isn't quite right - we return a static buffer,
		// so multiple calls to expr_name() could lead to confusion
		// by overwriting the buffer.  But oh well.
		snprintf(errbuf, sizeof(errbuf), "%d: not an expression tag", int(t));
		return errbuf;
		}

	return expr_names[int(t)];
	}

Expr::Expr(BroExprTag arg_tag) : tag(arg_tag), paren(false), type(nullptr)
	{
	SetLocationInfo(&start_location, &end_location);
	opt_info = new ExprOptInfo();
	}

Expr::~Expr()
	{
	delete opt_info;
	}

const ListExpr* Expr::AsListExpr() const
	{
	CHECK_TAG(tag, EXPR_LIST, "Expr::AsListExpr", expr_name)
	return (const ListExpr*)this;
	}

ListExpr* Expr::AsListExpr()
	{
	CHECK_TAG(tag, EXPR_LIST, "Expr::AsListExpr", expr_name)
	return (ListExpr*)this;
	}

ListExprPtr Expr::AsListExprPtr()
	{
	CHECK_TAG(tag, EXPR_LIST, "Expr::AsListExpr", expr_name)
	return {NewRef{}, (ListExpr*)this};
	}

const NameExpr* Expr::AsNameExpr() const
	{
	CHECK_TAG(tag, EXPR_NAME, "Expr::AsNameExpr", expr_name)
	return (const NameExpr*)this;
	}

NameExpr* Expr::AsNameExpr()
	{
	CHECK_TAG(tag, EXPR_NAME, "Expr::AsNameExpr", expr_name)
	return (NameExpr*)this;
	}

NameExprPtr Expr::AsNameExprPtr()
	{
	CHECK_TAG(tag, EXPR_NAME, "Expr::AsNameExpr", expr_name)
	return {NewRef{}, (NameExpr*)this};
	}

const ConstExpr* Expr::AsConstExpr() const
	{
	CHECK_TAG(tag, EXPR_CONST, "Expr::AsConstExpr", expr_name)
	return (const ConstExpr*)this;
	}

ConstExprPtr Expr::AsConstExprPtr()
	{
	CHECK_TAG(tag, EXPR_CONST, "Expr::AsConstExpr", expr_name)
	return {NewRef{}, (ConstExpr*)this};
	}

const CallExpr* Expr::AsCallExpr() const
	{
	CHECK_TAG(tag, EXPR_CALL, "Expr::AsCallExpr", expr_name)
	return (const CallExpr*)this;
	}

const AssignExpr* Expr::AsAssignExpr() const
	{
	CHECK_TAG(tag, EXPR_ASSIGN, "Expr::AsAssignExpr", expr_name)
	return (const AssignExpr*)this;
	}

AssignExpr* Expr::AsAssignExpr()
	{
	CHECK_TAG(tag, EXPR_ASSIGN, "Expr::AsAssignExpr", expr_name)
	return (AssignExpr*)this;
	}

const IndexExpr* Expr::AsIndexExpr() const
	{
	CHECK_TAG(tag, EXPR_INDEX, "Expr::AsIndexExpr", expr_name)
	return (const IndexExpr*)this;
	}

IndexExpr* Expr::AsIndexExpr()
	{
	CHECK_TAG(tag, EXPR_INDEX, "Expr::AsIndexExpr", expr_name)
	return (IndexExpr*)this;
	}

const EventExpr* Expr::AsEventExpr() const
	{
	CHECK_TAG(tag, EXPR_EVENT, "Expr::AsEventExpr", expr_name)
	return (const EventExpr*)this;
	}

EventExprPtr Expr::AsEventExprPtr()
	{
	CHECK_TAG(tag, EXPR_EVENT, "Expr::AsEventExpr", expr_name)
	return {NewRef{}, (EventExpr*)this};
	}

const RefExpr* Expr::AsRefExpr() const
	{
	CHECK_TAG(tag, EXPR_REF, "Expr::AsRefExpr", expr_name)
	return (const RefExpr*)this;
	}

RefExprPtr Expr::AsRefExprPtr()
	{
	CHECK_TAG(tag, EXPR_REF, "Expr::AsRefExpr", expr_name)
	return {NewRef{}, (RefExpr*)this};
	}

bool Expr::CanAdd() const
	{
	return false;
	}

bool Expr::CanDel() const
	{
	return false;
	}

void Expr::Add(Frame* /* f */)
	{
	Internal("Expr::Add called");
	}

void Expr::Delete(Frame* /* f */)
	{
	Internal("Expr::Delete called");
	}

ExprPtr Expr::MakeLvalue()
	{
	if ( ! IsError() )
		ExprError("can't be assigned to");

	return {NewRef{}, this};
	}

bool Expr::InvertSense()
	{
	return false;
	}

void Expr::EvalIntoAggregate(const zeek::Type* /* t */, Val* /* aggr */, Frame* /* f */) const
	{
	Internal("Expr::EvalIntoAggregate called");
	}

void Expr::Assign(Frame* /* f */, ValPtr /* v */)
	{
	Internal("Expr::Assign called");
	}

void Expr::AssignToIndex(ValPtr v1, ValPtr v2, ValPtr v3) const
	{
	bool iterators_invalidated;

	auto error_msg = assign_to_index(std::move(v1), std::move(v2), std::move(v3),
	                                 iterators_invalidated);

	if ( iterators_invalidated )
		{
		ODesc d;
		Describe(&d);
		reporter->PushLocation(GetLocationInfo());
		reporter->Warning("possible loop/iterator invalidation caused by expression: %s",
		                  d.Description());
		reporter->PopLocation();
		}

	if ( error_msg )
		RuntimeErrorWithCallStack(error_msg);
	}

static int get_slice_index(int idx, int len)
	{
	if ( abs(idx) > len )
		idx = idx > 0 ? len : 0; // Clamp maximum positive/negative indices.
	else if ( idx < 0 )
		idx += len; // Map to a positive index.

	return idx;
	}

const char* assign_to_index(ValPtr v1, ValPtr v2, ValPtr v3, bool& iterators_invalidated)
	{
	iterators_invalidated = false;

	if ( ! v1 || ! v2 || ! v3 )
		return nullptr;

	// Hold an extra reference in case the ownership transfer
	// to the table/vector goes wrong and we still want to obtain
	// diagnostic info from the original value after the assignment
	// already unref'd.
	auto v_extra = v3;

	switch ( v1->GetType()->Tag() )
		{
		case TYPE_VECTOR:
			{
			const ListVal* lv = v2->AsListVal();
			VectorVal* v1_vect = v1->AsVectorVal();

			if ( lv->Length() > 1 )
				{
				auto len = v1_vect->Size();
				bro_int_t first = get_slice_index(lv->Idx(0)->CoerceToInt(), len);
				bro_int_t last = get_slice_index(lv->Idx(1)->CoerceToInt(), len);

				// Remove the elements from the vector within the slice.
				for ( auto idx = first; idx < last; idx++ )
					v1_vect->Remove(first);

				// Insert the new elements starting at the first
				// position.

				VectorVal* v_vect = v3->AsVectorVal();

				for ( auto idx = 0u; idx < v_vect->Size(); idx++, first++ )
					v1_vect->Insert(first, v_vect->ValAt(idx));
				}

			else if ( ! v1_vect->Assign(lv->Idx(0)->CoerceToUnsigned(), std::move(v3)) )
				{
				v3 = std::move(v_extra);

				if ( v3 )
					{
					ODesc d;
					v3->Describe(&d);
					const auto& vt = v3->GetType();
					auto vtt = vt->Tag();
					std::string tn = vtt == TYPE_RECORD ? vt->GetName() : type_name(vtt);
					return util::fmt(
						"vector index assignment failed for invalid type '%s', value: %s",
						tn.data(), d.Description());
					}
				else
					return "assignment failed with null value";
				}
			break;
			}

		case TYPE_TABLE:
			{
			if ( ! v1->AsTableVal()->Assign(std::move(v2), std::move(v3), true,
			                                &iterators_invalidated) )
				{
				v3 = std::move(v_extra);

				if ( v3 )
					{
					ODesc d;
					v3->Describe(&d);
					const auto& vt = v3->GetType();
					auto vtt = vt->Tag();
					std::string tn = vtt == TYPE_RECORD ? vt->GetName() : type_name(vtt);
					return util::fmt(
						"table index assignment failed for invalid type '%s', value: %s", tn.data(),
						d.Description());
					}
				else
					return "assignment failed with null value";
				}

			break;
			}

		case TYPE_STRING:
			return "assignment via string index accessor not allowed";
			break;

		default:
			return "bad index expression type in assignment";
			break;
		}

	return nullptr;
	}

TypePtr Expr::InitType() const
	{
	return type;
	}

bool Expr::IsRecordElement(TypeDecl* /* td */) const
	{
	return false;
	}

bool Expr::IsPure() const
	{
	return true;
	}

ValPtr Expr::InitVal(const zeek::Type* t, ValPtr aggr) const
	{
	if ( aggr )
		{
		Error("bad initializer");
		return nullptr;
		}

	if ( IsError() )
		return nullptr;

	return check_and_promote(Eval(nullptr), t, true);
	}

bool Expr::IsError() const
	{
	return type && type->Tag() == TYPE_ERROR;
	}

void Expr::SetError()
	{
	SetType(error_type());
	}

void Expr::SetError(const char* msg)
	{
	Error(msg);
	SetError();
	}

bool Expr::IsZero() const
	{
	return IsConst() && ExprVal()->IsZero();
	}

bool Expr::IsOne() const
	{
	return IsConst() && ExprVal()->IsOne();
	}

void Expr::Describe(ODesc* d) const
	{
	if ( IsParen() && ! d->IsBinary() )
		d->Add("(");

	if ( d->IsPortable() || d->IsBinary() )
		AddTag(d);

	ExprDescribe(d);

	if ( IsParen() && ! d->IsBinary() )
		d->Add(")");
	}

void Expr::AddTag(ODesc* d) const
	{
	if ( d->IsBinary() )
		d->Add(int(Tag()));
	else
		d->AddSP(expr_name(Tag()));
	}

void Expr::Canonicize() { }

void Expr::SetType(TypePtr t)
	{
	if ( ! type || type->Tag() != TYPE_ERROR )
		type = std::move(t);
	}

void Expr::ExprError(const char msg[])
	{
	Error(msg);
	SetError();
	}

void Expr::RuntimeError(const std::string& msg) const
	{
	reporter->ExprRuntimeError(this, "%s", msg.data());
	}

void Expr::RuntimeErrorWithCallStack(const std::string& msg) const
	{
	auto rcs = render_call_stack();

	if ( rcs.empty() )
		reporter->ExprRuntimeError(this, "%s", msg.data());
	else
		{
		ODesc d;
		d.SetShort();
		Describe(&d);
		reporter->RuntimeError(GetLocationInfo(), "%s, expression: %s, call stack: %s", msg.data(),
		                       d.Description(), rcs.data());
		}
	}

NameExpr::NameExpr(IDPtr arg_id, bool const_init) : Expr(EXPR_NAME), id(std::move(arg_id))
	{
	in_const_init = const_init;

	if ( id->IsType() )
		SetType(make_intrusive<TypeType>(id->GetType()));
	else
		SetType(id->GetType());

	EventHandler* h = event_registry->Lookup(id->Name());
	if ( h )
		h->SetUsed();
	}

// This isn't in-lined to avoid needing to pull in ID.h.
const IDPtr& NameExpr::IdPtr() const
	{
	return id;
	}

ValPtr NameExpr::Eval(Frame* f) const
	{
	ValPtr v;

	if ( id->IsType() )
		return make_intrusive<TypeVal>(id->GetType(), true);

	if ( id->IsGlobal() )
		v = id->GetVal();

	else if ( f )
		v = f->GetElementByID(id);

	else
		// No frame - evaluating for purposes of resolving a
		// compile-time constant.
		return nullptr;

	if ( v )
		return v;
	else
		{
		RuntimeError("value used but not set");
		return nullptr;
		}
	}

ExprPtr NameExpr::MakeLvalue()
	{
	if ( id->IsType() )
		ExprError("Type name is not an lvalue");

	if ( id->IsConst() && ! in_const_init )
		ExprError("const is not a modifiable lvalue");

	if ( id->IsOption() && ! in_const_init )
		ExprError("option is not a modifiable lvalue");

	return make_intrusive<RefExpr>(IntrusivePtr{NewRef{}, this});
	}

void NameExpr::Assign(Frame* f, ValPtr v)
	{
	if ( id->IsGlobal() )
		id->SetVal(std::move(v));
	else
		f->SetElement(id, std::move(v));
	}

bool NameExpr::IsPure() const
	{
	return id->IsConst();
	}

TraversalCode NameExpr::Traverse(TraversalCallback* cb) const
	{
	TraversalCode tc = cb->PreExpr(this);
	HANDLE_TC_EXPR_PRE(tc);

	tc = id->Traverse(cb);
	HANDLE_TC_EXPR_PRE(tc);

	tc = cb->PostExpr(this);
	HANDLE_TC_EXPR_POST(tc);
	}

void NameExpr::ExprDescribe(ODesc* d) const
	{
	if ( d->IsReadable() )
		d->Add(id->Name());
	else
		{
		if ( d->IsPortable() )
			d->Add(id->Name());
		else
			d->AddCS(id->Name());
		}
	}

ConstExpr::ConstExpr(ValPtr arg_val) : Expr(EXPR_CONST), val(std::move(arg_val))
	{
	if ( val )
		{
		if ( val->GetType()->Tag() == TYPE_LIST && val->AsListVal()->Length() == 1 )
			val = val->AsListVal()->Idx(0);

		SetType(val->GetType());
		}
	else
		SetError();
	}

void ConstExpr::ExprDescribe(ODesc* d) const
	{
	val->Describe(d);
	}

ValPtr ConstExpr::Eval(Frame* /* f */) const
	{
	return {NewRef{}, Value()};
	}

TraversalCode ConstExpr::Traverse(TraversalCallback* cb) const
	{
	TraversalCode tc = cb->PreExpr(this);
	HANDLE_TC_EXPR_PRE(tc);

	tc = cb->PostExpr(this);
	HANDLE_TC_EXPR_POST(tc);
	}

UnaryExpr::UnaryExpr(BroExprTag arg_tag, ExprPtr arg_op) : Expr(arg_tag), op(std::move(arg_op))
	{
	if ( op->IsError() )
		SetError();
	}

ValPtr UnaryExpr::Eval(Frame* f) const
	{
	if ( IsError() )
		return nullptr;

	auto v = op->Eval(f);

	if ( ! v )
		return nullptr;

	if ( is_vector(v) && Tag() != EXPR_IS && Tag() != EXPR_CAST &&
	     // The following allows passing vectors-by-reference to
	     // functions that use vector-of-any for generic vector
	     // manipulation ...
	     Tag() != EXPR_TO_ANY_COERCE &&
	     // ... and the following to avoid vectorizing operations
	     // on vector-of-any's
	     Tag() != EXPR_FROM_ANY_COERCE )
		{
		VectorVal* v_op = v->AsVectorVal();
		VectorTypePtr out_t;

		if ( GetType()->Tag() == TYPE_ANY )
			out_t = v->GetType<VectorType>();
		else
			out_t = GetType<VectorType>();

		auto result = make_intrusive<VectorVal>(std::move(out_t));

		for ( unsigned int i = 0; i < v_op->Size(); ++i )
			{
			auto vop = v_op->ValAt(i);
			if ( vop )
				result->Assign(i, Fold(vop.get()));
			else
				result->Assign(i, nullptr);
			}

		return result;
		}
	else
		return Fold(v.get());
	}

bool UnaryExpr::IsPure() const
	{
	return op->IsPure();
	}

TraversalCode UnaryExpr::Traverse(TraversalCallback* cb) const
	{
	TraversalCode tc = cb->PreExpr(this);
	HANDLE_TC_EXPR_PRE(tc);

	tc = op->Traverse(cb);
	HANDLE_TC_EXPR_PRE(tc);

	tc = cb->PostExpr(this);
	HANDLE_TC_EXPR_POST(tc);
	}

ValPtr UnaryExpr::Fold(Val* v) const
	{
	return {NewRef{}, v};
	}

void UnaryExpr::ExprDescribe(ODesc* d) const
	{
	bool is_coerce = Tag() == EXPR_ARITH_COERCE || Tag() == EXPR_RECORD_COERCE ||
	                 Tag() == EXPR_TABLE_COERCE;

	if ( d->IsReadable() )
		{
		if ( is_coerce )
			d->Add("(coerce ");
		else if ( Tag() != EXPR_REF )
			d->Add(expr_name(Tag()));
		}

	op->Describe(d);

	if ( d->IsReadable() && is_coerce )
		{
		d->Add(" to ");
		GetType()->Describe(d);
		d->Add(")");
		}
	}

ValPtr BinaryExpr::Eval(Frame* f) const
	{
	if ( IsError() )
		return nullptr;

	auto v1 = op1->Eval(f);

	if ( ! v1 )
		return nullptr;

	auto v2 = op2->Eval(f);

	if ( ! v2 )
		return nullptr;

	bool is_vec1 = is_vector(v1);
	bool is_vec2 = is_vector(v2);

	if ( is_vec1 && is_vec2 )
		{ // fold pairs of elements
		VectorVal* v_op1 = v1->AsVectorVal();
		VectorVal* v_op2 = v2->AsVectorVal();

		if ( v_op1->Size() != v_op2->Size() )
			{
			RuntimeError("vector operands are of different sizes");
			return nullptr;
			}

		auto v_result = make_intrusive<VectorVal>(GetType<VectorType>());

		for ( unsigned int i = 0; i < v_op1->Size(); ++i )
			{
			auto v1_i = v_op1->ValAt(i);
			auto v2_i = v_op2->ValAt(i);
			if ( v1_i && v2_i )
				v_result->Assign(i, Fold(v_op1->ValAt(i).get(), v_op2->ValAt(i).get()));
			else
				v_result->Assign(i, nullptr);
			}

		return v_result;
		}

	if ( IsVector(GetType()->Tag()) && (is_vec1 || is_vec2) )
		{ // fold vector against scalar
		VectorVal* vv = (is_vec1 ? v1 : v2)->AsVectorVal();
		auto v_result = make_intrusive<VectorVal>(GetType<VectorType>());

		for ( unsigned int i = 0; i < vv->Size(); ++i )
			{
			auto vv_i = vv->ValAt(i);
			if ( vv_i )
				v_result->Assign(i,
				                 is_vec1 ? Fold(vv_i.get(), v2.get()) : Fold(v1.get(), vv_i.get()));
			else
				v_result->Assign(i, nullptr);
			}

		return v_result;
		}

	// scalar op scalar
	return Fold(v1.get(), v2.get());
	}

bool BinaryExpr::IsPure() const
	{
	return op1->IsPure() && op2->IsPure();
	}

TraversalCode BinaryExpr::Traverse(TraversalCallback* cb) const
	{
	TraversalCode tc = cb->PreExpr(this);
	HANDLE_TC_EXPR_PRE(tc);

	tc = op1->Traverse(cb);
	HANDLE_TC_EXPR_PRE(tc);

	tc = op2->Traverse(cb);
	HANDLE_TC_EXPR_PRE(tc);

	tc = cb->PostExpr(this);
	HANDLE_TC_EXPR_POST(tc);
	}

void BinaryExpr::ExprDescribe(ODesc* d) const
	{
	op1->Describe(d);

	d->SP();
	if ( d->IsReadable() )
		d->AddSP(expr_name(Tag()));

	op2->Describe(d);
	}

ValPtr BinaryExpr::Fold(Val* v1, Val* v2) const
	{
	InternalTypeTag it = v1->GetType()->InternalType();

	if ( it == TYPE_INTERNAL_STRING )
		return StringFold(v1, v2);

	if ( v1->GetType()->Tag() == TYPE_PATTERN )
		return PatternFold(v1, v2);

	if ( v1->GetType()->IsSet() )
		return SetFold(v1, v2);

	if ( it == TYPE_INTERNAL_ADDR )
		return AddrFold(v1, v2);

	if ( it == TYPE_INTERNAL_SUBNET )
		return SubNetFold(v1, v2);

	bro_int_t i1 = 0, i2 = 0, i3 = 0;
	bro_uint_t u1 = 0, u2 = 0, u3 = 0;
	double d1 = 0.0, d2 = 0.0, d3 = 0.0;
	bool is_integral = false;
	bool is_unsigned = false;

	if ( it == TYPE_INTERNAL_INT )
		{
		i1 = v1->InternalInt();
		i2 = v2->InternalInt();
		is_integral = true;
		}
	else if ( it == TYPE_INTERNAL_UNSIGNED )
		{
		u1 = v1->InternalUnsigned();
		u2 = v2->InternalUnsigned();
		is_unsigned = true;
		}
	else if ( it == TYPE_INTERNAL_DOUBLE )
		{
		d1 = v1->InternalDouble();
		d2 = v2->InternalDouble();
		}
	else
		RuntimeErrorWithCallStack("bad type in BinaryExpr::Fold");

	switch ( tag )
		{
#define DO_INT_FOLD(op)                                                                            \
	if ( is_integral )                                                                             \
		i3 = i1 op i2;                                                                             \
	else if ( is_unsigned )                                                                        \
		u3 = u1 op u2;                                                                             \
	else                                                                                           \
		RuntimeErrorWithCallStack("bad type in BinaryExpr::Fold");

#define DO_UINT_FOLD(op)                                                                           \
	if ( is_unsigned )                                                                             \
		u3 = u1 op u2;                                                                             \
	else                                                                                           \
		RuntimeErrorWithCallStack("bad type in BinaryExpr::Fold");

#define DO_FOLD(op)                                                                                \
	if ( is_integral )                                                                             \
		i3 = i1 op i2;                                                                             \
	else if ( is_unsigned )                                                                        \
		u3 = u1 op u2;                                                                             \
	else                                                                                           \
		d3 = d1 op d2;

#define DO_INT_VAL_FOLD(op)                                                                        \
	if ( is_integral )                                                                             \
		i3 = i1 op i2;                                                                             \
	else if ( is_unsigned )                                                                        \
		i3 = u1 op u2;                                                                             \
	else                                                                                           \
		i3 = d1 op d2;

		case EXPR_ADD:
		case EXPR_ADD_TO:
			DO_FOLD(+);
			break;
		case EXPR_SUB:
		case EXPR_REMOVE_FROM:
			DO_FOLD(-);
			break;
		case EXPR_TIMES:
			DO_FOLD(*);
			break;
		case EXPR_DIVIDE:
			{
			if ( is_integral )
				{
				if ( i2 == 0 )
					RuntimeError("division by zero");

				i3 = i1 / i2;
				}

			else if ( is_unsigned )
				{
				if ( u2 == 0 )
					RuntimeError("division by zero");

				u3 = u1 / u2;
				}
			else
				{
				if ( d2 == 0 )
					RuntimeError("division by zero");

				d3 = d1 / d2;
				}
			}
			break;

		case EXPR_MOD:
			{
			if ( is_integral )
				{
				if ( i2 == 0 )
					RuntimeError("modulo by zero");

				i3 = i1 % i2;
				}

			else if ( is_unsigned )
				{
				if ( u2 == 0 )
					RuntimeError("modulo by zero");

				u3 = u1 % u2;
				}

			else
				RuntimeErrorWithCallStack("bad type in BinaryExpr::Fold");
			}

			break;

		case EXPR_AND:
			DO_UINT_FOLD(&);
			break;
		case EXPR_OR:
			DO_UINT_FOLD(|);
			break;
		case EXPR_XOR:
			DO_UINT_FOLD(^);
			break;

		case EXPR_AND_AND:
			DO_INT_FOLD(&&);
			break;
		case EXPR_OR_OR:
			DO_INT_FOLD(||);
			break;

		case EXPR_LT:
			DO_INT_VAL_FOLD(<);
			break;
		case EXPR_LE:
			DO_INT_VAL_FOLD(<=);
			break;
		case EXPR_EQ:
			DO_INT_VAL_FOLD(==);
			break;
		case EXPR_NE:
			DO_INT_VAL_FOLD(!=);
			break;
		case EXPR_GE:
			DO_INT_VAL_FOLD(>=);
			break;
		case EXPR_GT:
			DO_INT_VAL_FOLD(>);
			break;

		default:
			BadTag("BinaryExpr::Fold", expr_name(tag));
		}

	const auto& ret_type = IsVector(GetType()->Tag()) ? GetType()->Yield() : GetType();

	if ( ret_type->Tag() == TYPE_INTERVAL )
		return make_intrusive<IntervalVal>(d3);
	else if ( ret_type->Tag() == TYPE_TIME )
		return make_intrusive<TimeVal>(d3);
	else if ( ret_type->Tag() == TYPE_DOUBLE )
		return make_intrusive<DoubleVal>(d3);
	else if ( ret_type->InternalType() == TYPE_INTERNAL_UNSIGNED )
		return val_mgr->Count(u3);
	else if ( ret_type->Tag() == TYPE_BOOL )
		return val_mgr->Bool(i3);
	else
		return val_mgr->Int(i3);
	}

ValPtr BinaryExpr::StringFold(Val* v1, Val* v2) const
	{
	const String* s1 = v1->AsString();
	const String* s2 = v2->AsString();
	int result = 0;

	switch ( tag )
		{
#undef DO_FOLD
#define DO_FOLD(sense)                                                                             \
		{                                                                                          \
		result = Bstr_cmp(s1, s2) sense 0;                                                         \
		break;                                                                                     \
		}

		case EXPR_LT:
			DO_FOLD(<)
		case EXPR_LE:
			DO_FOLD(<=)
		case EXPR_EQ:
			DO_FOLD(==)
		case EXPR_NE:
			DO_FOLD(!=)
		case EXPR_GE:
			DO_FOLD(>=)
		case EXPR_GT:
			DO_FOLD(>)

		case EXPR_ADD:
		case EXPR_ADD_TO:
			{
			std::vector<const String*> strings;
			strings.push_back(s1);
			strings.push_back(s2);

			return make_intrusive<StringVal>(concatenate(strings));
			}

		default:
			BadTag("BinaryExpr::StringFold", expr_name(tag));
		}

	return val_mgr->Bool(result);
	}

ValPtr BinaryExpr::PatternFold(Val* v1, Val* v2) const
	{
	const RE_Matcher* re1 = v1->AsPattern();
	const RE_Matcher* re2 = v2->AsPattern();

	if ( tag != EXPR_AND && tag != EXPR_OR )
		BadTag("BinaryExpr::PatternFold");

	RE_Matcher* res = tag == EXPR_AND ? RE_Matcher_conjunction(re1, re2)
	                                  : RE_Matcher_disjunction(re1, re2);

	return make_intrusive<PatternVal>(res);
	}

ValPtr BinaryExpr::SetFold(Val* v1, Val* v2) const
	{
	TableVal* tv1 = v1->AsTableVal();
	TableVal* tv2 = v2->AsTableVal();
	bool res = false;

	switch ( tag )
		{
		case EXPR_AND:
			return tv1->Intersection(*tv2);

		case EXPR_OR:
			{
			auto rval = v1->Clone();

			if ( ! tv2->AddTo(rval.get(), false, false) )
				reporter->InternalError("set union failed to type check");

			return rval;
			}

		case EXPR_SUB:
			{
			auto rval = v1->Clone();

			if ( ! tv2->RemoveFrom(rval.get()) )
				reporter->InternalError("set difference failed to type check");

			return rval;
			}

		case EXPR_EQ:
			res = tv1->EqualTo(*tv2);
			break;

		case EXPR_NE:
			res = ! tv1->EqualTo(*tv2);
			break;

		case EXPR_LT:
			res = tv1->IsSubsetOf(*tv2) && tv1->Size() < tv2->Size();
			break;

		case EXPR_LE:
			res = tv1->IsSubsetOf(*tv2);
			break;

		case EXPR_GE:
		case EXPR_GT:
			// These should't happen due to canonicalization.
			reporter->InternalError("confusion over canonicalization in set comparison");
			break;

		default:
			BadTag("BinaryExpr::SetFold", expr_name(tag));
			return nullptr;
		}

	return val_mgr->Bool(res);
	}

ValPtr BinaryExpr::AddrFold(Val* v1, Val* v2) const
	{
	IPAddr a1 = v1->AsAddr();
	IPAddr a2 = v2->AsAddr();
	bool result = false;

	switch ( tag )
		{

		case EXPR_LT:
			result = a1 < a2;
			break;
		case EXPR_LE:
			result = a1 < a2 || a1 == a2;
			break;
		case EXPR_EQ:
			result = a1 == a2;
			break;
		case EXPR_NE:
			result = a1 != a2;
			break;
		case EXPR_GE:
			result = ! (a1 < a2);
			break;
		case EXPR_GT:
			result = (! (a1 < a2)) && (a1 != a2);
			break;

		default:
			BadTag("BinaryExpr::AddrFold", expr_name(tag));
		}

	return val_mgr->Bool(result);
	}

ValPtr BinaryExpr::SubNetFold(Val* v1, Val* v2) const
	{
	const IPPrefix& n1 = v1->AsSubNet();
	const IPPrefix& n2 = v2->AsSubNet();

	bool result = n1 == n2;

	if ( tag == EXPR_NE )
		result = ! result;

	return val_mgr->Bool(result);
	}

void BinaryExpr::SwapOps()
	{
	// We could check here whether the operator is commutative.
	using std::swap;
	swap(op1, op2);
	}

void BinaryExpr::PromoteOps(TypeTag t)
	{
	TypeTag bt1 = op1->GetType()->Tag();
	TypeTag bt2 = op2->GetType()->Tag();

	bool is_vec1 = IsVector(bt1);
	bool is_vec2 = IsVector(bt2);

	if ( is_vec1 )
		bt1 = op1->GetType()->AsVectorType()->Yield()->Tag();
	if ( is_vec2 )
		bt2 = op2->GetType()->AsVectorType()->Yield()->Tag();

	if ( (is_vec1 || is_vec2) && ! (is_vec1 && is_vec2) )
		reporter->Warning("mixing vector and scalar operands is deprecated");

	if ( bt1 != t )
		op1 = make_intrusive<ArithCoerceExpr>(op1, t);
	if ( bt2 != t )
		op2 = make_intrusive<ArithCoerceExpr>(op2, t);
	}

void BinaryExpr::PromoteType(TypeTag t, bool is_vector)
	{
	PromoteOps(t);

	if ( is_vector )
		SetType(make_intrusive<VectorType>(base_type(t)));
	else
		SetType(base_type(t));
	}

void BinaryExpr::PromoteForInterval(ExprPtr& op)
	{
	if ( is_vector(op1) || is_vector(op2) )
		SetType(make_intrusive<VectorType>(base_type(TYPE_INTERVAL)));
	else
		SetType(base_type(TYPE_INTERVAL));

	if ( op->GetType()->Tag() != TYPE_DOUBLE )
		op = make_intrusive<ArithCoerceExpr>(op, TYPE_DOUBLE);
	}

CloneExpr::CloneExpr(ExprPtr arg_op) : UnaryExpr(EXPR_CLONE, std::move(arg_op))
	{
	if ( IsError() )
		return;

	SetType(op->GetType());
	}

ValPtr CloneExpr::Eval(Frame* f) const
	{
	if ( IsError() )
		return nullptr;

	if ( auto v = op->Eval(f) )
		return Fold(v.get());

	return nullptr;
	}

ValPtr CloneExpr::Fold(Val* v) const
	{
	return v->Clone();
	}

IncrExpr::IncrExpr(BroExprTag arg_tag, ExprPtr arg_op) : UnaryExpr(arg_tag, arg_op->MakeLvalue())
	{
	if ( IsError() )
		return;

	const auto& t = op->GetType();

	if ( IsVector(t->Tag()) )
		{
		if ( ! IsIntegral(t->AsVectorType()->Yield()->Tag()) )
			ExprError("vector elements must be integral for increment operator");
		else
			{
			reporter->Warning("increment/decrement operations for vectors deprecated");
			SetType(t);
			}
		}
	else
		{
		if ( ! IsIntegral(t->Tag()) )
			ExprError("requires an integral operand");
		else
			SetType(t);
		}
	}

ValPtr IncrExpr::DoSingleEval(Frame* f, Val* v) const
	{
	bro_int_t k = v->CoerceToInt();

	if ( Tag() == EXPR_INCR )
		++k;
	else
		{
		--k;

		if ( k < 0 && v->GetType()->InternalType() == TYPE_INTERNAL_UNSIGNED )
			RuntimeError("count underflow");
		}

	const auto& ret_type = IsVector(GetType()->Tag()) ? GetType()->Yield() : GetType();

	if ( ret_type->Tag() == TYPE_INT )
		return val_mgr->Int(k);
	else
		return val_mgr->Count(k);
	}

ValPtr IncrExpr::Eval(Frame* f) const
	{
	auto v = op->Eval(f);

	if ( ! v )
		return nullptr;

	if ( is_vector(v) )
		{
		VectorValPtr v_vec{NewRef{}, v->AsVectorVal()};

		for ( unsigned int i = 0; i < v_vec->Size(); ++i )
			{
			auto elt = v_vec->ValAt(i);
			if ( elt )
				v_vec->Assign(i, DoSingleEval(f, elt.get()));
			}

		op->Assign(f, std::move(v_vec));
		return v;
		}
	else
		{
		auto new_v = DoSingleEval(f, v.get());
		op->Assign(f, new_v);
		return new_v;
		}
	}

bool IncrExpr::IsPure() const
	{
	return false;
	}

ComplementExpr::ComplementExpr(ExprPtr arg_op) : UnaryExpr(EXPR_COMPLEMENT, std::move(arg_op))
	{
	if ( IsError() )
		return;

	const auto& t = op->GetType();
	TypeTag bt = t->Tag();

	if ( bt != TYPE_COUNT )
		ExprError("requires \"count\" operand");
	else
		SetType(base_type(TYPE_COUNT));
	}

ValPtr ComplementExpr::Fold(Val* v) const
	{
	return val_mgr->Count(~v->InternalUnsigned());
	}

NotExpr::NotExpr(ExprPtr arg_op) : UnaryExpr(EXPR_NOT, std::move(arg_op))
	{
	if ( IsError() )
		return;

	TypeTag bt = op->GetType()->Tag();

	if ( ! IsIntegral(bt) && bt != TYPE_BOOL )
		ExprError("requires an integral or boolean operand");
	else
		SetType(base_type(TYPE_BOOL));
	}

ValPtr NotExpr::Fold(Val* v) const
	{
	return val_mgr->Bool(! v->InternalInt());
	}

PosExpr::PosExpr(ExprPtr arg_op) : UnaryExpr(EXPR_POSITIVE, std::move(arg_op))
	{
	if ( IsError() )
		return;

	const auto& t = IsVector(op->GetType()->Tag()) ? op->GetType()->Yield() : op->GetType();

	TypeTag bt = t->Tag();
	TypePtr base_result_type;

	if ( IsIntegral(bt) )
		// Promote count and counter to int.
		base_result_type = base_type(TYPE_INT);
	else if ( bt == TYPE_INTERVAL || bt == TYPE_DOUBLE )
		base_result_type = t;
	else
		ExprError("requires an integral or double operand");

	if ( is_vector(op) )
		SetType(make_intrusive<VectorType>(std::move(base_result_type)));
	else
		SetType(std::move(base_result_type));
	}

ValPtr PosExpr::Fold(Val* v) const
	{
	TypeTag t = v->GetType()->Tag();

	if ( t == TYPE_DOUBLE || t == TYPE_INTERVAL || t == TYPE_INT )
		return {NewRef{}, v};
	else
		return val_mgr->Int(v->CoerceToInt());
	}

NegExpr::NegExpr(ExprPtr arg_op) : UnaryExpr(EXPR_NEGATE, std::move(arg_op))
	{
	if ( IsError() )
		return;

	const auto& t = IsVector(op->GetType()->Tag()) ? op->GetType()->Yield() : op->GetType();

	TypeTag bt = t->Tag();
	TypePtr base_result_type;

	if ( IsIntegral(bt) )
		// Promote count and counter to int.
		base_result_type = base_type(TYPE_INT);
	else if ( bt == TYPE_INTERVAL || bt == TYPE_DOUBLE )
		base_result_type = t;
	else
		ExprError("requires an integral or double operand");

	if ( is_vector(op) )
		SetType(make_intrusive<VectorType>(std::move(base_result_type)));
	else
		SetType(std::move(base_result_type));
	}

ValPtr NegExpr::Fold(Val* v) const
	{
	if ( v->GetType()->Tag() == TYPE_DOUBLE )
		return make_intrusive<DoubleVal>(-v->InternalDouble());
	else if ( v->GetType()->Tag() == TYPE_INTERVAL )
		return make_intrusive<IntervalVal>(-v->InternalDouble());
	else
		return val_mgr->Int(-v->CoerceToInt());
	}

SizeExpr::SizeExpr(ExprPtr arg_op) : UnaryExpr(EXPR_SIZE, std::move(arg_op))
	{
	if ( IsError() )
		return;

	auto& t = op->GetType();

	if ( t->Tag() == TYPE_ANY )
		SetType(base_type(TYPE_ANY));
	else if ( t->Tag() == TYPE_FILE || t->Tag() == TYPE_SUBNET ||
	          t->InternalType() == TYPE_INTERNAL_DOUBLE )
		SetType(base_type(TYPE_DOUBLE));
	else
		SetType(base_type(TYPE_COUNT));
	}

ValPtr SizeExpr::Eval(Frame* f) const
	{
	auto v = op->Eval(f);

	if ( ! v )
		return nullptr;

	return Fold(v.get());
	}

ValPtr SizeExpr::Fold(Val* v) const
	{
	return v->SizeVal();
	}

AddExpr::AddExpr(ExprPtr arg_op1, ExprPtr arg_op2)
	: BinaryExpr(EXPR_ADD, std::move(arg_op1), std::move(arg_op2))
	{
	if ( IsError() )
		return;

	TypeTag bt1 = op1->GetType()->Tag();

	if ( IsVector(bt1) )
		bt1 = op1->GetType()->AsVectorType()->Yield()->Tag();

	TypeTag bt2 = op2->GetType()->Tag();

	if ( IsVector(bt2) )
		bt2 = op2->GetType()->AsVectorType()->Yield()->Tag();

	TypePtr base_result_type;

	if ( bt2 == TYPE_INTERVAL && (bt1 == TYPE_TIME || bt1 == TYPE_INTERVAL) )
		base_result_type = base_type(bt1);
	else if ( bt2 == TYPE_TIME && bt1 == TYPE_INTERVAL )
		base_result_type = base_type(bt2);
	else if ( BothArithmetic(bt1, bt2) )
		PromoteType(max_type(bt1, bt2), is_vector(op1) || is_vector(op2));
	else if ( BothString(bt1, bt2) )
		base_result_type = base_type(bt1);
	else
		ExprError("requires arithmetic operands");

	if ( base_result_type )
		{
		if ( is_vector(op1) || is_vector(op2) )
			SetType(make_intrusive<VectorType>(std::move(base_result_type)));
		else
			SetType(std::move(base_result_type));
		}
	}

void AddExpr::Canonicize()
	{
	if ( expr_greater(op2.get(), op1.get()) ||
	     (op1->GetType()->Tag() == TYPE_INTERVAL && op2->GetType()->Tag() == TYPE_TIME) ||
	     (op2->IsConst() && ! is_vector(op2->ExprVal()) && ! op1->IsConst()) )
		SwapOps();
	}

AddToExpr::AddToExpr(ExprPtr arg_op1, ExprPtr arg_op2)
	: BinaryExpr(EXPR_ADD_TO, is_vector(arg_op1) ? std::move(arg_op1) : arg_op1->MakeLvalue(),
                 std::move(arg_op2))
	{
	if ( IsError() )
		return;

	TypeTag bt1 = op1->GetType()->Tag();
	TypeTag bt2 = op2->GetType()->Tag();

	if ( BothArithmetic(bt1, bt2) )
		PromoteType(max_type(bt1, bt2), is_vector(op1) || is_vector(op2));
	else if ( BothString(bt1, bt2) || BothInterval(bt1, bt2) )
		SetType(base_type(bt1));

	else if ( IsVector(bt1) )
		{
		bt1 = op1->GetType()->AsVectorType()->Yield()->Tag();

		if ( IsArithmetic(bt1) )
			{
			if ( IsArithmetic(bt2) )
				{
				if ( bt2 != bt1 )
					op2 = make_intrusive<ArithCoerceExpr>(std::move(op2), bt1);

				SetType(op1->GetType());
				}

			else
				ExprError("appending non-arithmetic to arithmetic vector");
			}

		else if ( bt1 != bt2 && bt1 != TYPE_ANY )
			ExprError(
				util::fmt("incompatible vector append: %s and %s", type_name(bt1), type_name(bt2)));

		else
			SetType(op1->GetType());
		}

	else
		ExprError("requires two arithmetic or two string operands");
	}

ValPtr AddToExpr::Eval(Frame* f) const
	{
	auto v1 = op1->Eval(f);

	if ( ! v1 )
		return nullptr;

	auto v2 = op2->Eval(f);

	if ( ! v2 )
		return nullptr;

	if ( is_vector(v1) )
		{
		VectorVal* vv = v1->AsVectorVal();

		if ( ! vv->Assign(vv->Size(), v2) )
			RuntimeError("type-checking failed in vector append");

		return v1;
		}

	if ( auto result = Fold(v1.get(), v2.get()) )
		{
		op1->Assign(f, result);
		return result;
		}
	else
		return nullptr;
	}

SubExpr::SubExpr(ExprPtr arg_op1, ExprPtr arg_op2)
	: BinaryExpr(EXPR_SUB, std::move(arg_op1), std::move(arg_op2))
	{
	if ( IsError() )
		return;

	const auto& t1 = op1->GetType();
	const auto& t2 = op2->GetType();

	TypeTag bt1 = t1->Tag();
	if ( IsVector(bt1) )
		bt1 = t1->AsVectorType()->Yield()->Tag();

	TypeTag bt2 = t2->Tag();
	if ( IsVector(bt2) )
		bt2 = t2->AsVectorType()->Yield()->Tag();

	TypePtr base_result_type;

	if ( bt2 == TYPE_INTERVAL && (bt1 == TYPE_TIME || bt1 == TYPE_INTERVAL) )
		base_result_type = base_type(bt1);

	else if ( bt1 == TYPE_TIME && bt2 == TYPE_TIME )
		SetType(base_type(TYPE_INTERVAL));

	else if ( t1->IsSet() && t2->IsSet() )
		{
		if ( same_type(t1, t2) )
			SetType(op1->GetType());
		else
			ExprError("incompatible \"set\" operands");
		}

	else if ( BothArithmetic(bt1, bt2) )
		PromoteType(max_type(bt1, bt2), is_vector(op1) || is_vector(op2));

	else
		ExprError("requires arithmetic operands");

	if ( base_result_type )
		{
		if ( is_vector(op1) || is_vector(op2) )
			SetType(make_intrusive<VectorType>(std::move(base_result_type)));
		else
			SetType(std::move(base_result_type));
		}
	}

RemoveFromExpr::RemoveFromExpr(ExprPtr arg_op1, ExprPtr arg_op2)
	: BinaryExpr(EXPR_REMOVE_FROM, arg_op1->MakeLvalue(), std::move(arg_op2))
	{
	if ( IsError() )
		return;

	TypeTag bt1 = op1->GetType()->Tag();
	TypeTag bt2 = op2->GetType()->Tag();

	if ( BothArithmetic(bt1, bt2) )
		PromoteType(max_type(bt1, bt2), is_vector(op1) || is_vector(op2));
	else if ( BothInterval(bt1, bt2) )
		SetType(base_type(bt1));
	else
		ExprError("requires two arithmetic operands");
	}

ValPtr RemoveFromExpr::Eval(Frame* f) const
	{
	auto v1 = op1->Eval(f);

	if ( ! v1 )
		return nullptr;

	auto v2 = op2->Eval(f);

	if ( ! v2 )
		return nullptr;

	if ( auto result = Fold(v1.get(), v2.get()) )
		{
		op1->Assign(f, result);
		return result;
		}
	else
		return nullptr;
	}

TimesExpr::TimesExpr(ExprPtr arg_op1, ExprPtr arg_op2)
	: BinaryExpr(EXPR_TIMES, std::move(arg_op1), std::move(arg_op2))
	{
	if ( IsError() )
		return;

	Canonicize();

	TypeTag bt1 = op1->GetType()->Tag();

	if ( IsVector(bt1) )
		bt1 = op1->GetType()->AsVectorType()->Yield()->Tag();

	TypeTag bt2 = op2->GetType()->Tag();

	if ( IsVector(bt2) )
		bt2 = op2->GetType()->AsVectorType()->Yield()->Tag();

	if ( bt1 == TYPE_INTERVAL || bt2 == TYPE_INTERVAL )
		{
		if ( IsArithmetic(bt1) || IsArithmetic(bt2) )
			PromoteForInterval(IsArithmetic(bt1) ? op1 : op2);
		else
			ExprError("multiplication with interval requires arithmetic operand");
		}
	else if ( BothArithmetic(bt1, bt2) )
		PromoteType(max_type(bt1, bt2), is_vector(op1) || is_vector(op2));
	else
		ExprError("requires arithmetic operands");
	}

void TimesExpr::Canonicize()
	{
	if ( expr_greater(op2.get(), op1.get()) || op2->GetType()->Tag() == TYPE_INTERVAL ||
	     (op2->IsConst() && ! is_vector(op2->ExprVal()) && ! op1->IsConst()) )
		SwapOps();
	}

DivideExpr::DivideExpr(ExprPtr arg_op1, ExprPtr arg_op2)
	: BinaryExpr(EXPR_DIVIDE, std::move(arg_op1), std::move(arg_op2))
	{
	if ( IsError() )
		return;

	TypeTag bt1 = op1->GetType()->Tag();

	if ( IsVector(bt1) )
		bt1 = op1->GetType()->AsVectorType()->Yield()->Tag();

	TypeTag bt2 = op2->GetType()->Tag();

	if ( IsVector(bt2) )
		bt2 = op2->GetType()->AsVectorType()->Yield()->Tag();

	if ( bt1 == TYPE_INTERVAL || bt2 == TYPE_INTERVAL )
		{
		if ( IsArithmetic(bt1) || IsArithmetic(bt2) )
			PromoteForInterval(IsArithmetic(bt1) ? op1 : op2);
		else if ( bt1 == TYPE_INTERVAL && bt2 == TYPE_INTERVAL )
			{
			if ( is_vector(op1) || is_vector(op2) )
				SetType(make_intrusive<VectorType>(base_type(TYPE_DOUBLE)));
			else
				SetType(base_type(TYPE_DOUBLE));
			}
		else
			ExprError("division of interval requires arithmetic operand");
		}

	else if ( BothArithmetic(bt1, bt2) )
		PromoteType(max_type(bt1, bt2), is_vector(op1) || is_vector(op2));

	else if ( bt1 == TYPE_ADDR && ! is_vector(op2) && (bt2 == TYPE_COUNT || bt2 == TYPE_INT) )
		SetType(base_type(TYPE_SUBNET));

	else
		ExprError("requires arithmetic operands");
	}

ValPtr DivideExpr::AddrFold(Val* v1, Val* v2) const
	{
	uint32_t mask;

	if ( v2->GetType()->Tag() == TYPE_COUNT )
		mask = static_cast<uint32_t>(v2->InternalUnsigned());
	else
		mask = static_cast<uint32_t>(v2->InternalInt());

	auto& a = v1->AsAddr();

	if ( a.GetFamily() == IPv4 )
		{
		if ( mask > 32 )
			RuntimeError(util::fmt("bad IPv4 subnet prefix length: %" PRIu32, mask));
		}
	else
		{
		if ( mask > 128 )
			RuntimeError(util::fmt("bad IPv6 subnet prefix length: %" PRIu32, mask));
		}

	return make_intrusive<SubNetVal>(a, mask);
	}

ModExpr::ModExpr(ExprPtr arg_op1, ExprPtr arg_op2)
	: BinaryExpr(EXPR_MOD, std::move(arg_op1), std::move(arg_op2))
	{
	if ( IsError() )
		return;

	TypeTag bt1 = op1->GetType()->Tag();

	if ( IsVector(bt1) )
		bt1 = op1->GetType()->AsVectorType()->Yield()->Tag();

	TypeTag bt2 = op2->GetType()->Tag();

	if ( IsVector(bt2) )
		bt2 = op2->GetType()->AsVectorType()->Yield()->Tag();

	if ( BothIntegral(bt1, bt2) )
		PromoteType(max_type(bt1, bt2), is_vector(op1) || is_vector(op2));
	else
		ExprError("requires integral operands");
	}

BoolExpr::BoolExpr(BroExprTag arg_tag, ExprPtr arg_op1, ExprPtr arg_op2)
	: BinaryExpr(arg_tag, std::move(arg_op1), std::move(arg_op2))
	{
	if ( IsError() )
		return;

	TypeTag bt1 = op1->GetType()->Tag();

	if ( IsVector(bt1) )
		bt1 = op1->GetType()->AsVectorType()->Yield()->Tag();

	TypeTag bt2 = op2->GetType()->Tag();

	if ( IsVector(bt2) )
		bt2 = op2->GetType()->AsVectorType()->Yield()->Tag();

	if ( BothBool(bt1, bt2) )
		{
		if ( is_vector(op1) || is_vector(op2) )
			{
			if ( ! (is_vector(op1) && is_vector(op2)) )
				reporter->Warning("mixing vector and scalar operands is deprecated");
			SetType(make_intrusive<VectorType>(base_type(TYPE_BOOL)));
			}
		else
			SetType(base_type(TYPE_BOOL));
		}
	else
		ExprError("requires boolean operands");
	}

ValPtr BoolExpr::DoSingleEval(Frame* f, ValPtr v1, Expr* op2) const
	{
	if ( ! v1 )
		return nullptr;

	if ( tag == EXPR_AND_AND )
		{
		if ( v1->IsZero() )
			return v1;
		else
			return op2->Eval(f);
		}

	else
		{
		if ( v1->IsZero() )
			return op2->Eval(f);
		else
			return v1;
		}
	}

ValPtr BoolExpr::Eval(Frame* f) const
	{
	if ( IsError() )
		return nullptr;

	auto v1 = op1->Eval(f);

	if ( ! v1 )
		return nullptr;

	bool is_vec1 = is_vector(op1);
	bool is_vec2 = is_vector(op2);

	// Handle scalar op scalar
	if ( ! is_vec1 && ! is_vec2 )
		return DoSingleEval(f, std::move(v1), op2.get());

	// Handle scalar op vector  or  vector op scalar
	// We can't short-circuit everything since we need to eval
	// a vector in order to find out its length.
	if ( ! (is_vec1 && is_vec2) )
		{ // Only one is a vector.
		ValPtr scalar_v;
		VectorValPtr vector_v;

		if ( is_vec1 )
			{
			scalar_v = op2->Eval(f);
			vector_v = {AdoptRef{}, v1.release()->AsVectorVal()};
			}
		else
			{
			scalar_v = std::move(v1);
			vector_v = {AdoptRef{}, op2->Eval(f).release()->AsVectorVal()};
			}

		if ( ! scalar_v || ! vector_v )
			return nullptr;

		VectorValPtr result;

		// It's either an EXPR_AND_AND or an EXPR_OR_OR.
		bool is_and = (tag == EXPR_AND_AND);

		if ( scalar_v->IsZero() == is_and )
			{
			result = make_intrusive<VectorVal>(GetType<VectorType>());
			result->Resize(vector_v->Size());
			result->AssignRepeat(0, result->Size(), std::move(scalar_v));
			}
		else
			result = std::move(vector_v);

		return result;
		}

	// Only case remaining: both are vectors.
	auto v2 = op2->Eval(f);

	if ( ! v2 )
		return nullptr;

	VectorVal* vec_v1 = v1->AsVectorVal();
	VectorVal* vec_v2 = v2->AsVectorVal();

	if ( vec_v1->Size() != vec_v2->Size() )
		{
		RuntimeError("vector operands have different sizes");
		return nullptr;
		}

	auto result = make_intrusive<VectorVal>(GetType<VectorType>());
	result->Resize(vec_v1->Size());

	for ( unsigned int i = 0; i < vec_v1->Size(); ++i )
		{
		const auto op1 = vec_v1->BoolAt(i);
		const auto op2 = vec_v2->BoolAt(i);

		bool local_result = (tag == EXPR_AND_AND) ? (op1 && op2) : (op1 || op2);

		result->Assign(i, val_mgr->Bool(local_result));
		}

	return result;
	}

BitExpr::BitExpr(BroExprTag arg_tag, ExprPtr arg_op1, ExprPtr arg_op2)
	: BinaryExpr(arg_tag, std::move(arg_op1), std::move(arg_op2))
	{
	if ( IsError() )
		return;

	const auto& t1 = op1->GetType();
	const auto& t2 = op2->GetType();

	TypeTag bt1 = t1->Tag();

	if ( IsVector(bt1) )
		bt1 = t1->AsVectorType()->Yield()->Tag();

	TypeTag bt2 = t2->Tag();

	if ( IsVector(bt2) )
		bt2 = t2->AsVectorType()->Yield()->Tag();

	if ( (bt1 == TYPE_COUNT) && (bt2 == TYPE_COUNT) )
		{
		if ( is_vector(op1) || is_vector(op2) )
			SetType(make_intrusive<VectorType>(base_type(TYPE_COUNT)));
		else
			SetType(base_type(TYPE_COUNT));
		}

	else if ( bt1 == TYPE_PATTERN )
		{
		if ( bt2 != TYPE_PATTERN )
			ExprError("cannot mix pattern and non-pattern operands");
		else if ( tag == EXPR_XOR )
			ExprError("'^' operator does not apply to patterns");
		else
			SetType(base_type(TYPE_PATTERN));
		}

	else if ( t1->IsSet() && t2->IsSet() )
		{
		if ( same_type(t1, t2) )
			SetType(op1->GetType());
		else
			ExprError("incompatible \"set\" operands");
		}

	else
		ExprError("requires \"count\" or compatible \"set\" operands");
	}

EqExpr::EqExpr(BroExprTag arg_tag, ExprPtr arg_op1, ExprPtr arg_op2)
	: BinaryExpr(arg_tag, std::move(arg_op1), std::move(arg_op2))
	{
	if ( IsError() )
		return;

	Canonicize();

	const auto& t1 = op1->GetType();
	const auto& t2 = op2->GetType();

	TypeTag bt1 = t1->Tag();
	if ( IsVector(bt1) )
		bt1 = t1->AsVectorType()->Yield()->Tag();

	TypeTag bt2 = t2->Tag();
	if ( IsVector(bt2) )
		bt2 = t2->AsVectorType()->Yield()->Tag();

	if ( is_vector(op1) || is_vector(op2) )
		SetType(make_intrusive<VectorType>(base_type(TYPE_BOOL)));
	else
		SetType(base_type(TYPE_BOOL));

	if ( BothArithmetic(bt1, bt2) )
		PromoteOps(max_type(bt1, bt2));

	else if ( EitherArithmetic(bt1, bt2) &&
	          // Allow comparisons with zero.
	          ((bt1 == TYPE_TIME && op2->IsZero()) || (bt2 == TYPE_TIME && op1->IsZero())) )
		PromoteOps(TYPE_TIME);

	else if ( bt1 == bt2 )
		{
		switch ( bt1 )
			{
			case TYPE_BOOL:
			case TYPE_TIME:
			case TYPE_INTERVAL:
			case TYPE_STRING:
			case TYPE_PORT:
			case TYPE_ADDR:
			case TYPE_SUBNET:
			case TYPE_ERROR:
			case TYPE_FUNC:
				break;

			case TYPE_ENUM:
				if ( ! same_type(t1, t2) )
					ExprError("illegal enum comparison");
				break;

			case TYPE_TABLE:
				if ( t1->IsSet() && t2->IsSet() )
					{
					if ( ! same_type(t1, t2) )
						ExprError("incompatible sets in comparison");
					break;
					}

				// FALL THROUGH

			default:
				ExprError("illegal comparison");
			}
		}

	else if ( bt1 == TYPE_PATTERN && bt2 == TYPE_STRING )
		;

	else
		ExprError("type clash in comparison");
	}

void EqExpr::Canonicize()
	{
	if ( op2->GetType()->Tag() == TYPE_PATTERN )
		SwapOps();

	else if ( op1->GetType()->Tag() == TYPE_PATTERN )
		;

	else if ( expr_greater(op2.get(), op1.get()) )
		SwapOps();
	}

ValPtr EqExpr::Fold(Val* v1, Val* v2) const
	{
	if ( op1->GetType()->Tag() == TYPE_PATTERN )
		{
		auto re = v1->As<PatternVal*>();
		const String* s = v2->AsString();
		if ( tag == EXPR_EQ )
			return val_mgr->Bool(re->MatchExactly(s));
		else
			return val_mgr->Bool(! re->MatchExactly(s));
		}
	else if ( op1->GetType()->Tag() == TYPE_FUNC )
		{
		auto res = v1->AsFunc() == v2->AsFunc();
		return val_mgr->Bool(tag == EXPR_EQ ? res : ! res);
		}

	else
		return BinaryExpr::Fold(v1, v2);
	}

bool EqExpr::InvertSense()
	{
	tag = (tag == EXPR_EQ ? EXPR_NE : EXPR_EQ);
	return true;
	}

RelExpr::RelExpr(BroExprTag arg_tag, ExprPtr arg_op1, ExprPtr arg_op2)
	: BinaryExpr(arg_tag, std::move(arg_op1), std::move(arg_op2))
	{
	if ( IsError() )
		return;

	Canonicize();

	const auto& t1 = op1->GetType();
	const auto& t2 = op2->GetType();

	TypeTag bt1 = t1->Tag();
	if ( IsVector(bt1) )
		bt1 = t1->AsVectorType()->Yield()->Tag();

	TypeTag bt2 = t2->Tag();
	if ( IsVector(bt2) )
		bt2 = t2->AsVectorType()->Yield()->Tag();

	if ( is_vector(op1) || is_vector(op2) )
		SetType(make_intrusive<VectorType>(base_type(TYPE_BOOL)));
	else
		SetType(base_type(TYPE_BOOL));

	if ( BothArithmetic(bt1, bt2) )
		PromoteOps(max_type(bt1, bt2));

	else if ( t1->IsSet() && t2->IsSet() )
		{
		if ( ! same_type(t1, t2) )
			ExprError("incompatible sets in comparison");
		}

	else if ( bt1 != bt2 )
		ExprError("operands must be of the same type");

	else if ( bt1 != TYPE_TIME && bt1 != TYPE_INTERVAL && bt1 != TYPE_PORT && bt1 != TYPE_ADDR &&
	          bt1 != TYPE_STRING )
		ExprError("illegal comparison");
	}

void RelExpr::Canonicize()
	{
	if ( tag == EXPR_GT )
		{
		SwapOps();
		tag = EXPR_LT;
		}

	else if ( tag == EXPR_GE )
		{
		SwapOps();
		tag = EXPR_LE;
		}
	}

bool RelExpr::InvertSense()
	{
	switch ( tag )
		{
		case EXPR_LT:
			tag = EXPR_GE;
			return true;
		case EXPR_LE:
			tag = EXPR_GT;
			return true;
		case EXPR_GE:
			tag = EXPR_LT;
			return true;
		case EXPR_GT:
			tag = EXPR_LE;
			return true;

		default:
			return false;
		}
	}

CondExpr::CondExpr(ExprPtr arg_op1, ExprPtr arg_op2, ExprPtr arg_op3)
	: Expr(EXPR_COND), op1(std::move(arg_op1)), op2(std::move(arg_op2)), op3(std::move(arg_op3))
	{
	TypeTag bt1 = op1->GetType()->Tag();

	if ( IsVector(bt1) )
		bt1 = op1->GetType()->AsVectorType()->Yield()->Tag();

	if ( op1->IsError() || op2->IsError() || op3->IsError() )
		SetError();

	else if ( bt1 != TYPE_BOOL )
		ExprError("requires boolean conditional");

	else
		{
		TypeTag bt2 = op2->GetType()->Tag();
		TypeTag bt3 = op3->GetType()->Tag();

		if ( is_vector(op1) )
			{
			if ( ! (is_vector(op2) && is_vector(op3)) )
				{
				ExprError("vector conditional requires vector alternatives");
				return;
				}

			bt2 = op2->GetType()->AsVectorType()->Yield()->Tag();
			bt3 = op3->GetType()->AsVectorType()->Yield()->Tag();
			}

		if ( BothArithmetic(bt2, bt3) )
			{
			TypeTag t = max_type(bt2, bt3);
			if ( bt2 != t )
				op2 = make_intrusive<ArithCoerceExpr>(std::move(op2), t);
			if ( bt3 != t )
				op3 = make_intrusive<ArithCoerceExpr>(std::move(op3), t);

			if ( is_vector(op1) )
				SetType(make_intrusive<VectorType>(base_type(t)));
			else
				SetType(base_type(t));
			}

		else if ( bt2 != bt3 )
			ExprError("operands must be of the same type");

		else
			{
			if ( is_vector(op1) )
				{
				ExprError("vector conditional type clash between alternatives");
				return;
				}

			if ( bt2 == zeek::TYPE_TABLE )
				{
				auto tt2 = op2->GetType<TableType>();
				auto tt3 = op3->GetType<TableType>();

				if ( tt2->IsUnspecifiedTable() )
					op2 = make_intrusive<TableCoerceExpr>(std::move(op2), std::move(tt3));
				else if ( tt3->IsUnspecifiedTable() )
					op3 = make_intrusive<TableCoerceExpr>(std::move(op3), std::move(tt2));
				else if ( ! same_type(op2->GetType(), op3->GetType()) )
					ExprError("operands must be of the same type");
				}
			else if ( bt2 == zeek::TYPE_VECTOR )
				{
				auto vt2 = op2->GetType<VectorType>();
				auto vt3 = op3->GetType<VectorType>();

				if ( vt2->IsUnspecifiedVector() )
					op2 = make_intrusive<VectorCoerceExpr>(std::move(op2), std::move(vt3));
				else if ( vt3->IsUnspecifiedVector() )
					op3 = make_intrusive<VectorCoerceExpr>(std::move(op3), std::move(vt2));
				else if ( ! same_type(op2->GetType(), op3->GetType()) )
					ExprError("operands must be of the same type");
				}
			else if ( ! same_type(op2->GetType(), op3->GetType()) )
				// Records could potentially also coerce, but may have some cases
				// where the coercion direction is ambiguous.
				ExprError("operands must be of the same type");

			if ( ! IsError() )
				SetType(op2->GetType());
			}
		}
	}

ValPtr CondExpr::Eval(Frame* f) const
	{
	if ( ! is_vector(op1) )
		{
		// Scalar case
		auto false_eval = op1->Eval(f)->IsZero();
		return (false_eval ? op3 : op2)->Eval(f);
		}

	// Vector case: no mixed scalar/vector cases allowed
	auto v1 = op1->Eval(f);

	if ( ! v1 )
		return nullptr;

	auto v2 = op2->Eval(f);

	if ( ! v2 )
		return nullptr;

	auto v3 = op3->Eval(f);

	if ( ! v3 )
		return nullptr;

	VectorVal* cond = v1->AsVectorVal();
	VectorVal* a = v2->AsVectorVal();
	VectorVal* b = v3->AsVectorVal();

	if ( cond->Size() != a->Size() || a->Size() != b->Size() )
		{
		RuntimeError("vectors in conditional expression have different sizes");
		return nullptr;
		}

	auto result = make_intrusive<VectorVal>(GetType<VectorType>());
	result->Resize(cond->Size());

	for ( unsigned int i = 0; i < cond->Size(); ++i )
		{
		auto local_cond = cond->BoolAt(i);
		auto v = local_cond ? a->ValAt(i) : b->ValAt(i);
		result->Assign(i, v);
		}

	return result;
	}

bool CondExpr::IsPure() const
	{
	return op1->IsPure() && op2->IsPure() && op3->IsPure();
	}

TraversalCode CondExpr::Traverse(TraversalCallback* cb) const
	{
	TraversalCode tc = cb->PreExpr(this);
	HANDLE_TC_EXPR_PRE(tc);

	tc = op1->Traverse(cb);
	HANDLE_TC_EXPR_PRE(tc);

	tc = op2->Traverse(cb);
	HANDLE_TC_EXPR_PRE(tc);

	tc = op3->Traverse(cb);
	HANDLE_TC_EXPR_PRE(tc);

	tc = cb->PostExpr(this);
	HANDLE_TC_EXPR_POST(tc);
	}

void CondExpr::ExprDescribe(ODesc* d) const
	{
	op1->Describe(d);
	d->AddSP(" ?");
	op2->Describe(d);
	d->AddSP(" :");
	op3->Describe(d);
	}

RefExpr::RefExpr(ExprPtr arg_op) : UnaryExpr(EXPR_REF, std::move(arg_op))
	{
	if ( IsError() )
		return;

	if ( ! is_assignable(op->GetType()->Tag()) )
		ExprError("illegal assignment target");
	else
		SetType(op->GetType());
	}

ExprPtr RefExpr::MakeLvalue()
	{
	return {NewRef{}, this};
	}

void RefExpr::Assign(Frame* f, ValPtr v)
	{
	op->Assign(f, std::move(v));
	}

AssignExpr::AssignExpr(ExprPtr arg_op1, ExprPtr arg_op2, bool arg_is_init, ValPtr arg_val,
                       const AttributesPtr& attrs, bool typecheck)
	: BinaryExpr(EXPR_ASSIGN, arg_is_init ? std::move(arg_op1) : arg_op1->MakeLvalue(),
                 std::move(arg_op2))
	{
	val = nullptr;
	is_init = arg_is_init;

	if ( IsError() )
		return;

	if ( arg_val )
		SetType(arg_val->GetType());
	else
		SetType(op1->GetType());

	if ( is_init )
		{
		SetLocationInfo(op1->GetLocationInfo(), op2->GetLocationInfo());
		return;
		}

	if ( typecheck )
		// We discard the status from TypeCheck since it has already
		// generated error messages.
		(void)TypeCheck(attrs);

	val = std::move(arg_val);

	SetLocationInfo(op1->GetLocationInfo(), op2->GetLocationInfo());
	}

bool AssignExpr::TypeCheck(const AttributesPtr& attrs)
	{
	TypeTag bt1 = op1->GetType()->Tag();
	TypeTag bt2 = op2->GetType()->Tag();

	if ( bt1 == TYPE_LIST && bt2 == TYPE_ANY )
		// This is ok because we cannot explicitly declare lists on
		// the script level.
		return true;

	// This should be one of them, but not both (i.e. XOR)
	if ( ((bt1 == TYPE_ENUM) ^ (bt2 == TYPE_ENUM)) )
		{
		ExprError("can't convert to/from enumerated type");
		return false;
		}

	if ( IsArithmetic(bt1) )
		return TypeCheckArithmetics(bt1, bt2);

	if ( bt1 == TYPE_TIME && IsArithmetic(bt2) && op2->IsZero() )
		{ // Allow assignments to zero as a special case.
		op2 = make_intrusive<ArithCoerceExpr>(std::move(op2), bt1);
		return true;
		}

	if ( bt1 == TYPE_TABLE && bt2 == bt1 && op2->GetType()->AsTableType()->IsUnspecifiedTable() )
		{
		op2 = make_intrusive<TableCoerceExpr>(std::move(op2), op1->GetType<TableType>());
		return true;
		}

	if ( bt1 == TYPE_TABLE && op2->Tag() == EXPR_LIST )
		{
		std::unique_ptr<std::vector<AttrPtr>> attr_copy;

		if ( attrs )
			attr_copy = std::make_unique<std::vector<AttrPtr>>(attrs->GetAttrs());

		if ( op1->GetType()->IsSet() )
			op2 = make_intrusive<SetConstructorExpr>(cast_intrusive<ListExpr>(op2),
			                                         std::move(attr_copy), op1->GetType());
		else
			op2 = make_intrusive<TableConstructorExpr>(cast_intrusive<ListExpr>(op2),
			                                           std::move(attr_copy), op1->GetType());

		// The constructor expressions are performing the type
		// checking and will set op2 to an error state on failure.
		return ! op2->IsError();
		}

	if ( bt1 == TYPE_VECTOR )
		{
		if ( bt2 == bt1 && op2->GetType()->AsVectorType()->IsUnspecifiedVector() )
			{
			op2 = make_intrusive<VectorCoerceExpr>(std::move(op2), op1->GetType<VectorType>());
			return true;
			}

		if ( op2->Tag() == EXPR_LIST )
			{
			op2 = make_intrusive<VectorConstructorExpr>(
				IntrusivePtr{AdoptRef{}, op2.release()->AsListExpr()}, op1->GetType());
			return true;
			}
		}

	if ( op1->GetType()->Tag() == TYPE_RECORD && op2->GetType()->Tag() == TYPE_RECORD )
		{
		if ( same_type(op1->GetType(), op2->GetType()) )
			return true;

		// Need to coerce.
		op2 = make_intrusive<RecordCoerceExpr>(std::move(op2), op1->GetType<RecordType>());
		return true;
		}

	if ( ! same_type(op1->GetType(), op2->GetType()) )
		{
		if ( bt1 == TYPE_TABLE && bt2 == TYPE_TABLE )
			{
			if ( op2->Tag() == EXPR_SET_CONSTRUCTOR )
				{
				// Some elements in constructor list must not match, see if
				// we can create a new constructor now that the expected type
				// of LHS is known and let it do coercions where possible.
				SetConstructorExpr* sce = dynamic_cast<SetConstructorExpr*>(op2.get());

				if ( ! sce )
					{
					ExprError("Failed typecast to SetConstructorExpr");
					return false;
					}

				ListExpr* ctor_list = dynamic_cast<ListExpr*>(sce->Op());

				if ( ! ctor_list )
					{
					ExprError("Failed typecast to ListExpr");
					return false;
					}

				std::unique_ptr<std::vector<AttrPtr>> attr_copy;

				if ( sce->GetAttrs() )
					{
					const auto& a = sce->GetAttrs()->GetAttrs();
					attr_copy = std::make_unique<std::vector<AttrPtr>>(a);
					}

				int errors_before = reporter->Errors();
				op2 = make_intrusive<SetConstructorExpr>(IntrusivePtr{NewRef{}, ctor_list},
				                                         std::move(attr_copy), op1->GetType());
				int errors_after = reporter->Errors();

				if ( errors_after > errors_before )
					{
					ExprError("type clash in assignment");
					return false;
					}

				return true;
				}
			}

		ExprError("type clash in assignment");
		return false;
		}

	return true;
	}

bool AssignExpr::TypeCheckArithmetics(TypeTag bt1, TypeTag bt2)
	{
	if ( ! IsArithmetic(bt2) )
		{
		ExprError(util::fmt("assignment of non-arithmetic value to arithmetic (%s/%s)",
		                    type_name(bt1), type_name(bt2)));
		return false;
		}

	if ( bt1 == TYPE_DOUBLE )
		{
		PromoteOps(TYPE_DOUBLE);
		return true;
		}

	if ( bt2 == TYPE_DOUBLE )
		{
		Warn("dangerous assignment of double to integral");
		op2 = make_intrusive<ArithCoerceExpr>(std::move(op2), bt1);
		bt2 = op2->GetType()->Tag();
		}

	if ( bt1 == TYPE_INT )
		PromoteOps(TYPE_INT);
	else
		{
		if ( bt2 == TYPE_INT )
			{
			Warn("dangerous assignment of integer to count");
			op2 = make_intrusive<ArithCoerceExpr>(std::move(op2), bt1);
			}

		// Assignment of count to counter or vice
		// versa is allowed, and requires no
		// coercion.
		}

	return true;
	}

ValPtr AssignExpr::Eval(Frame* f) const
	{
	if ( is_init )
		{
		RuntimeError("illegal assignment in initialization");
		return nullptr;
		}

	if ( auto v = op2->Eval(f) )
		{
		op1->Assign(f, v);

		if ( val )
			return val;

		return v;
		}
	else
		return nullptr;
	}

TypePtr AssignExpr::InitType() const
	{
	if ( op1->Tag() != EXPR_LIST )
		{
		Error("bad initializer, first operand should be a list");
		return nullptr;
		}

	const auto& tl = op1->GetType();
	if ( tl->Tag() != TYPE_LIST )
		Internal("inconsistent list expr in AssignExpr::InitType");

	return make_intrusive<TableType>(IntrusivePtr{NewRef{}, tl->AsTypeList()}, op2->GetType());
	}

void AssignExpr::EvalIntoAggregate(const zeek::Type* t, Val* aggr, Frame* f) const
	{
	if ( IsError() )
		return;

	TypeDecl td;

	if ( IsRecordElement(&td) )
		{
		if ( t->Tag() != TYPE_RECORD )
			{
			RuntimeError("not a record initializer");
			return;
			}

		const RecordType* rt = t->AsRecordType();
		int field = rt->FieldOffset(td.id);

		if ( field < 0 )
			{
			RuntimeError("no such field");
			return;
			}

		RecordVal* aggr_r = aggr->AsRecordVal();

		auto v = op2->Eval(f);

		if ( v )
			aggr_r->Assign(field, std::move(v));

		return;
		}

	if ( op1->Tag() != EXPR_LIST )
		RuntimeError("bad table insertion");

	TableVal* tv = aggr->AsTableVal();

	auto index = op1->Eval(f);
	auto v = check_and_promote(op2->Eval(f), t->Yield().get(), true);

	if ( ! index || ! v )
		return;

	if ( ! tv->Assign(std::move(index), std::move(v)) )
		RuntimeError("type clash in table assignment");
	}

ValPtr AssignExpr::InitVal(const zeek::Type* t, ValPtr aggr) const
	{
	if ( ! aggr )
		{
		Error("assignment in initialization");
		return nullptr;
		}

	if ( IsError() )
		return nullptr;

	TypeDecl td;

	if ( IsRecordElement(&td) )
		{
		if ( t->Tag() != TYPE_RECORD )
			{
			Error("not a record initializer", t);
			return nullptr;
			}

		const RecordType* rt = t->AsRecordType();
		int field = rt->FieldOffset(td.id);

		if ( field < 0 )
			{
			Error("no such field");
			return nullptr;
			}

		if ( aggr->GetType()->Tag() != TYPE_RECORD )
			Internal("bad aggregate in AssignExpr::InitVal");

		RecordVal* aggr_r = aggr->AsRecordVal();

		auto v = op2->InitVal(rt->GetFieldType(td.id).get(), nullptr);

		if ( ! v )
			return nullptr;

		aggr_r->Assign(field, v);
		return v;
		}

	else if ( op1->Tag() == EXPR_LIST )
		{
		if ( t->Tag() != TYPE_TABLE )
			{
			Error("not a table initialization", t);
			return nullptr;
			}

		if ( aggr->GetType()->Tag() != TYPE_TABLE )
			Internal("bad aggregate in AssignExpr::InitVal");

		auto tv = cast_intrusive<TableVal>(std::move(aggr));
		const TableType* tt = tv->GetType()->AsTableType();
		const auto& yt = tv->GetType()->Yield();

		auto index = op1->InitVal(tt->GetIndices().get(), nullptr);

		if ( yt->Tag() == TYPE_RECORD )
			{
			if ( op2->GetType()->Tag() != TYPE_RECORD )
				{
				Error(util::fmt("type mismatch in table value initialization: "
				                "assigning '%s' to table with values of type '%s'",
				                type_name(op2->GetType()->Tag()), type_name(yt->Tag())));
				return nullptr;
				}

			if ( ! same_type(*yt, *op2->GetType()) &&
			     ! record_promotion_compatible(yt->AsRecordType(), op2->GetType()->AsRecordType()) )
				{
				Error("type mismatch in table value initialization: "
				      "incompatible record types");
				return nullptr;
				}
			}
		else
			{
			if ( ! same_type(*yt, *op2->GetType(), true) )
				{
				Error(util::fmt("type mismatch in table value initialization: "
				                "assigning '%s' to table with values of type '%s'",
				                type_name(op2->GetType()->Tag()), type_name(yt->Tag())));
				return nullptr;
				}
			}

		auto v = op2->InitVal(yt.get(), nullptr);

		if ( ! index || ! v )
			return nullptr;

		if ( ! tv->ExpandAndInit(std::move(index), std::move(v)) )
			return nullptr;

		return tv;
		}

	else
		{
		Error("illegal initializer");
		return nullptr;
		}
	}

bool AssignExpr::IsRecordElement(TypeDecl* td) const
	{
	if ( op1->Tag() == EXPR_NAME )
		{
		if ( td )
			{
			const NameExpr* n = (const NameExpr*)op1.get();
			td->type = op2->GetType();
			td->id = util::copy_string(n->Id()->Name());
			}

		return true;
		}

	return false;
	}

bool AssignExpr::IsPure() const
	{
	return false;
	}

IndexSliceAssignExpr::IndexSliceAssignExpr(ExprPtr op1, ExprPtr op2, bool is_init)
	: AssignExpr(std::move(op1), std::move(op2), is_init)
	{
	}

ValPtr IndexSliceAssignExpr::Eval(Frame* f) const
	{
	if ( is_init )
		{
		RuntimeError("illegal assignment in initialization");
		return nullptr;
		}

	if ( auto v = op2->Eval(f) )
		op1->Assign(f, std::move(v));

	return nullptr;
	}

IndexExpr::IndexExpr(ExprPtr arg_op1, ListExprPtr arg_op2, bool arg_is_slice)
	: BinaryExpr(EXPR_INDEX, std::move(arg_op1), std::move(arg_op2)), is_slice(arg_is_slice)
	{
	if ( IsError() )
		return;

	if ( is_slice )
		{
		if ( ! IsString(op1->GetType()->Tag()) && ! IsVector(op1->GetType()->Tag()) )
			ExprError("slice notation indexing only supported for strings and vectors currently");
		}

	else if ( IsString(op1->GetType()->Tag()) )
		{
		if ( op2->AsListExpr()->Exprs().length() != 1 )
			ExprError("invalid string index expression");
		}

	if ( IsError() )
		return;

	int match_type = op1->GetType()->MatchesIndex(op2->AsListExpr());

	if ( match_type == DOES_NOT_MATCH_INDEX )
		{
		std::string error_msg = util::fmt(
			"expression with type '%s' is not a type that can be indexed",
			type_name(op1->GetType()->Tag()));
		SetError(error_msg.data());
		}

	else if ( ! op1->GetType()->Yield() )
		{
		if ( IsString(op1->GetType()->Tag()) && match_type == MATCHES_INDEX_SCALAR )
			SetType(base_type(TYPE_STRING));
		else
			// It's a set - so indexing it yields void.  We don't
			// directly generate an error message, though, since this
			// expression might be part of an add/delete statement,
			// rather than yielding a value.
			SetType(base_type(TYPE_VOID));
		}

	else if ( match_type == MATCHES_INDEX_SCALAR )
		SetType(op1->GetType()->Yield());

	else if ( match_type == MATCHES_INDEX_VECTOR )
		SetType(make_intrusive<VectorType>(op1->GetType()->Yield()));

	else
		ExprError("Unknown MatchesIndex() return value");
	}

bool IndexExpr::CanAdd() const
	{
	if ( IsError() )
		return true; // avoid cascading the error report

	// "add" only allowed if our type is "set".
	return op1->GetType()->IsSet();
	}

bool IndexExpr::CanDel() const
	{
	if ( IsError() )
		return true; // avoid cascading the error report

	return op1->GetType()->Tag() == TYPE_TABLE;
	}

void IndexExpr::Add(Frame* f)
	{
	if ( IsError() )
		return;

	auto v1 = op1->Eval(f);

	if ( ! v1 )
		return;

	auto v2 = op2->Eval(f);

	if ( ! v2 )
		return;

	bool iterators_invalidated = false;
	v1->AsTableVal()->Assign(std::move(v2), nullptr, true, &iterators_invalidated);

	if ( iterators_invalidated )
		{
		ODesc d;
		Describe(&d);
		reporter->PushLocation(GetLocationInfo());
		reporter->Warning("possible loop/iterator invalidation caused by expression: %s",
		                  d.Description());
		reporter->PopLocation();
		}
	}

void IndexExpr::Delete(Frame* f)
	{
	if ( IsError() )
		return;

	auto v1 = op1->Eval(f);

	if ( ! v1 )
		return;

	auto v2 = op2->Eval(f);

	if ( ! v2 )
		return;

	bool iterators_invalidated = false;
	v1->AsTableVal()->Remove(*v2, true, &iterators_invalidated);

	if ( iterators_invalidated )
		{
		ODesc d;
		Describe(&d);
		reporter->PushLocation(GetLocationInfo());
		reporter->Warning("possible loop/iterator invalidation caused by expression: %s",
		                  d.Description());
		reporter->PopLocation();
		}
	}

ExprPtr IndexExpr::MakeLvalue()
	{
	if ( IsString(op1->GetType()->Tag()) )
		ExprError("cannot assign to string index expression");

	return make_intrusive<RefExpr>(IntrusivePtr{NewRef{}, this});
	}

ValPtr IndexExpr::Eval(Frame* f) const
	{
	auto v1 = op1->Eval(f);

	if ( ! v1 )
		return nullptr;

	auto v2 = op2->Eval(f);

	if ( ! v2 )
		return nullptr;

	Val* indv = v2->AsListVal()->Idx(0).get();

	if ( is_vector(v1) && is_vector(indv) )
		{
		VectorVal* v_v1 = v1->AsVectorVal();
		VectorVal* v_v2 = indv->AsVectorVal();
		auto vt = cast_intrusive<VectorType>(GetType());

		// Booleans select each element (or not).
		if ( IsBool(v_v2->GetType()->Yield()->Tag()) )
			{
			if ( v_v1->Size() != v_v2->Size() )
				{
				RuntimeError("size mismatch, boolean index and vector");
				return nullptr;
				}

			return vector_bool_select(vt, v_v1, v_v2);
			}
		else
			// Elements are indices.
			return vector_int_select(vt, v_v1, v_v2);
		}
	else
		return Fold(v1.get(), v2.get());
	}

ValPtr IndexExpr::Fold(Val* v1, Val* v2) const
	{
	if ( IsError() )
		return nullptr;

	ValPtr v;

	switch ( v1->GetType()->Tag() )
		{
		case TYPE_VECTOR:
			{
			VectorVal* vect = v1->AsVectorVal();
			const ListVal* lv = v2->AsListVal();

			if ( lv->Length() == 1 )
				v = vect->ValAt(lv->Idx(0)->CoerceToUnsigned());
			else
				return index_slice(vect, lv);
			}
			break;

		case TYPE_TABLE:
			v = v1->AsTableVal()->FindOrDefault({NewRef{}, v2});
			break;

		case TYPE_STRING:
			return index_string(v1->AsString(), v2->AsListVal());

		default:
			RuntimeError("type cannot be indexed");
			break;
		}

	if ( v )
		return v;

	RuntimeError("no such index");
	return nullptr;
	}

StringValPtr index_string(const String* s, const ListVal* lv)
	{
	int len = s->Len();
	String* substring = nullptr;

	if ( lv->Length() == 1 )
		{
		bro_int_t idx = lv->Idx(0)->AsInt();

		if ( idx < 0 )
			idx += len;

		// Out-of-range index will return null pointer.
		substring = s->GetSubstring(idx, 1);
		}
	else
		{
		bro_int_t first = get_slice_index(lv->Idx(0)->AsInt(), len);
		bro_int_t last = get_slice_index(lv->Idx(1)->AsInt(), len);
		bro_int_t substring_len = last - first;

		if ( substring_len < 0 )
			substring = nullptr;
		else
			substring = s->GetSubstring(first, substring_len);
		}

	return make_intrusive<StringVal>(substring ? substring : new String(""));
	}

VectorValPtr index_slice(VectorVal* vect, const ListVal* lv)
	{
	auto first = lv->Idx(0)->CoerceToInt();
	auto last = lv->Idx(1)->CoerceToInt();
	return index_slice(vect, first, last);
	}

VectorValPtr index_slice(VectorVal* vect, int _first, int _last)
	{
	size_t len = vect->Size();
	auto result = make_intrusive<VectorVal>(vect->GetType<VectorType>());

	bro_int_t first = get_slice_index(_first, len);
	bro_int_t last = get_slice_index(_last, len);
	bro_int_t sub_length = last - first;

	if ( sub_length >= 0 )
		{
		result->Resize(sub_length);

		for ( bro_int_t idx = first; idx < last; idx++ )
			result->Assign(idx - first, vect->ValAt(idx));
		}

	return result;
	}

VectorValPtr vector_bool_select(VectorTypePtr vt, const VectorVal* v1, const VectorVal* v2)
	{
	auto v_result = make_intrusive<VectorVal>(std::move(vt));

	for ( unsigned int i = 0; i < v2->Size(); ++i )
		if ( v2->BoolAt(i) )
			v_result->Assign(v_result->Size() + 1, v1->ValAt(i));

	return v_result;
	}

VectorValPtr vector_int_select(VectorTypePtr vt, const VectorVal* v1, const VectorVal* v2)
	{
	auto v_result = make_intrusive<VectorVal>(std::move(vt));

	// The elements are indices.
	//
	// ### Should handle negative indices here like S does, i.e.,
	// by excluding those elements.  Probably only do this if *all*
	// are negative.
	v_result->Resize(v2->Size());
	for ( unsigned int i = 0; i < v2->Size(); ++i )
		v_result->Assign(i, v1->ValAt(v2->ValAt(i)->CoerceToInt()));

	return v_result;
	}

void IndexExpr::Assign(Frame* f, ValPtr v)
	{
	if ( IsError() )
		return;

	auto v1 = op1->Eval(f);
	auto v2 = op2->Eval(f);

	AssignToIndex(v1, v2, v);
	}

void IndexExpr::ExprDescribe(ODesc* d) const
	{
	op1->Describe(d);
	if ( d->IsReadable() )
		d->Add("[");

	op2->Describe(d);
	if ( d->IsReadable() )
		d->Add("]");
	}

FieldExpr::FieldExpr(ExprPtr arg_op, const char* arg_field_name)
	: UnaryExpr(EXPR_FIELD, std::move(arg_op)), field_name(util::copy_string(arg_field_name)),
	  td(nullptr), field(0)
	{
	if ( IsError() )
		return;

	if ( ! IsRecord(op->GetType()->Tag()) )
		ExprError("not a record");
	else
		{
		RecordType* rt = op->GetType()->AsRecordType();
		field = rt->FieldOffset(field_name);

		if ( field < 0 )
			ExprError("no such field in record");
		else
			{
			SetType(rt->GetFieldType(field));
			td = rt->FieldDecl(field);

			if ( rt->IsFieldDeprecated(field) )
				reporter->Warning("%s", rt->GetFieldDeprecationWarning(field, false).c_str());
			}
		}
	}

FieldExpr::~FieldExpr()
	{
	delete[] field_name;
	}

ExprPtr FieldExpr::MakeLvalue()
	{
	return make_intrusive<RefExpr>(IntrusivePtr{NewRef{}, this});
	}

bool FieldExpr::CanDel() const
	{
	return td->GetAttr(ATTR_DEFAULT) || td->GetAttr(ATTR_OPTIONAL);
	}

void FieldExpr::Assign(Frame* f, ValPtr v)
	{
	if ( IsError() )
		return;

	if ( auto op_v = op->Eval(f) )
		{
		RecordVal* r = op_v->AsRecordVal();
		r->Assign(field, std::move(v));
		}
	}

void FieldExpr::Delete(Frame* f)
	{
	Assign(f, nullptr);
	}

ValPtr FieldExpr::Fold(Val* v) const
	{
	if ( const auto& result = v->AsRecordVal()->GetField(field) )
		return result;

	// Check for &default.
	const Attr* def_attr = td ? td->GetAttr(ATTR_DEFAULT).get() : nullptr;

	if ( def_attr )
		return def_attr->GetExpr()->Eval(nullptr);
	else
		{
		RuntimeError("field value missing");
		assert(false);
		return nullptr; // Will never get here, but compiler can't tell.
		}
	}

void FieldExpr::ExprDescribe(ODesc* d) const
	{
	op->Describe(d);
	if ( d->IsReadable() )
		d->Add("$");

	if ( IsError() )
		d->Add("<error>");
	else if ( d->IsReadable() )
		d->Add(field_name);
	else
		d->Add(field);
	}

HasFieldExpr::HasFieldExpr(ExprPtr arg_op, const char* arg_field_name)
	: UnaryExpr(EXPR_HAS_FIELD, std::move(arg_op)), field_name(arg_field_name), field(0)
	{
	if ( IsError() )
		return;

	if ( ! IsRecord(op->GetType()->Tag()) )
		ExprError("not a record");
	else
		{
		RecordType* rt = op->GetType()->AsRecordType();
		field = rt->FieldOffset(field_name);

		if ( field < 0 )
			ExprError("no such field in record");
		else if ( rt->IsFieldDeprecated(field) )
			reporter->Warning("%s", rt->GetFieldDeprecationWarning(field, true).c_str());

		SetType(base_type(TYPE_BOOL));
		}
	}

HasFieldExpr::~HasFieldExpr()
	{
	delete field_name;
	}

ValPtr HasFieldExpr::Fold(Val* v) const
	{
	auto rv = v->AsRecordVal();
	return val_mgr->Bool(rv->HasField(field));
	}

void HasFieldExpr::ExprDescribe(ODesc* d) const
	{
	op->Describe(d);

	if ( d->IsReadable() )
		d->Add("?$");

	if ( IsError() )
		d->Add("<error>");
	else if ( d->IsReadable() )
		d->Add(field_name);
	else
		d->Add(field);
	}

RecordConstructorExpr::RecordConstructorExpr(ListExprPtr constructor_list)
	: Expr(EXPR_RECORD_CONSTRUCTOR), op(std::move(constructor_list)), map(std::nullopt)
	{
	if ( IsError() )
		return;

	// Spin through the list, which should be comprised only of
	// record-field-assign expressions, and build up a
	// record type to associate with this constructor.
	const ExprPList& exprs = op->AsListExpr()->Exprs();
	type_decl_list* record_types = new type_decl_list(exprs.length());

	for ( const auto& e : exprs )
		{
		if ( e->Tag() != EXPR_FIELD_ASSIGN )
			{
			Error("bad type in record constructor", e);
			SetError();
			continue;
			}

		FieldAssignExpr* field = (FieldAssignExpr*)e;
		const auto& field_type = field->GetType();
		char* field_name = util::copy_string(field->FieldName());
		record_types->push_back(new TypeDecl(field_name, field_type));
		}

	SetType(make_intrusive<RecordType>(record_types));
	}

RecordConstructorExpr::RecordConstructorExpr(RecordTypePtr known_rt, ListExprPtr constructor_list)
	: Expr(EXPR_RECORD_CONSTRUCTOR), op(std::move(constructor_list))
	{
	if ( IsError() )
		return;

	SetType(known_rt);

	const auto& exprs = op->AsListExpr()->Exprs();
	map = std::vector<int>(exprs.length());

	int i = 0;
	for ( const auto& e : exprs )
		{
		if ( e->Tag() != EXPR_FIELD_ASSIGN )
			{
			Error("bad type in record constructor", e);
			SetError();
			continue;
			}

		auto field = e->AsFieldAssignExpr();
		int index = known_rt->FieldOffset(field->FieldName());

		if ( index < 0 )
			{
			Error("no such field in record", e);
			SetError();
			continue;
			}

		auto known_ft = known_rt->GetFieldType(index);

		if ( ! field->PromoteTo(known_ft) )
			SetError();

		(*map)[i++] = index;
		}
	}

ValPtr RecordConstructorExpr::InitVal(const zeek::Type* t, ValPtr aggr) const
	{
	if ( IsError() )
		{
		Error("bad record initializer");
		return nullptr;
		}

	if ( ! init_tag_check(this, "record constructor", TYPE_RECORD, t->Tag()) )
		return nullptr;

	auto v = Eval(nullptr);

	if ( v )
		{
		RecordVal* rv = v->AsRecordVal();
		auto bt = const_cast<zeek::Type*>(t);
		RecordTypePtr rt{NewRef{}, bt->AsRecordType()};
		auto aggr_rec = cast_intrusive<RecordVal>(std::move(aggr));
		auto ar = rv->CoerceTo(std::move(rt), std::move(aggr_rec));

		if ( ar )
			return ar;
		}

	Error("bad record initializer");
	return nullptr;
	}

ValPtr RecordConstructorExpr::Eval(Frame* f) const
	{
	if ( IsError() )
		return nullptr;

	const auto& exprs = op->Exprs();
	auto rt = cast_intrusive<RecordType>(type);

	if ( ! map && exprs.length() != rt->NumFields() )
		RuntimeErrorWithCallStack("inconsistency evaluating record constructor");

	auto rv = make_intrusive<RecordVal>(rt);

	for ( int i = 0; i < exprs.length(); ++i )
		{
		auto v_i = exprs[i]->Eval(f);
		int ind = map ? (*map)[i] : i;

		if ( v_i && v_i->GetType()->Tag() == TYPE_VECTOR &&
		     v_i->GetType<VectorType>()->IsUnspecifiedVector() )
			{
			const auto& t_ind = rt->GetFieldType(ind);
			v_i->AsVectorVal()->Concretize(t_ind->Yield());
			}

		rv->Assign(ind, v_i);
		}

	return rv;
	}

bool RecordConstructorExpr::IsPure() const
	{
	return op->IsPure();
	}

void RecordConstructorExpr::ExprDescribe(ODesc* d) const
	{
	auto& tn = type->GetName();

	if ( tn.size() > 0 )
		{
		d->Add(tn);
		d->Add("(");
		op->Describe(d);
		d->Add(")");
		}
	else
		{
		d->Add("[");
		op->Describe(d);
		d->Add("]");
		}
	}

TraversalCode RecordConstructorExpr::Traverse(TraversalCallback* cb) const
	{
	TraversalCode tc = cb->PreExpr(this);
	HANDLE_TC_EXPR_PRE(tc);

	tc = op->Traverse(cb);
	HANDLE_TC_EXPR_PRE(tc);

	tc = cb->PostExpr(this);
	HANDLE_TC_EXPR_POST(tc);
	}

TableConstructorExpr::TableConstructorExpr(ListExprPtr constructor_list,
                                           std::unique_ptr<std::vector<AttrPtr>> arg_attrs,
                                           TypePtr arg_type, AttributesPtr arg_attrs2)
	: UnaryExpr(EXPR_TABLE_CONSTRUCTOR, std::move(constructor_list))
	{
	if ( IsError() )
		return;

	if ( arg_type )
		{
		if ( ! arg_type->IsTable() )
			{
			Error("bad table constructor type", arg_type.get());
			SetError();
			return;
			}

		SetType(std::move(arg_type));
		}
	else
		{
		if ( op->AsListExpr()->Exprs().empty() )
			SetType(
				make_intrusive<TableType>(make_intrusive<TypeList>(base_type(TYPE_ANY)), nullptr));
		else
			{
			SetType(init_type(op.get()));

			if ( ! type )
				{
				SetError();
				return;
				}

			else if ( type->Tag() != TYPE_TABLE || type->AsTableType()->IsSet() )
				SetError("values in table(...) constructor do not specify a table");
			}
		}

	if ( arg_attrs )
		attrs = make_intrusive<Attributes>(std::move(*arg_attrs), type, false, false);
	else
		attrs = arg_attrs2;

	const auto& indices = type->AsTableType()->GetIndices()->GetTypes();
	const ExprPList& cle = op->AsListExpr()->Exprs();

	// check and promote all assign expressions in ctor list
	for ( const auto& expr : cle )
		{
		if ( expr->Tag() != EXPR_ASSIGN )
			continue;

		auto idx_expr = expr->AsAssignExpr()->GetOp1();
		auto val_expr = expr->AsAssignExpr()->GetOp2();
		auto yield_type = GetType()->AsTableType()->Yield();

		// Promote LHS
		assert(idx_expr->Tag() == EXPR_LIST);
		ExprPList& idx_exprs = idx_expr->AsListExpr()->Exprs();

		if ( idx_exprs.length() != static_cast<int>(indices.size()) )
			continue;

		loop_over_list(idx_exprs, j)
			{
			ExprPtr idx = {NewRef{}, idx_exprs[j]};

			auto promoted_idx = check_and_promote_expr(idx, indices[j]);

			if ( promoted_idx )
				{
				if ( promoted_idx != idx )
					Unref(idx_exprs.replace(j, promoted_idx.release()));

				continue;
				}

			ExprError("inconsistent types in table constructor");
			return;
			}

		// Promote RHS
		if ( auto promoted_val = check_and_promote_expr(val_expr, yield_type) )
			{
			if ( promoted_val != val_expr )
				expr->AsAssignExpr()->SetOp2(promoted_val);
			}
		else
			{
			ExprError("inconsistent types in table constructor");
			return;
			}
		}
	}

ValPtr TableConstructorExpr::Eval(Frame* f) const
	{
	if ( IsError() )
		return nullptr;

	auto aggr = make_intrusive<TableVal>(GetType<TableType>(), attrs);
	const ExprPList& exprs = op->AsListExpr()->Exprs();

	for ( const auto& expr : exprs )
		expr->EvalIntoAggregate(type.get(), aggr.get(), f);

	aggr->InitDefaultFunc(f);

	return aggr;
	}

ValPtr TableConstructorExpr::InitVal(const zeek::Type* t, ValPtr aggr) const
	{
	if ( IsError() )
		return nullptr;

	if ( ! init_tag_check(this, "table constructor", TYPE_TABLE, t->Tag()) )
		return nullptr;

	auto tt = GetType<TableType>();

	auto tval = aggr ? TableValPtr{AdoptRef{}, aggr.release()->AsTableVal()}
	                 : make_intrusive<TableVal>(std::move(tt), attrs);
	const ExprPList& exprs = op->AsListExpr()->Exprs();

	for ( const auto& expr : exprs )
		expr->EvalIntoAggregate(t, tval.get(), nullptr);

	return tval;
	}

void TableConstructorExpr::ExprDescribe(ODesc* d) const
	{
	d->Add("table(");
	op->Describe(d);
	d->Add(")");
	}

SetConstructorExpr::SetConstructorExpr(ListExprPtr constructor_list,
                                       std::unique_ptr<std::vector<AttrPtr>> arg_attrs,
                                       TypePtr arg_type, AttributesPtr arg_attrs2)
	: UnaryExpr(EXPR_SET_CONSTRUCTOR, std::move(constructor_list))
	{
	if ( IsError() )
		return;

	if ( arg_type )
		{
		if ( ! arg_type->IsSet() )
			{
			Error("bad set constructor type", arg_type.get());
			SetError();
			return;
			}

		SetType(std::move(arg_type));
		}
	else
		{
		if ( op->AsListExpr()->Exprs().empty() )
			SetType(make_intrusive<zeek::SetType>(make_intrusive<TypeList>(base_type(TYPE_ANY)),
			                                      nullptr));
		else
			SetType(init_type(op.get()));
		}

	if ( ! type )
		SetError();

	else if ( type->Tag() != TYPE_TABLE || ! type->AsTableType()->IsSet() )
		SetError("values in set(...) constructor do not specify a set");

	if ( arg_attrs )
		attrs = make_intrusive<Attributes>(std::move(*arg_attrs), type, false, false);
	else
		attrs = arg_attrs2;

	const auto& indices = type->AsTableType()->GetIndices()->GetTypes();
	ExprPList& cle = op->AsListExpr()->Exprs();

	if ( indices.size() == 1 )
		{
		if ( ! check_and_promote_exprs_to_type(op->AsListExpr(), indices[0]) )
			ExprError("inconsistent type in set constructor");
		}

	else if ( indices.size() > 1 )
		{
		// Check/promote each expression in composite index.
		loop_over_list(cle, i)
			{
			Expr* ce = cle[i];
			ListExpr* le = ce->AsListExpr();

			assert(ce->Tag() == EXPR_LIST);
			if ( check_and_promote_exprs(le, type->AsTableType()->GetIndices().get()) )
				{
				if ( le != cle[i] )
					cle.replace(i, le);

				continue;
				}

			ExprError("inconsistent types in set constructor");
			}
		}
	}

ValPtr SetConstructorExpr::Eval(Frame* f) const
	{
	if ( IsError() )
		return nullptr;

	auto aggr = make_intrusive<TableVal>(IntrusivePtr{NewRef{}, type->AsTableType()}, attrs);
	const ExprPList& exprs = op->AsListExpr()->Exprs();

	for ( const auto& expr : exprs )
		{
		auto element = expr->Eval(f);
		aggr->Assign(std::move(element), nullptr);
		}

	return aggr;
	}

ValPtr SetConstructorExpr::InitVal(const zeek::Type* t, ValPtr aggr) const
	{
	if ( IsError() )
		return nullptr;

	if ( ! init_tag_check(this, "set constructor", TYPE_TABLE, t->Tag()) )
		return nullptr;

	const auto& index_type = t->AsTableType()->GetIndices();
	auto tt = GetType<TableType>();
	auto tval = aggr ? TableValPtr{AdoptRef{}, aggr.release()->AsTableVal()}
	                 : make_intrusive<TableVal>(std::move(tt), attrs);
	const ExprPList& exprs = op->AsListExpr()->Exprs();

	for ( const auto& e : exprs )
		{
		auto element = check_and_promote(e->Eval(nullptr), index_type.get(), true);

		if ( ! element || ! tval->Assign(std::move(element), nullptr) )
			{
			Error(util::fmt("initialization type mismatch in set"), e);
			return nullptr;
			}
		}

	return tval;
	}

void SetConstructorExpr::ExprDescribe(ODesc* d) const
	{
	d->Add("set(");
	op->Describe(d);
	d->Add(")");
	}

VectorConstructorExpr::VectorConstructorExpr(ListExprPtr constructor_list, TypePtr arg_type)
	: UnaryExpr(EXPR_VECTOR_CONSTRUCTOR, std::move(constructor_list))
	{
	if ( IsError() )
		return;

	if ( arg_type )
		{
		if ( arg_type->Tag() != TYPE_VECTOR )
			{
			Error("bad vector constructor type", arg_type.get());
			SetError();
			return;
			}

		SetType(std::move(arg_type));
		}
	else
		{
		if ( op->AsListExpr()->Exprs().empty() )
			{
			// vector().
			// By default, assign VOID type here. A vector with
			// void type set is seen as an unspecified vector.
			SetType(make_intrusive<VectorType>(base_type(TYPE_VOID)));
			return;
			}

		if ( auto t = merge_type_list(op->AsListExpr()) )
			SetType(make_intrusive<VectorType>(std::move(t)));
		else
			{
			SetError();
			return;
			}
		}

	if ( ! check_and_promote_exprs_to_type(op->AsListExpr(), type->AsVectorType()->Yield()) )
		ExprError("inconsistent types in vector constructor");
	}

ValPtr VectorConstructorExpr::Eval(Frame* f) const
	{
	if ( IsError() )
		return nullptr;

	auto vec = make_intrusive<VectorVal>(GetType<VectorType>());
	const ExprPList& exprs = op->AsListExpr()->Exprs();

	loop_over_list(exprs, i)
		{
		Expr* e = exprs[i];

		if ( ! vec->Assign(i, e->Eval(f)) )
			{
			RuntimeError(util::fmt("type mismatch at index %d", i));
			return nullptr;
			}
		}

	return vec;
	}

ValPtr VectorConstructorExpr::InitVal(const zeek::Type* t, ValPtr aggr) const
	{
	if ( IsError() )
		return nullptr;

	if ( ! init_tag_check(this, "vector constructor", TYPE_VECTOR, t->Tag()) )
		return nullptr;

	auto vt = GetType<VectorType>();
	auto vec = aggr ? VectorValPtr{AdoptRef{}, aggr.release()->AsVectorVal()}
	                : make_intrusive<VectorVal>(std::move(vt));
	const ExprPList& exprs = op->AsListExpr()->Exprs();

	loop_over_list(exprs, i)
		{
		Expr* e = exprs[i];
		auto v = check_and_promote(e->Eval(nullptr), t->Yield().get(), true);

		if ( ! v || ! vec->Assign(i, std::move(v)) )
			{
			Error(util::fmt("initialization type mismatch at index %d", i), e);
			return nullptr;
			}
		}

	return vec;
	}

void VectorConstructorExpr::ExprDescribe(ODesc* d) const
	{
	d->Add("vector(");
	op->Describe(d);
	d->Add(")");
	}

FieldAssignExpr::FieldAssignExpr(const char* arg_field_name, ExprPtr value)
	: UnaryExpr(EXPR_FIELD_ASSIGN, std::move(value)), field_name(arg_field_name)
	{
	SetType(op->GetType());
	}

bool FieldAssignExpr::PromoteTo(TypePtr t)
	{
	op = check_and_promote_expr(op, t);
	return op != nullptr;
	}

void FieldAssignExpr::EvalIntoAggregate(const zeek::Type* t, Val* aggr, Frame* f) const
	{
	if ( IsError() )
		return;

	if ( auto v = op->Eval(f) )
		{
		RecordVal* rec = aggr->AsRecordVal();
		const RecordType* rt = t->AsRecordType();

		int idx = rt->FieldOffset(field_name.c_str());

		if ( idx < 0 )
			reporter->InternalError("Missing record field: %s", field_name.c_str());

		rec->Assign(idx, std::move(v));
		}
	}

bool FieldAssignExpr::IsRecordElement(TypeDecl* td) const
	{
	if ( td )
		{
		td->type = op->GetType();
		td->id = util::copy_string(field_name.c_str());
		}

	return true;
	}

void FieldAssignExpr::ExprDescribe(ODesc* d) const
	{
	d->Add("$");
	d->Add(FieldName());
	d->Add("=");

	if ( op )
		op->Describe(d);
	else
		d->Add("<error>");
	}

ArithCoerceExpr::ArithCoerceExpr(ExprPtr arg_op, TypeTag t)
	: UnaryExpr(EXPR_ARITH_COERCE, std::move(arg_op))
	{
	if ( IsError() )
		return;

	TypeTag bt = op->GetType()->Tag();
	TypeTag vbt = bt;

	if ( IsVector(bt) )
		{
		SetType(make_intrusive<VectorType>(base_type(t)));
		vbt = op->GetType()->AsVectorType()->Yield()->Tag();
		}
	else
		SetType(base_type(t));

	if ( (bt == TYPE_ENUM) != (t == TYPE_ENUM) )
		ExprError("can't convert to/from enumerated type");

	else if ( ! IsArithmetic(t) && ! IsBool(t) && t != TYPE_TIME && t != TYPE_INTERVAL )
		ExprError("bad coercion");

	else if ( ! IsArithmetic(bt) && ! IsBool(bt) && ! IsArithmetic(vbt) && ! IsBool(vbt) )
		ExprError("bad coercion value");
	}

ValPtr ArithCoerceExpr::FoldSingleVal(ValPtr v, const TypePtr& t) const
	{
	return check_and_promote(v, t.get(), false, location);
	}

ValPtr ArithCoerceExpr::Fold(Val* v) const
	{
	auto t = GetType();

	if ( ! is_vector(v) )
		{
		// Our result type might be vector, in which case this
		// invocation is being done per-element rather than on
		// the whole vector.  Correct the type if so.
		if ( type->Tag() == TYPE_VECTOR )
			t = t->AsVectorType()->Yield();

		return FoldSingleVal({NewRef{}, v}, t);
		}

	VectorVal* vv = v->AsVectorVal();
	auto result = make_intrusive<VectorVal>(cast_intrusive<VectorType>(t));

	auto yt = t->AsVectorType()->Yield();

	for ( unsigned int i = 0; i < vv->Size(); ++i )
		{
		auto elt = vv->ValAt(i);
		if ( elt )
			result->Assign(i, FoldSingleVal(elt, yt));
		else
			result->Assign(i, nullptr);
		}

	return result;
	}

RecordCoerceExpr::RecordCoerceExpr(ExprPtr arg_op, RecordTypePtr r)
	: UnaryExpr(EXPR_RECORD_COERCE, std::move(arg_op))
	{
	if ( IsError() )
		return;

	SetType(std::move(r));

	if ( GetType()->Tag() != TYPE_RECORD )
		ExprError("coercion to non-record");

	else if ( op->GetType()->Tag() != TYPE_RECORD )
		ExprError("coercion of non-record to record");

	else
		{
		RecordType* t_r = type->AsRecordType();
		RecordType* sub_r = op->GetType()->AsRecordType();

		int map_size = t_r->NumFields();
		map.resize(map_size, -1); // -1 = field is not mapped

		int i;
		for ( i = 0; i < sub_r->NumFields(); ++i )
			{
			int t_i = t_r->FieldOffset(sub_r->FieldName(i));
			if ( t_i < 0 )
				{
				ExprError(
					util::fmt("orphaned field \"%s\" in record coercion", sub_r->FieldName(i)));
				break;
				}

			const auto& sub_t_i = sub_r->GetFieldType(i);
			const auto& sup_t_i = t_r->GetFieldType(t_i);

			if ( ! same_type(sup_t_i, sub_t_i) )
				{
				auto is_arithmetic_promotable = [](zeek::Type* sup, zeek::Type* sub) -> bool
				{
					auto sup_tag = sup->Tag();
					auto sub_tag = sub->Tag();

					if ( ! BothArithmetic(sup_tag, sub_tag) )
						return false;

					if ( sub_tag == TYPE_DOUBLE && IsIntegral(sup_tag) )
						return false;

					if ( sub_tag == TYPE_INT && sup_tag == TYPE_COUNT )
						return false;

					return true;
				};

				auto is_record_promotable = [](zeek::Type* sup, zeek::Type* sub) -> bool
				{
					if ( sup->Tag() != TYPE_RECORD )
						return false;

					if ( sub->Tag() != TYPE_RECORD )
						return false;

					return record_promotion_compatible(sup->AsRecordType(), sub->AsRecordType());
				};

				if ( ! is_arithmetic_promotable(sup_t_i.get(), sub_t_i.get()) &&
				     ! is_record_promotable(sup_t_i.get(), sub_t_i.get()) )
					{
					std::string error_msg = util::fmt("type clash for field \"%s\"",
					                                  sub_r->FieldName(i));
					Error(error_msg.c_str(), sub_t_i.get());
					SetError();
					break;
					}
				}

			map[t_i] = i;
			}

		if ( IsError() )
			return;

		for ( i = 0; i < map_size; ++i )
			{
			if ( map[i] == -1 )
				{
				if ( ! t_r->FieldDecl(i)->GetAttr(ATTR_OPTIONAL) )
					{
					std::string error_msg = util::fmt("non-optional field \"%s\" missing",
					                                  t_r->FieldName(i));
					Error(error_msg.c_str());
					SetError();
					break;
					}
				}
			else if ( t_r->IsFieldDeprecated(i) )
				reporter->Warning("%s", t_r->GetFieldDeprecationWarning(i, false).c_str());
			}
		}
	}

ValPtr RecordCoerceExpr::InitVal(const zeek::Type* t, ValPtr aggr) const
	{
	if ( IsError() )
		{
		Error("bad record initializer");
		return nullptr;
		}

	if ( ! init_tag_check(this, "record", TYPE_RECORD, t->Tag()) )
		return nullptr;

	if ( auto v = Eval(nullptr) )
		{
		RecordVal* rv = v->AsRecordVal();
		auto bt = const_cast<zeek::Type*>(t);
		RecordTypePtr rt{NewRef{}, bt->AsRecordType()};
		auto aggr_rec = cast_intrusive<RecordVal>(std::move(aggr));

		if ( auto ar = rv->CoerceTo(std::move(rt), std::move(aggr_rec)) )
			return ar;
		}

	Error("bad record initializer");
	return nullptr;
	}

ValPtr RecordCoerceExpr::Fold(Val* v) const
	{
	if ( same_type(GetType(), Op()->GetType()) )
		return IntrusivePtr{NewRef{}, v};

	auto rt = cast_intrusive<RecordType>(GetType());
	return coerce_to_record(rt, v, map);
	}

RecordValPtr coerce_to_record(RecordTypePtr rt, Val* v, const std::vector<int>& map)
	{
	int map_size = map.size();
	auto val = make_intrusive<RecordVal>(rt);
	RecordType* val_type = val->GetType()->AsRecordType();

	RecordVal* rv = v->AsRecordVal();

	for ( int i = 0; i < map_size; ++i )
		{
		if ( map[i] >= 0 )
			{
			auto rhs = rv->GetField(map[i]);

			if ( ! rhs )
				{
				auto rv_rt = rv->GetType()->AsRecordType();
				const auto& def = rv_rt->FieldDecl(map[i])->GetAttr(ATTR_DEFAULT);

				if ( def )
					rhs = def->GetExpr()->Eval(nullptr);
				}

			assert(rhs || rt->FieldDecl(i)->GetAttr(ATTR_OPTIONAL));

			if ( ! rhs )
				{
				// Optional field is missing.
				val->Remove(i);
				continue;
				}

			const auto& rhs_type = rhs->GetType();
			const auto& field_type = val_type->GetFieldType(i);

			if ( rhs_type->Tag() == TYPE_RECORD && field_type->Tag() == TYPE_RECORD &&
			     ! same_type(rhs_type, field_type) )
				{
				if ( auto new_val = rhs->AsRecordVal()->CoerceTo(
						 cast_intrusive<RecordType>(field_type)) )
					rhs = std::move(new_val);
				}
			else if ( rhs_type->Tag() == TYPE_VECTOR && field_type->Tag() == TYPE_VECTOR &&
			          rhs_type->AsVectorType()->IsUnspecifiedVector() )
				{
				auto rhs_v = rhs->AsVectorVal();
				if ( ! rhs_v->Concretize(field_type->Yield()) )
					reporter->InternalError("could not concretize empty vector");
				}
			else if ( BothArithmetic(rhs_type->Tag(), field_type->Tag()) &&
			          ! same_type(rhs_type, field_type) )
				{
				auto new_val = check_and_promote(rhs, field_type.get(), false);
				rhs = std::move(new_val);
				}

			val->Assign(i, std::move(rhs));
			}
		else
			{
			if ( const auto& def = rt->FieldDecl(i)->GetAttr(ATTR_DEFAULT) )
				{
				auto def_val = def->GetExpr()->Eval(nullptr);
				const auto& def_type = def_val->GetType();
				const auto& field_type = rt->GetFieldType(i);

				if ( def_type->Tag() == TYPE_RECORD && field_type->Tag() == TYPE_RECORD &&
				     ! same_type(def_type, field_type) )
					{
					auto tmp = def_val->AsRecordVal()->CoerceTo(
						cast_intrusive<RecordType>(field_type));

					if ( tmp )
						def_val = std::move(tmp);
					}

				val->Assign(i, std::move(def_val));
				}
			else
				val->Remove(i);
			}
		}

	return val;
	}

TableCoerceExpr::TableCoerceExpr(ExprPtr arg_op, TableTypePtr r)
	: UnaryExpr(EXPR_TABLE_COERCE, std::move(arg_op))
	{
	if ( IsError() )
		return;

	SetType(std::move(r));

	if ( GetType()->Tag() != TYPE_TABLE )
		ExprError("coercion to non-table");

	else if ( op->GetType()->Tag() != TYPE_TABLE )
		ExprError("coercion of non-table/set to table/set");
	}

TableCoerceExpr::~TableCoerceExpr() { }

ValPtr TableCoerceExpr::Fold(Val* v) const
	{
	TableVal* tv = v->AsTableVal();

	if ( tv->Size() > 0 )
		RuntimeErrorWithCallStack("coercion of non-empty table/set");

	return make_intrusive<TableVal>(GetType<TableType>(), tv->GetAttrs());
	}

VectorCoerceExpr::VectorCoerceExpr(ExprPtr arg_op, VectorTypePtr v)
	: UnaryExpr(EXPR_VECTOR_COERCE, std::move(arg_op))
	{
	if ( IsError() )
		return;

	SetType(std::move(v));

	if ( GetType()->Tag() != TYPE_VECTOR )
		ExprError("coercion to non-vector");

	else if ( op->GetType()->Tag() != TYPE_VECTOR )
		ExprError("coercion of non-vector to vector");
	}

VectorCoerceExpr::~VectorCoerceExpr() { }

ValPtr VectorCoerceExpr::Fold(Val* v) const
	{
	VectorVal* vv = v->AsVectorVal();

	if ( vv->Size() > 0 )
		RuntimeErrorWithCallStack("coercion of non-empty vector");

	return make_intrusive<VectorVal>(GetType<VectorType>());
	}

ScheduleTimer::ScheduleTimer(const EventHandlerPtr& arg_event, Args arg_args, double t)
	: Timer(t, TIMER_SCHEDULE), event(arg_event), args(std::move(arg_args))
	{
	}

ScheduleTimer::~ScheduleTimer() { }

void ScheduleTimer::Dispatch(double /* t */, bool /* is_expire */)
	{
	if ( event )
		event_mgr.Enqueue(event, std::move(args));
	}

ScheduleExpr::ScheduleExpr(ExprPtr arg_when, EventExprPtr arg_event)
	: Expr(EXPR_SCHEDULE), when(std::move(arg_when)), event(std::move(arg_event))
	{
	if ( IsError() || when->IsError() || event->IsError() )
		return;

	TypeTag bt = when->GetType()->Tag();

	if ( bt != TYPE_TIME && bt != TYPE_INTERVAL )
		ExprError("schedule expression requires a time or time interval");
	else
		SetType(base_type(TYPE_TIMER));
	}

bool ScheduleExpr::IsPure() const
	{
	return false;
	}

ValPtr ScheduleExpr::Eval(Frame* f) const
	{
	if ( run_state::terminating )
		return nullptr;

	auto when_val = when->Eval(f);

	if ( ! when_val )
		return nullptr;

	double dt = when_val->InternalDouble();

	if ( when->GetType()->Tag() == TYPE_INTERVAL )
		dt += run_state::network_time;

	auto args = eval_list(f, event->Args());

	if ( args )
		timer_mgr->Add(new ScheduleTimer(event->Handler(), std::move(*args), dt));

	return nullptr;
	}

TraversalCode ScheduleExpr::Traverse(TraversalCallback* cb) const
	{
	TraversalCode tc = cb->PreExpr(this);
	HANDLE_TC_EXPR_PRE(tc);

	tc = when->Traverse(cb);
	HANDLE_TC_EXPR_PRE(tc);

	tc = event->Traverse(cb);
	HANDLE_TC_EXPR_PRE(tc);

	tc = cb->PostExpr(this);
	HANDLE_TC_EXPR_POST(tc);
	}

void ScheduleExpr::ExprDescribe(ODesc* d) const
	{
	if ( d->IsReadable() )
		d->AddSP("schedule");

	when->Describe(d);
	d->SP();

	if ( d->IsReadable() )
		{
		d->Add("{");
		d->PushIndent();
		event->Describe(d);
		d->PopIndent();
		d->Add("}");
		}
	else
		event->Describe(d);
	}

InExpr::InExpr(ExprPtr arg_op1, ExprPtr arg_op2)
	: BinaryExpr(EXPR_IN, std::move(arg_op1), std::move(arg_op2))
	{
	if ( IsError() )
		return;

	if ( op1->GetType()->Tag() == TYPE_PATTERN )
		{
		if ( op2->GetType()->Tag() == TYPE_STRING )
			{
			SetType(base_type(TYPE_BOOL));
			return;
			}
		else if ( op2->GetType()->Tag() == TYPE_TABLE )
			{
			// fall through to type-checking at end of function
			}
		else
			{
			op2->GetType()->Error("pattern requires string or set/table index", op1.get());
			SetError();
			return;
			}
		}

	if ( op1->GetType()->Tag() == TYPE_STRING && op2->GetType()->Tag() == TYPE_STRING )
		{
		SetType(base_type(TYPE_BOOL));
		return;
		}

	// Check for:	<addr> in <subnet>
	//		<addr> in set[subnet]
	//		<addr> in table[subnet] of ...
	if ( op1->GetType()->Tag() == TYPE_ADDR )
		{
		if ( op2->GetType()->Tag() == TYPE_SUBNET )
			{
			SetType(base_type(TYPE_BOOL));
			return;
			}

		if ( op2->GetType()->Tag() == TYPE_TABLE && op2->GetType()->AsTableType()->IsSubNetIndex() )
			{
			SetType(base_type(TYPE_BOOL));
			return;
			}
		}

	if ( op1->Tag() != EXPR_LIST )
		op1 = make_intrusive<ListExpr>(std::move(op1));

	ListExpr* lop1 = op1->AsListExpr();

	if ( ! op2->GetType()->MatchesIndex(lop1) )
		SetError("not an index type");
	else
		SetType(base_type(TYPE_BOOL));
	}

ValPtr InExpr::Fold(Val* v1, Val* v2) const
	{
	if ( v2->GetType()->Tag() == TYPE_STRING )
		{
		const String* s2 = v2->AsString();

		if ( v1->GetType()->Tag() == TYPE_PATTERN )
			{
			auto re = v1->As<PatternVal*>();
			return val_mgr->Bool(re->MatchAnywhere(s2) != 0);
			}

		const String* s1 = v1->AsString();

		// Could do better here e.g. Boyer-Moore if done repeatedly.
		auto s = reinterpret_cast<const unsigned char*>(s1->CheckString());
		auto res = util::strstr_n(s2->Len(), s2->Bytes(), s1->Len(), s) != -1;
		return val_mgr->Bool(res);
		}

	if ( v1->GetType()->Tag() == TYPE_ADDR && v2->GetType()->Tag() == TYPE_SUBNET )
		return val_mgr->Bool(v2->AsSubNetVal()->Contains(v1->AsAddr()));

	bool res;

	if ( is_vector(v2) )
		{
		auto vv2 = v2->AsVectorVal();
		auto ind = v1->AsListVal()->Idx(0)->CoerceToUnsigned();
		res = ind < vv2->Size() && vv2->ValAt(ind);
		}
	else
		res = (bool)v2->AsTableVal()->Find({NewRef{}, v1});

	return val_mgr->Bool(res);
	}

CallExpr::CallExpr(ExprPtr arg_func, ListExprPtr arg_args, bool in_hook)
	: Expr(EXPR_CALL), func(std::move(arg_func)), args(std::move(arg_args))
	{
	if ( func->IsError() || args->IsError() )
		{
		SetError();
		return;
		}

	const auto& func_type = func->GetType();

	if ( ! IsFunc(func_type->Tag()) )
		{
		func->Error("not a function");
		SetError();
		return;
		}

	if ( func_type->AsFuncType()->Flavor() == FUNC_FLAVOR_HOOK && ! in_hook )
		{
		func->Error("hook cannot be called directly, use hook operator");
		SetError();
		return;
		}

	if ( ! func_type->MatchesIndex(args.get()) )
		SetError("argument type mismatch in function call");
	else
		{
		const auto& yield = func_type->Yield();

		if ( ! yield )
			{
			switch ( func_type->AsFuncType()->Flavor() )
				{

				case FUNC_FLAVOR_FUNCTION:
					Error("function has no yield type");
					SetError();
					break;

				case FUNC_FLAVOR_EVENT:
					Error("event called in expression, use event statement instead");
					SetError();
					break;

				case FUNC_FLAVOR_HOOK:
					Error("hook has no yield type");
					SetError();
					break;

				default:
					Error("invalid function flavor");
					SetError();
					break;
				}
			}
		else
			SetType(yield);

		// Check for call to built-ins that can be statically analyzed.
		ValPtr func_val;

		if ( func->Tag() == EXPR_NAME &&
		     // This is cheating, but without it processing gets
		     // quite confused regarding "value used but not set"
		     // run-time errors when we apply this analysis during
		     // parsing.  Really we should instead do it after we've
		     // parsed the entire set of scripts.
		     util::streq(((NameExpr*)func.get())->Id()->Name(), "fmt") &&
		     // The following is needed because fmt might not yet
		     // be bound as a name.
		     did_builtin_init && (func_val = func->Eval(nullptr)) )
			{
			zeek::Func* f = func_val->AsFunc();
			if ( f->GetKind() == Func::BUILTIN_FUNC &&
			     ! check_built_in_call((BuiltinFunc*)f, this) )
				SetError();
			}
		}
	}

bool CallExpr::IsPure() const
	{
	if ( IsError() )
		return true;

	if ( ! func->IsPure() )
		return false;

	auto func_val = func->Eval(nullptr);

	if ( ! func_val )
		return false;

	zeek::Func* f = func_val->AsFunc();

	// Only recurse for built-in functions, as recursing on script
	// functions can lead to infinite recursion if the function being
	// called here happens to be recursive (either directly
	// or indirectly).
	bool pure = false;

	if ( f->GetKind() == Func::BUILTIN_FUNC )
		pure = f->IsPure() && args->IsPure();

	return pure;
	}

ValPtr CallExpr::Eval(Frame* f) const
	{
	if ( IsError() )
		return nullptr;

	// If we are inside a trigger condition, we may have already been
	// called, delayed, and then produced a result which is now cached.
	// Check for that.
	if ( f )
		{
		if ( trigger::Trigger* trigger = f->GetTrigger() )
			{
			if ( Val* v = trigger->Lookup(this) )
				{
				DBG_LOG(DBG_NOTIFIERS, "%s: provides cached function result", trigger->Name());
				return {NewRef{}, v};
				}
			}
		}

	ValPtr ret;
	auto func_val = func->Eval(f);
	auto v = eval_list(f, args.get());

	if ( func_val && v )
		{
		const zeek::Func* funcv = func_val->AsFunc();
		const CallExpr* current_call = f ? f->GetCall() : nullptr;

		if ( f )
			f->SetCall(this);

		auto& args = *v;
		ret = funcv->Invoke(&args, f);

		if ( f )
			f->SetCall(current_call);
		}

	return ret;
	}

TraversalCode CallExpr::Traverse(TraversalCallback* cb) const
	{
	TraversalCode tc = cb->PreExpr(this);
	HANDLE_TC_EXPR_PRE(tc);

	tc = func->Traverse(cb);
	HANDLE_TC_EXPR_PRE(tc);

	tc = args->Traverse(cb);
	HANDLE_TC_EXPR_PRE(tc);

	tc = cb->PostExpr(this);
	HANDLE_TC_EXPR_POST(tc);
	}

void CallExpr::ExprDescribe(ODesc* d) const
	{
	func->Describe(d);
	if ( d->IsReadable() || d->IsPortable() )
		{
		d->Add("(");
		args->Describe(d);
		d->Add(")");
		}
	else
		args->Describe(d);
	}

LambdaExpr::LambdaExpr(std::unique_ptr<function_ingredients> arg_ing, IDPList arg_outer_ids)
	: Expr(EXPR_LAMBDA)
	{
	ingredients = std::move(arg_ing);
	outer_ids = std::move(arg_outer_ids);

	SetType(ingredients->id->GetType());

	CheckCaptures();

	// Install a dummy version of the function globally for use only
	// when broker provides a closure.
	auto dummy_func = make_intrusive<ScriptFunc>(ingredients->id, ingredients->body,
	                                             ingredients->inits, ingredients->frame_size,
	                                             ingredients->priority);

	dummy_func->SetOuterIDs(outer_ids);

	// Get the body's "string" representation.
	ODesc d;
	dummy_func->Describe(&d);

	for ( ;; )
		{
		hash128_t h;
		KeyedHash::Hash128(d.Bytes(), d.Len(), &h);

		my_name = "lambda_<" + std::to_string(h[0]) + ">";
		auto fullname = make_full_var_name(current_module.data(), my_name.data());
		const auto& id = global_scope()->Find(fullname);

		if ( id )
			// Just try again to make a unique lambda name.
			// If two peer processes need to agree on the same
			// lambda name, this assumes they're loading the same
			// scripts and thus have the same hash collisions.
			d.Add(" ");
		else
			break;
		}

	// Install that in the global_scope
	auto id = install_ID(my_name.c_str(), current_module.c_str(), true, false);

	// Update lamb's name
	dummy_func->SetName(my_name.c_str());

	auto v = make_intrusive<FuncVal>(std::move(dummy_func));
	id->SetVal(std::move(v));
	id->SetType(ingredients->id->GetType());
	id->SetConst();
	}

void LambdaExpr::CheckCaptures()
	{
	auto ft = type->AsFuncType();
	const auto& captures = ft->GetCaptures();

	capture_by_ref = false;

	if ( ! captures )
		{
		if ( outer_ids.size() > 0 )
			{
			// TODO: Remove in v5.1: these deprecated closure semantics
			reporter->Warning(
				"use of outer identifiers in lambdas without [] captures is deprecated: %s%s",
				outer_ids.size() > 1 ? "e.g., " : "", outer_ids[0]->Name());
			capture_by_ref = true;
			}

		return;
		}

	std::set<const ID*> outer_is_matched;
	std::set<const ID*> capture_is_matched;

	for ( const auto& c : *captures )
		{
		auto cid = c.id.get();

		if ( ! cid )
			// This happens for undefined/inappropriate
			// identifiers listed in captures.  There's
			// already been an error message.
			continue;

		if ( capture_is_matched.count(cid) > 0 )
			{
			ExprError(util::fmt("%s listed multiple times in capture", cid->Name()));
			continue;
			}

		for ( auto id : outer_ids )
			if ( cid == id )
				{
				outer_is_matched.insert(id);
				capture_is_matched.insert(cid);
				break;
				}
		}

	for ( auto id : outer_ids )
		if ( outer_is_matched.count(id) == 0 )
			ExprError(util::fmt("%s is used inside lambda but not captured", id->Name()));

	for ( const auto& c : *captures )
		{
		auto cid = c.id.get();
		if ( cid && capture_is_matched.count(cid) == 0 )
			ExprError(util::fmt("%s is captured but not used inside lambda", cid->Name()));
		}
	}

ScopePtr LambdaExpr::GetScope() const
	{
	return ingredients->scope;
	}

ValPtr LambdaExpr::Eval(Frame* f) const
	{
	auto lamb = make_intrusive<ScriptFunc>(ingredients->id, ingredients->body, ingredients->inits,
	                                       ingredients->frame_size, ingredients->priority);

	if ( capture_by_ref )
		lamb->AddClosure(outer_ids, f);
	else
		lamb->CreateCaptures(f);

	// Set name to corresponding dummy func.
	// Allows for lookups by the receiver.
	lamb->SetName(my_name.c_str());

	return make_intrusive<FuncVal>(std::move(lamb));
	}

void LambdaExpr::ExprDescribe(ODesc* d) const
	{
	d->Add(expr_name(Tag()));
	ingredients->body->Describe(d);
	}

TraversalCode LambdaExpr::Traverse(TraversalCallback* cb) const
	{
	TraversalCode tc = cb->PreExpr(this);
	HANDLE_TC_EXPR_PRE(tc);

	tc = ingredients->body->Traverse(cb);
	HANDLE_TC_STMT_PRE(tc);

	tc = cb->PostExpr(this);
	HANDLE_TC_EXPR_POST(tc);
	}

EventExpr::EventExpr(const char* arg_name, ListExprPtr arg_args)
	: Expr(EXPR_EVENT), name(arg_name), args(std::move(arg_args))
	{
	EventHandler* h = event_registry->Lookup(name);

	if ( ! h )
		{
		h = new EventHandler(name.c_str());
		event_registry->Register(h);
		}

	h->SetUsed();

	handler = h;

	if ( args->IsError() )
		{
		SetError();
		return;
		}

	const auto& func_type = h->GetType();

	if ( ! func_type )
		{
		Error("not an event");
		SetError();
		return;
		}

	if ( ! func_type->MatchesIndex(args.get()) )
		SetError("argument type mismatch in event invocation");
	else
		{
		if ( func_type->Yield() )
			{
			Error("function invoked as an event");
			SetError();
			}
		}
	}

ValPtr EventExpr::Eval(Frame* f) const
	{
	if ( IsError() )
		return nullptr;

	auto v = eval_list(f, args.get());

	if ( handler )
		event_mgr.Enqueue(handler, std::move(*v));

	return nullptr;
	}

TraversalCode EventExpr::Traverse(TraversalCallback* cb) const
	{
	TraversalCode tc = cb->PreExpr(this);
	HANDLE_TC_EXPR_PRE(tc);

	tc = args->Traverse(cb);
	HANDLE_TC_EXPR_PRE(tc);

	tc = cb->PostExpr(this);
	HANDLE_TC_EXPR_POST(tc);
	}

void EventExpr::ExprDescribe(ODesc* d) const
	{
	d->Add(name.c_str());
	if ( d->IsReadable() || d->IsPortable() )
		{
		d->Add("(");
		args->Describe(d);
		d->Add(")");
		}
	else
		args->Describe(d);
	}

ListExpr::ListExpr() : Expr(EXPR_LIST)
	{
	SetType(make_intrusive<TypeList>());
	}

ListExpr::ListExpr(ExprPtr e) : Expr(EXPR_LIST)
	{
	SetType(make_intrusive<TypeList>());
	Append(std::move(e));
	}

ListExpr::~ListExpr()
	{
	for ( const auto& expr : exprs )
		Unref(expr);
	}

void ListExpr::Append(ExprPtr e)
	{
	exprs.push_back(e.release());
	((TypeList*)type.get())->Append(exprs.back()->GetType());
	}

bool ListExpr::IsPure() const
	{
	for ( const auto& expr : exprs )
		if ( ! expr->IsPure() )
			return false;

	return true;
	}

ValPtr ListExpr::Eval(Frame* f) const
	{
	auto v = make_intrusive<ListVal>(TYPE_ANY);

	for ( const auto& expr : exprs )
		{
		auto ev = expr->Eval(f);

		if ( ! ev )
			{
			RuntimeError("uninitialized list value");
			return nullptr;
			}

		v->Append(std::move(ev));
		}

	return v;
	}

TypePtr ListExpr::InitType() const
	{
	if ( exprs.empty() )
		{
		Error("empty list in untyped initialization");
		return nullptr;
		}

	if ( exprs[0]->IsRecordElement(nullptr) )
		{
		type_decl_list* types = new type_decl_list(exprs.length());
		for ( const auto& expr : exprs )
			{
			TypeDecl* td = new TypeDecl(nullptr, nullptr);
			if ( ! expr->IsRecordElement(td) )
				{
				expr->Error("record element expected");
				delete td;
				delete types;
				return nullptr;
				}

			types->push_back(td);
			}

		return make_intrusive<RecordType>(types);
		}

	else
		{
		auto tl = make_intrusive<TypeList>();

		for ( const auto& e : exprs )
			{
			const auto& ti = e->GetType();

			// Collapse any embedded sets or lists.
			if ( ti->IsSet() || ti->Tag() == TYPE_LIST )
				{
				TypeList* til = ti->IsSet() ? ti->AsSetType()->GetIndices().get()
				                            : ti->AsTypeList();

				if ( ! til->IsPure() || ! til->AllMatch(til->GetPureType(), true) )
					tl->Append({NewRef{}, til});
				else
					tl->Append(til->GetPureType());
				}
			else
				tl->Append(ti);
			}

		return tl;
		}
	}

ValPtr ListExpr::InitVal(const zeek::Type* t, ValPtr aggr) const
	{
	// While fairly similar to the EvalIntoAggregate() code,
	// we keep this separate since it also deals with initialization
	// idioms such as embedded aggregates and cross-product
	// expansion.
	if ( IsError() )
		return nullptr;

	// Check whether each element of this list itself matches t,
	// in which case we should expand as a ListVal.
	if ( ! aggr && type->AsTypeList()->AllMatch(t, true) )
		{
		auto v = make_intrusive<ListVal>(TYPE_ANY);
		const auto& tl = type->AsTypeList()->GetTypes();

		if ( exprs.length() != static_cast<int>(tl.size()) )
			{
			Error("index mismatch", t);
			return nullptr;
			}

		loop_over_list(exprs, i)
			{
			auto vi = exprs[i]->InitVal(tl[i].get(), nullptr);
			if ( ! vi )
				return nullptr;

			v->Append(std::move(vi));
			}

		return v;
		}

	if ( t->Tag() == TYPE_LIST )
		{
		if ( aggr )
			{
			Error("bad use of list in initialization", t);
			return nullptr;
			}

		const auto& tl = t->AsTypeList()->GetTypes();

		if ( exprs.length() != static_cast<int>(tl.size()) )
			{
			Error("index mismatch", t);
			return nullptr;
			}

		auto v = make_intrusive<ListVal>(TYPE_ANY);

		loop_over_list(exprs, i)
			{
			auto vi = exprs[i]->InitVal(tl[i].get(), nullptr);

			if ( ! vi )
				return nullptr;

			v->Append(std::move(vi));
			}

		return v;
		}

	if ( t->Tag() != TYPE_RECORD && t->Tag() != TYPE_TABLE && t->Tag() != TYPE_VECTOR )
		{
		if ( exprs.length() == 1 )
			// Allow "global x:int = { 5 }"
			return exprs[0]->InitVal(t, aggr);
		else
			{
			Error("aggregate initializer for scalar type", t);
			return nullptr;
			}
		}

	if ( ! aggr )
		Internal("missing aggregate in ListExpr::InitVal");

	if ( t->IsSet() )
		return AddSetInit(t, std::move(aggr));

	if ( t->Tag() == TYPE_VECTOR )
		{
		// v: vector = [10, 20, 30];
		VectorVal* vec = aggr->AsVectorVal();

		loop_over_list(exprs, i)
			{
			ExprPtr e = {NewRef{}, exprs[i]};
			const auto& vyt = vec->GetType()->AsVectorType()->Yield();
			auto promoted_e = check_and_promote_expr(e, vyt);

			if ( promoted_e )
				e = promoted_e;

			if ( ! vec->Assign(i, e->Eval(nullptr)) )
				{
				e->Error(util::fmt("type mismatch at index %d", i));
				return nullptr;
				}
			}

		return aggr;
		}

	// If we got this far, then it's either a table or record
	// initialization.  Both of those involve AssignExpr's, which
	// know how to add themselves to a table or record.  Another
	// possibility is an expression that evaluates itself to a
	// table, which we can then add to the aggregate.
	for ( const auto& e : exprs )
		{
		if ( e->Tag() == EXPR_ASSIGN || e->Tag() == EXPR_FIELD_ASSIGN )
			{
			if ( ! e->InitVal(t, aggr) )
				return nullptr;
			}
		else
			{
			if ( t->Tag() == TYPE_RECORD )
				{
				e->Error("bad record initializer", t);
				return nullptr;
				}

			auto v = e->Eval(nullptr);

			if ( ! same_type(v->GetType(), t) )
				{
				v->GetType()->Error("type clash in table initializer", t);
				return nullptr;
				}

			if ( ! v->AsTableVal()->AddTo(aggr->AsTableVal(), true) )
				return nullptr;
			}
		}

	return aggr;
	}

ValPtr ListExpr::AddSetInit(const zeek::Type* t, ValPtr aggr) const
	{
	if ( aggr->GetType()->Tag() != TYPE_TABLE )
		Internal("bad aggregate in ListExpr::AddSetInit");

	TableVal* tv = aggr->AsTableVal();
	const TableType* tt = tv->GetType()->AsTableType();
	const TypeList* it = tt->GetIndices().get();

	for ( const auto& expr : exprs )
		{
		ValPtr element;

		if ( expr->GetType()->IsSet() )
			// A set to flatten.
			element = expr->Eval(nullptr);
		else if ( expr->GetType()->Tag() == TYPE_LIST )
			element = expr->InitVal(it, nullptr);
		else
			element = expr->InitVal(it->GetTypes()[0].get(), nullptr);

		if ( ! element )
			return nullptr;

		if ( element->GetType()->IsSet() )
			{
			if ( ! same_type(element->GetType(), t) )
				{
				element->Error("type clash in set initializer", t);
				return nullptr;
				}

			if ( ! element->AsTableVal()->AddTo(tv, true) )
				return nullptr;

			continue;
			}

		if ( expr->GetType()->Tag() == TYPE_LIST )
			element = check_and_promote(std::move(element), it, true);
		else
			element = check_and_promote(std::move(element), it->GetTypes()[0].get(), true);

		if ( ! element )
			return nullptr;

		if ( ! tv->ExpandAndInit(std::move(element), nullptr) )
			return nullptr;
		}

	return aggr;
	}

void ListExpr::ExprDescribe(ODesc* d) const
	{
	d->AddCount(exprs.length());

	loop_over_list(exprs, i)
		{
		if ( (d->IsReadable() || d->IsPortable()) && i > 0 )
			d->Add(", ");

		exprs[i]->Describe(d);
		}
	}

ExprPtr ListExpr::MakeLvalue()
	{
	for ( const auto& expr : exprs )
		if ( expr->Tag() != EXPR_NAME )
			ExprError("can only assign to list of identifiers");

	return make_intrusive<RefExpr>(IntrusivePtr{NewRef{}, this});
	}

void ListExpr::Assign(Frame* f, ValPtr v)
	{
	ListVal* lv = v->AsListVal();

	if ( exprs.length() != lv->Length() )
		RuntimeError("mismatch in list lengths");

	loop_over_list(exprs, i) exprs[i]->Assign(f, lv->Idx(i));
	}

TraversalCode ListExpr::Traverse(TraversalCallback* cb) const
	{
	TraversalCode tc = cb->PreExpr(this);
	HANDLE_TC_EXPR_PRE(tc);

	for ( const auto& expr : exprs )
		{
		tc = expr->Traverse(cb);
		HANDLE_TC_EXPR_PRE(tc);
		}

	tc = cb->PostExpr(this);
	HANDLE_TC_EXPR_POST(tc);
	}

RecordAssignExpr::RecordAssignExpr(const ExprPtr& record, const ExprPtr& init_list, bool is_init)
	{
	const ExprPList& inits = init_list->AsListExpr()->Exprs();

	RecordType* lhs = record->GetType()->AsRecordType();

	// The inits have two forms:
	// 1) other records -- use all matching field names+types
	// 2) a string indicating the field name, then (as the next element)
	//    the value to use for that field.

	for ( const auto& init : inits )
		{
		if ( init->GetType()->Tag() == TYPE_RECORD )
			{
			RecordType* t = init->GetType()->AsRecordType();

			for ( int j = 0; j < t->NumFields(); ++j )
				{
				const char* field_name = t->FieldName(j);
				int field = lhs->FieldOffset(field_name);

				if ( field >= 0 && same_type(lhs->GetFieldType(field), t->GetFieldType(j)) )
					{
					auto fe_lhs = make_intrusive<FieldExpr>(record, field_name);
					auto fe_rhs = make_intrusive<FieldExpr>(IntrusivePtr{NewRef{}, init},
					                                        field_name);
					Append(get_assign_expr(std::move(fe_lhs), std::move(fe_rhs), is_init));
					}
				}
			}

		else if ( init->Tag() == EXPR_FIELD_ASSIGN )
			{
			FieldAssignExpr* rf = (FieldAssignExpr*)init;
			rf->Ref();

			const char* field_name = ""; // rf->FieldName();
			if ( lhs->HasField(field_name) )
				{
				auto fe_lhs = make_intrusive<FieldExpr>(record, field_name);
				ExprPtr fe_rhs = {NewRef{}, rf->Op()};
				Append(get_assign_expr(std::move(fe_lhs), std::move(fe_rhs), is_init));
				}
			else
				{
				std::string s = "No such field '";
				s += field_name;
				s += "'";
				init_list->SetError(s.c_str());
				}
			}

		else
			{
			init_list->SetError("bad record initializer");
			return;
			}
		}
	}

CastExpr::CastExpr(ExprPtr arg_op, TypePtr t) : UnaryExpr(EXPR_CAST, std::move(arg_op))
	{
	auto stype = Op()->GetType();

	SetType(std::move(t));

	if ( ! can_cast_value_to_type(stype.get(), GetType().get()) )
		ExprError("cast not supported");
	}

ValPtr CastExpr::Fold(Val* v) const
	{
	std::string error;
	auto res = cast_value({NewRef{}, v}, GetType(), error);

	if ( ! res )
		RuntimeError(error.c_str());

	return res;
	}

ValPtr cast_value(ValPtr v, const TypePtr& t, std::string& error)
	{
	auto nv = cast_value_to_type(v.get(), t.get());

	if ( nv )
		return nv;

	ODesc d;

	d.Add("invalid cast of value with type '");
	v->GetType()->Describe(&d);
	d.Add("' to type '");
	t->Describe(&d);
	d.Add("'");

	if ( same_type(v->GetType(), Broker::detail::DataVal::ScriptDataType()) &&
	     ! v->AsRecordVal()->HasField(0) )
		d.Add(" (nil $data field)");

	error = d.Description();
	return nullptr;
	}

void CastExpr::ExprDescribe(ODesc* d) const
	{
	Op()->Describe(d);
	d->Add(" as ");
	GetType()->Describe(d);
	}

IsExpr::IsExpr(ExprPtr arg_op, TypePtr arg_t)
	: UnaryExpr(EXPR_IS, std::move(arg_op)), t(std::move(arg_t))
	{
	SetType(base_type(TYPE_BOOL));
	}

ValPtr IsExpr::Fold(Val* v) const
	{
	if ( IsError() )
		return nullptr;

	return val_mgr->Bool(can_cast_value_to_type(v, t.get()));
	}

void IsExpr::ExprDescribe(ODesc* d) const
	{
	Op()->Describe(d);
	d->Add(" is ");
	t->Describe(d);
	}

ExprPtr get_assign_expr(ExprPtr op1, ExprPtr op2, bool is_init)
	{
	if ( op1->GetType()->Tag() == TYPE_RECORD && op2->GetType()->Tag() == TYPE_LIST )
		return make_intrusive<RecordAssignExpr>(std::move(op1), std::move(op2), is_init);

	else if ( op1->Tag() == EXPR_INDEX && op1->AsIndexExpr()->IsSlice() )
		return make_intrusive<IndexSliceAssignExpr>(std::move(op1), std::move(op2), is_init);

	else
		return make_intrusive<AssignExpr>(std::move(op1), std::move(op2), is_init);
	}

ExprPtr check_and_promote_expr(ExprPtr e, TypePtr t)
	{
	const auto& et = e->GetType();
	TypeTag e_tag = et->Tag();
	TypeTag t_tag = t->Tag();

	if ( t_tag == TYPE_ANY )
		{
		if ( e_tag != TYPE_ANY )
			return make_intrusive<CoerceToAnyExpr>(e);

		return e;
		}

	if ( e_tag == TYPE_ANY )
		return make_intrusive<CoerceFromAnyExpr>(e, t);

	if ( EitherArithmetic(t_tag, e_tag) )
		{
		if ( e_tag == t_tag )
			return e;

		if ( ! BothArithmetic(t_tag, e_tag) )
			{
			t->Error("arithmetic mixed with non-arithmetic", e.get());
			return nullptr;
			}

		TypeTag mt = max_type(t_tag, e_tag);
		if ( mt != t_tag )
			{
			t->Error("over-promotion of arithmetic value", e.get());
			return nullptr;
			}

		return make_intrusive<ArithCoerceExpr>(e, t_tag);
		}

	if ( t->Tag() == TYPE_RECORD && et->Tag() == TYPE_RECORD )
		{
		RecordType* t_r = t->AsRecordType();
		RecordType* et_r = et->AsRecordType();

		if ( same_type(t, et) )
			return e;

		if ( record_promotion_compatible(t_r, et_r) )
			return make_intrusive<RecordCoerceExpr>(e, IntrusivePtr{NewRef{}, t_r});

		t->Error("incompatible record types", e.get());
		return nullptr;
		}

	if ( ! same_type(t, et) )
		{
		if ( t->Tag() == TYPE_TABLE && et->Tag() == TYPE_TABLE &&
		     et->AsTableType()->IsUnspecifiedTable() )
			return make_intrusive<TableCoerceExpr>(e, IntrusivePtr{NewRef{}, t->AsTableType()});

		if ( t->Tag() == TYPE_VECTOR && et->Tag() == TYPE_VECTOR &&
		     et->AsVectorType()->IsUnspecifiedVector() )
			return make_intrusive<VectorCoerceExpr>(e, IntrusivePtr{NewRef{}, t->AsVectorType()});

		if ( t->Tag() != TYPE_ERROR && et->Tag() != TYPE_ERROR )
			t->Error("type clash", e.get());

		return nullptr;
		}

	return e;
	}

bool check_and_promote_exprs(ListExpr* const elements, TypeList* types)
	{
	ExprPList& el = elements->Exprs();
	const auto& tl = types->GetTypes();

	if ( tl.size() == 1 && tl[0]->Tag() == TYPE_ANY )
		return true;

	if ( el.length() != static_cast<int>(tl.size()) )
		{
		types->Error("indexing mismatch", elements);
		return false;
		}

	loop_over_list(el, i)
		{
		ExprPtr e = {NewRef{}, el[i]};
		auto promoted_e = check_and_promote_expr(e, tl[i]);

		if ( ! promoted_e )
			{
			e->Error("type mismatch", tl[i].get());
			return false;
			}

		if ( promoted_e != e )
			Unref(el.replace(i, promoted_e.release()));
		}

	return true;
	}

bool check_and_promote_args(ListExpr* const args, const RecordType* types)
	{
	ExprPList& el = args->Exprs();
	int ntypes = types->NumFields();

	// give variadic BIFs automatic pass
	if ( ntypes == 1 && types->FieldDecl(0)->type->Tag() == TYPE_ANY )
		return true;

	if ( el.length() < ntypes )
		{
		std::vector<ExprPtr> def_elements;

		// Start from rightmost parameter, work backward to fill in missing
		// arguments using &default expressions.
		for ( int i = ntypes - 1; i >= el.length(); --i )
			{
			auto td = types->FieldDecl(i);
			const auto& def_attr = td->attrs ? td->attrs->Find(ATTR_DEFAULT).get() : nullptr;

			if ( ! def_attr )
				{
				types->Error("parameter mismatch", args);
				return false;
				}

			// Don't use the default expression directly, as
			// doing so will wind up sharing its code across
			// different invocations that use the default
			// argument.  That works okay for the interpreter,
			// but if we transform the code we want that done
			// separately for each instance, rather than
			// one instance inheriting the transformed version
			// from another.
			const auto& e = def_attr->GetExpr();
			def_elements.emplace_back(e->Duplicate());
			}

		auto ne = def_elements.size();
		while ( ne )
			el.push_back(def_elements[--ne].release());
		}

	TypeList* tl = new TypeList();

	for ( int i = 0; i < types->NumFields(); ++i )
		tl->Append(types->GetFieldType(i));

	int rval = check_and_promote_exprs(args, tl);
	Unref(tl);

	return rval;
	}

bool check_and_promote_exprs_to_type(ListExpr* const elements, TypePtr t)
	{
	ExprPList& el = elements->Exprs();

	if ( t->Tag() == TYPE_ANY )
		return true;

	loop_over_list(el, i)
		{
		ExprPtr e = {NewRef{}, el[i]};
		auto promoted_e = check_and_promote_expr(e, t);

		if ( ! promoted_e )
			{
			e->Error("type mismatch", t.get());
			return false;
			}

		if ( promoted_e != e )
			Unref(el.replace(i, promoted_e.release()));
		}

	return true;
	}

std::optional<std::vector<ValPtr>> eval_list(Frame* f, const ListExpr* l)
	{
	const ExprPList& e = l->Exprs();
	auto rval = std::make_optional<std::vector<ValPtr>>();
	rval->reserve(e.length());

	for ( const auto& expr : e )
		{
		auto ev = expr->Eval(f);

		if ( ! ev )
			return {};

		rval->emplace_back(std::move(ev));
		}

	return rval;
	}

bool expr_greater(const Expr* e1, const Expr* e2)
	{
	return e1->Tag() > e2->Tag();
	}

	} // namespace zeek::detail
