expr
Turn Zig AST into untyped ZIR instructions.
When rl is discard, ptr, inferred_ptr, or inferred_ptr, the
result instruction can be used to inspect whether it is isNoReturn() but that is it,
it must otherwise not be used.
Function parameters
Parameters
- gz:*GenZir
- scope:*Scope
- node:Ast.Node.Index
Functions in this namespace
Functions
Source
Implementation
fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerError!Zir.Inst.Ref {
const astgen = gz.astgen;
const tree = astgen.tree;
switch (tree.nodeTag(node)) {
.root => unreachable, // Top-level declaration.
.test_decl => unreachable, // Top-level declaration.
.container_field_init => unreachable, // Top-level declaration.
.container_field_align => unreachable, // Top-level declaration.
.container_field => unreachable, // Top-level declaration.
.fn_decl => unreachable, // Top-level declaration.
.global_var_decl => unreachable, // Handled in `blockExpr`.
.local_var_decl => unreachable, // Handled in `blockExpr`.
.simple_var_decl => unreachable, // Handled in `blockExpr`.
.aligned_var_decl => unreachable, // Handled in `blockExpr`.
.@"defer" => unreachable, // Handled in `blockExpr`.
.@"errdefer" => unreachable, // Handled in `blockExpr`.
.switch_case => unreachable, // Handled in `switchExpr`.
.switch_case_inline => unreachable, // Handled in `switchExpr`.
.switch_case_one => unreachable, // Handled in `switchExpr`.
.switch_case_inline_one => unreachable, // Handled in `switchExpr`.
.switch_range => unreachable, // Handled in `switchExpr`.
.asm_output => unreachable, // Handled in `asmExpr`.
.asm_input => unreachable, // Handled in `asmExpr`.
.for_range => unreachable, // Handled in `forExpr`.
.assign => {
try assign(gz, scope, node);
return rvalue(gz, ri, .void_value, node);
},
.assign_destructure => {
// Note that this variant does not declare any new var/const: that
// variant is handled by `blockExprStmts`.
try assignDestructure(gz, scope, node);
return rvalue(gz, ri, .void_value, node);
},
.assign_shl => {
try assignShift(gz, scope, node, .shl);
return rvalue(gz, ri, .void_value, node);
},
.assign_shl_sat => {
try assignShiftSat(gz, scope, node);
return rvalue(gz, ri, .void_value, node);
},
.assign_shr => {
try assignShift(gz, scope, node, .shr);
return rvalue(gz, ri, .void_value, node);
},
.assign_bit_and => {
try assignOp(gz, scope, node, .bit_and);
return rvalue(gz, ri, .void_value, node);
},
.assign_bit_or => {
try assignOp(gz, scope, node, .bit_or);
return rvalue(gz, ri, .void_value, node);
},
.assign_bit_xor => {
try assignOp(gz, scope, node, .xor);
return rvalue(gz, ri, .void_value, node);
},
.assign_div => {
try assignOp(gz, scope, node, .div);
return rvalue(gz, ri, .void_value, node);
},
.assign_sub => {
try assignOp(gz, scope, node, .sub);
return rvalue(gz, ri, .void_value, node);
},
.assign_sub_wrap => {
try assignOp(gz, scope, node, .subwrap);
return rvalue(gz, ri, .void_value, node);
},
.assign_sub_sat => {
try assignOp(gz, scope, node, .sub_sat);
return rvalue(gz, ri, .void_value, node);
},
.assign_mod => {
try assignOp(gz, scope, node, .mod_rem);
return rvalue(gz, ri, .void_value, node);
},
.assign_add => {
try assignOp(gz, scope, node, .add);
return rvalue(gz, ri, .void_value, node);
},
.assign_add_wrap => {
try assignOp(gz, scope, node, .addwrap);
return rvalue(gz, ri, .void_value, node);
},
.assign_add_sat => {
try assignOp(gz, scope, node, .add_sat);
return rvalue(gz, ri, .void_value, node);
},
.assign_mul => {
try assignOp(gz, scope, node, .mul);
return rvalue(gz, ri, .void_value, node);
},
.assign_mul_wrap => {
try assignOp(gz, scope, node, .mulwrap);
return rvalue(gz, ri, .void_value, node);
},
.assign_mul_sat => {
try assignOp(gz, scope, node, .mul_sat);
return rvalue(gz, ri, .void_value, node);
},
// zig fmt: off
.shl => return shiftOp(gz, scope, ri, node, tree.nodeData(node).node_and_node[0], tree.nodeData(node).node_and_node[1], .shl),
.shr => return shiftOp(gz, scope, ri, node, tree.nodeData(node).node_and_node[0], tree.nodeData(node).node_and_node[1], .shr),
.add => return simpleBinOp(gz, scope, ri, node, .add),
.add_wrap => return simpleBinOp(gz, scope, ri, node, .addwrap),
.add_sat => return simpleBinOp(gz, scope, ri, node, .add_sat),
.sub => return simpleBinOp(gz, scope, ri, node, .sub),
.sub_wrap => return simpleBinOp(gz, scope, ri, node, .subwrap),
.sub_sat => return simpleBinOp(gz, scope, ri, node, .sub_sat),
.mul => return simpleBinOp(gz, scope, ri, node, .mul),
.mul_wrap => return simpleBinOp(gz, scope, ri, node, .mulwrap),
.mul_sat => return simpleBinOp(gz, scope, ri, node, .mul_sat),
.div => return simpleBinOp(gz, scope, ri, node, .div),
.mod => return simpleBinOp(gz, scope, ri, node, .mod_rem),
.shl_sat => return simpleBinOp(gz, scope, ri, node, .shl_sat),
.bit_and => return simpleBinOp(gz, scope, ri, node, .bit_and),
.bit_or => return simpleBinOp(gz, scope, ri, node, .bit_or),
.bit_xor => return simpleBinOp(gz, scope, ri, node, .xor),
.bang_equal => return simpleBinOp(gz, scope, ri, node, .cmp_neq),
.equal_equal => return simpleBinOp(gz, scope, ri, node, .cmp_eq),
.greater_than => return simpleBinOp(gz, scope, ri, node, .cmp_gt),
.greater_or_equal => return simpleBinOp(gz, scope, ri, node, .cmp_gte),
.less_than => return simpleBinOp(gz, scope, ri, node, .cmp_lt),
.less_or_equal => return simpleBinOp(gz, scope, ri, node, .cmp_lte),
.array_cat => return simpleBinOp(gz, scope, ri, node, .array_cat),
.array_mult => {
// This syntax form does not currently use the result type in the language specification.
// However, the result type can be used to emit more optimal code for large multiplications by
// having Sema perform a coercion before the multiplication operation.
const lhs_node, const rhs_node = tree.nodeData(node).node_and_node;
const result = try gz.addPlNode(.array_mul, node, Zir.Inst.ArrayMul{
.res_ty = if (try ri.rl.resultType(gz, node)) |t| t else .none,
.lhs = try expr(gz, scope, .{ .rl = .none }, lhs_node),
.rhs = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .usize_type } }, rhs_node, .array_mul_factor),
});
return rvalue(gz, ri, result, node);
},
.error_union, .merge_error_sets => |tag| {
const inst_tag: Zir.Inst.Tag = switch (tag) {
.error_union => .error_union_type,
.merge_error_sets => .merge_error_sets,
else => unreachable,
};
const lhs_node, const rhs_node = tree.nodeData(node).node_and_node;
const lhs = try reachableTypeExpr(gz, scope, lhs_node, node);
const rhs = try reachableTypeExpr(gz, scope, rhs_node, node);
const result = try gz.addPlNode(inst_tag, node, Zir.Inst.Bin{ .lhs = lhs, .rhs = rhs });
return rvalue(gz, ri, result, node);
},
.bool_and => return boolBinOp(gz, scope, ri, node, .bool_br_and),
.bool_or => return boolBinOp(gz, scope, ri, node, .bool_br_or),
.bool_not => return simpleUnOp(gz, scope, ri, node, .{ .rl = .none }, tree.nodeData(node).node, .bool_not),
.bit_not => return simpleUnOp(gz, scope, ri, node, .{ .rl = .none }, tree.nodeData(node).node, .bit_not),
.negation => return negation(gz, scope, ri, node),
.negation_wrap => return simpleUnOp(gz, scope, ri, node, .{ .rl = .none }, tree.nodeData(node).node, .negate_wrap),
.identifier => return identifier(gz, scope, ri, node, null),
.asm_simple,
.@"asm",
=> return asmExpr(gz, scope, ri, node, tree.fullAsm(node).?),
.asm_legacy => {
return astgen.failNodeNotes(node, "legacy asm clobbers syntax", .{}, &[_]u32{
try astgen.errNoteNode(node, "use 'zig fmt' to auto-upgrade", .{}),
});
},
.string_literal => return stringLiteral(gz, ri, node),
.multiline_string_literal => return multilineStringLiteral(gz, ri, node),
.number_literal => return numberLiteral(gz, ri, node, node, .positive),
// zig fmt: on
.builtin_call_two,
.builtin_call_two_comma,
.builtin_call,
.builtin_call_comma,
=> {
var buf: [2]Ast.Node.Index = undefined;
const params = tree.builtinCallParams(&buf, node).?;
return builtinCall(gz, scope, ri, node, params, false);
},
.call_one,
.call_one_comma,
.call,
.call_comma,
=> {
var buf: [1]Ast.Node.Index = undefined;
return callExpr(gz, scope, ri, .none, node, tree.fullCall(&buf, node).?);
},
.unreachable_literal => {
try emitDbgNode(gz, node);
_ = try gz.addAsIndex(.{
.tag = .@"unreachable",
.data = .{ .@"unreachable" = .{
.src_node = gz.nodeIndexToRelative(node),
} },
});
return Zir.Inst.Ref.unreachable_value;
},
.@"return" => return ret(gz, scope, node),
.field_access => return fieldAccess(gz, scope, ri, node),
.if_simple,
.@"if",
=> {
const if_full = tree.fullIf(node).?;
no_switch_on_err: {
const error_token = if_full.error_token orelse break :no_switch_on_err;
const else_node = if_full.ast.else_expr.unwrap() orelse break :no_switch_on_err;
const full_switch = tree.fullSwitch(else_node) orelse break :no_switch_on_err;
if (full_switch.label_token != null) break :no_switch_on_err;
if (tree.nodeTag(full_switch.ast.condition) != .identifier) break :no_switch_on_err;
if (!mem.eql(u8, tree.tokenSlice(error_token), tree.tokenSlice(tree.nodeMainToken(full_switch.ast.condition)))) break :no_switch_on_err;
return switchExprErrUnion(gz, scope, ri.br(), node, .@"if");
}
return ifExpr(gz, scope, ri.br(), node, if_full);
},
.while_simple,
.while_cont,
.@"while",
=> return whileExpr(gz, scope, ri.br(), node, tree.fullWhile(node).?, false),
.for_simple, .@"for" => return forExpr(gz, scope, ri.br(), node, tree.fullFor(node).?, false),
.slice_open,
.slice,
.slice_sentinel,
=> {
const full = tree.fullSlice(node).?;
if (full.ast.end != .none and
tree.nodeTag(full.ast.sliced) == .slice_open and
nodeIsTriviallyZero(tree, full.ast.start))
{
const lhs_extra = tree.sliceOpen(full.ast.sliced).ast;
const lhs = try expr(gz, scope, .{ .rl = .ref }, lhs_extra.sliced);
const start = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .usize_type } }, lhs_extra.start);
const cursor = maybeAdvanceSourceCursorToMainToken(gz, node);
const len = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .usize_type } }, full.ast.end.unwrap().?);
const sentinel = if (full.ast.sentinel.unwrap()) |sentinel| try expr(gz, scope, .{ .rl = .none }, sentinel) else .none;
try emitDbgStmt(gz, cursor);
const result = try gz.addPlNode(.slice_length, node, Zir.Inst.SliceLength{
.lhs = lhs,
.start = start,
.len = len,
.start_src_node_offset = gz.nodeIndexToRelative(full.ast.sliced),
.sentinel = sentinel,
});
return rvalue(gz, ri, result, node);
}
const lhs = try expr(gz, scope, .{ .rl = .ref }, full.ast.sliced);
const cursor = maybeAdvanceSourceCursorToMainToken(gz, node);
const start = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .usize_type } }, full.ast.start);
const end = if (full.ast.end.unwrap()) |end| try expr(gz, scope, .{ .rl = .{ .coerced_ty = .usize_type } }, end) else .none;
const sentinel = if (full.ast.sentinel.unwrap()) |sentinel| s: {
const sentinel_ty = try gz.addUnNode(.slice_sentinel_ty, lhs, node);
break :s try expr(gz, scope, .{ .rl = .{ .coerced_ty = sentinel_ty } }, sentinel);
} else .none;
try emitDbgStmt(gz, cursor);
if (sentinel != .none) {
const result = try gz.addPlNode(.slice_sentinel, node, Zir.Inst.SliceSentinel{
.lhs = lhs,
.start = start,
.end = end,
.sentinel = sentinel,
});
return rvalue(gz, ri, result, node);
} else if (end != .none) {
const result = try gz.addPlNode(.slice_end, node, Zir.Inst.SliceEnd{
.lhs = lhs,
.start = start,
.end = end,
});
return rvalue(gz, ri, result, node);
} else {
const result = try gz.addPlNode(.slice_start, node, Zir.Inst.SliceStart{
.lhs = lhs,
.start = start,
});
return rvalue(gz, ri, result, node);
}
},
.deref => {
const lhs = try expr(gz, scope, .{ .rl = .none }, tree.nodeData(node).node);
_ = try gz.addUnNode(.validate_deref, lhs, node);
switch (ri.rl) {
.ref, .ref_coerced_ty => return lhs,
else => {
const result = try gz.addUnNode(.load, lhs, node);
return rvalue(gz, ri, result, node);
},
}
},
.address_of => {
const operand_rl: ResultInfo.Loc = if (try ri.rl.resultType(gz, node)) |res_ty_inst| rl: {
_ = try gz.addUnTok(.validate_ref_ty, res_ty_inst, tree.firstToken(node));
break :rl .{ .ref_coerced_ty = res_ty_inst };
} else .ref;
const result = try expr(gz, scope, .{ .rl = operand_rl }, tree.nodeData(node).node);
return rvalue(gz, ri, result, node);
},
.optional_type => {
const operand = try typeExpr(gz, scope, tree.nodeData(node).node);
const result = try gz.addUnNode(.optional_type, operand, node);
return rvalue(gz, ri, result, node);
},
.unwrap_optional => switch (ri.rl) {
.ref, .ref_coerced_ty => {
const lhs = try expr(gz, scope, .{ .rl = .ref }, tree.nodeData(node).node_and_token[0]);
const cursor = maybeAdvanceSourceCursorToMainToken(gz, node);
try emitDbgStmt(gz, cursor);
return gz.addUnNode(.optional_payload_safe_ptr, lhs, node);
},
else => {
const lhs = try expr(gz, scope, .{ .rl = .none }, tree.nodeData(node).node_and_token[0]);
const cursor = maybeAdvanceSourceCursorToMainToken(gz, node);
try emitDbgStmt(gz, cursor);
return rvalue(gz, ri, try gz.addUnNode(.optional_payload_safe, lhs, node), node);
},
},
.block_two,
.block_two_semicolon,
.block,
.block_semicolon,
=> {
var buf: [2]Ast.Node.Index = undefined;
const statements = tree.blockStatements(&buf, node).?;
return blockExpr(gz, scope, ri, node, statements, .normal);
},
.enum_literal => if (try ri.rl.resultType(gz, node)) |res_ty| {
const str_index = try astgen.identAsString(tree.nodeMainToken(node));
const res = try gz.addPlNode(.decl_literal, node, Zir.Inst.Field{
.lhs = res_ty,
.field_name_start = str_index,
});
switch (ri.rl) {
.discard, .none, .ref => unreachable, // no result type
.ty, .coerced_ty => return res, // `decl_literal` does the coercion for us
.ref_coerced_ty, .ptr, .inferred_ptr, .destructure => return rvalue(gz, ri, res, node),
}
} else return simpleStrTok(gz, ri, tree.nodeMainToken(node), node, .enum_literal),
.error_value => return simpleStrTok(gz, ri, tree.nodeMainToken(node) + 2, node, .error_value),
// TODO restore this when implementing https://github.com/ziglang/zig/issues/6025
// .anyframe_literal => return rvalue(gz, ri, .anyframe_type, node),
.anyframe_literal => {
const result = try gz.addUnNode(.anyframe_type, .void_type, node);
return rvalue(gz, ri, result, node);
},
.anyframe_type => {
const return_type = try typeExpr(gz, scope, tree.nodeData(node).token_and_node[1]);
const result = try gz.addUnNode(.anyframe_type, return_type, node);
return rvalue(gz, ri, result, node);
},
.@"catch" => {
const catch_token = tree.nodeMainToken(node);
const payload_token: ?Ast.TokenIndex = if (tree.tokenTag(catch_token + 1) == .pipe)
catch_token + 2
else
null;
no_switch_on_err: {
const capture_token = payload_token orelse break :no_switch_on_err;
const full_switch = tree.fullSwitch(tree.nodeData(node).node_and_node[1]) orelse break :no_switch_on_err;
if (full_switch.label_token != null) break :no_switch_on_err;
if (tree.nodeTag(full_switch.ast.condition) != .identifier) break :no_switch_on_err;
if (!mem.eql(u8, tree.tokenSlice(capture_token), tree.tokenSlice(tree.nodeMainToken(full_switch.ast.condition)))) break :no_switch_on_err;
return switchExprErrUnion(gz, scope, ri.br(), node, .@"catch");
}
switch (ri.rl) {
.ref, .ref_coerced_ty => return orelseCatchExpr(
gz,
scope,
ri,
node,
.is_non_err_ptr,
.err_union_payload_unsafe_ptr,
.err_union_code_ptr,
payload_token,
),
else => return orelseCatchExpr(
gz,
scope,
ri,
node,
.is_non_err,
.err_union_payload_unsafe,
.err_union_code,
payload_token,
),
}
},
.@"orelse" => switch (ri.rl) {
.ref, .ref_coerced_ty => return orelseCatchExpr(
gz,
scope,
ri,
node,
.is_non_null_ptr,
.optional_payload_unsafe_ptr,
undefined,
null,
),
else => return orelseCatchExpr(
gz,
scope,
ri,
node,
.is_non_null,
.optional_payload_unsafe,
undefined,
null,
),
},
.ptr_type_aligned,
.ptr_type_sentinel,
.ptr_type,
.ptr_type_bit_range,
=> return ptrType(gz, scope, ri, node, tree.fullPtrType(node).?),
.container_decl,
.container_decl_trailing,
.container_decl_arg,
.container_decl_arg_trailing,
.container_decl_two,
.container_decl_two_trailing,
.tagged_union,
.tagged_union_trailing,
.tagged_union_enum_tag,
.tagged_union_enum_tag_trailing,
.tagged_union_two,
.tagged_union_two_trailing,
=> {
var buf: [2]Ast.Node.Index = undefined;
return containerDecl(gz, scope, ri, node, tree.fullContainerDecl(&buf, node).?, .anon);
},
.@"break" => return breakExpr(gz, scope, node),
.@"continue" => return continueExpr(gz, scope, node),
.grouped_expression => return expr(gz, scope, ri, tree.nodeData(node).node_and_token[0]),
.array_type => return arrayType(gz, scope, ri, node),
.array_type_sentinel => return arrayTypeSentinel(gz, scope, ri, node),
.char_literal => return charLiteral(gz, ri, node),
.error_set_decl => return errorSetDecl(gz, ri, node),
.array_access => return arrayAccess(gz, scope, ri, node),
.@"comptime" => return comptimeExprAst(gz, scope, ri, node),
.@"switch", .switch_comma => return switchExpr(gz, scope, ri.br(), node, tree.fullSwitch(node).?),
.@"nosuspend" => return nosuspendExpr(gz, scope, ri, node),
.@"suspend" => return suspendExpr(gz, scope, node),
.@"resume" => return resumeExpr(gz, scope, ri, node),
.@"try" => return tryExpr(gz, scope, ri, node, tree.nodeData(node).node),
.array_init_one,
.array_init_one_comma,
.array_init_dot_two,
.array_init_dot_two_comma,
.array_init_dot,
.array_init_dot_comma,
.array_init,
.array_init_comma,
=> {
var buf: [2]Ast.Node.Index = undefined;
return arrayInitExpr(gz, scope, ri, node, tree.fullArrayInit(&buf, node).?);
},
.struct_init_one,
.struct_init_one_comma,
.struct_init_dot_two,
.struct_init_dot_two_comma,
.struct_init_dot,
.struct_init_dot_comma,
.struct_init,
.struct_init_comma,
=> {
var buf: [2]Ast.Node.Index = undefined;
return structInitExpr(gz, scope, ri, node, tree.fullStructInit(&buf, node).?);
},
.fn_proto_simple,
.fn_proto_multi,
.fn_proto_one,
.fn_proto,
=> {
var buf: [1]Ast.Node.Index = undefined;
return fnProtoExpr(gz, scope, ri, node, tree.fullFnProto(&buf, node).?);
},
}
}