3680 lines
152 KiB
C++
3680 lines
152 KiB
C++
/*
|
|
* Copyright (c) 2015 Andrew Kelley
|
|
*
|
|
* This file is part of zig, which is MIT licensed.
|
|
* See http://opensource.org/licenses/MIT
|
|
*/
|
|
|
|
#include "analyze.hpp"
|
|
#include "parser.hpp"
|
|
#include "error.hpp"
|
|
#include "zig_llvm.hpp"
|
|
#include "os.hpp"
|
|
|
|
static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
TypeTableEntry *expected_type, AstNode *node);
|
|
static TypeTableEntry *eval_const_expr(CodeGen *g, BlockContext *context,
|
|
AstNode *node, AstNodeNumberLiteral *out_number_literal);
|
|
static void collect_type_decl_deps(CodeGen *g, ImportTableEntry *import, AstNode *type_node, TopLevelDecl *decl_node);
|
|
static VariableTableEntry *analyze_variable_declaration(CodeGen *g, ImportTableEntry *import,
|
|
BlockContext *context, TypeTableEntry *expected_type, AstNode *node);
|
|
static void resolve_struct_type(CodeGen *g, ImportTableEntry *import, TypeTableEntry *struct_type);
|
|
|
|
static AstNode *first_executing_node(AstNode *node) {
|
|
switch (node->type) {
|
|
case NodeTypeFnCallExpr:
|
|
return first_executing_node(node->data.fn_call_expr.fn_ref_expr);
|
|
case NodeTypeBinOpExpr:
|
|
return first_executing_node(node->data.bin_op_expr.op1);
|
|
case NodeTypeArrayAccessExpr:
|
|
return first_executing_node(node->data.array_access_expr.array_ref_expr);
|
|
case NodeTypeSliceExpr:
|
|
return first_executing_node(node->data.slice_expr.array_ref_expr);
|
|
case NodeTypeFieldAccessExpr:
|
|
return first_executing_node(node->data.field_access_expr.struct_expr);
|
|
case NodeTypeCastExpr:
|
|
return first_executing_node(node->data.cast_expr.expr);
|
|
case NodeTypeRoot:
|
|
case NodeTypeRootExportDecl:
|
|
case NodeTypeFnProto:
|
|
case NodeTypeFnDef:
|
|
case NodeTypeFnDecl:
|
|
case NodeTypeParamDecl:
|
|
case NodeTypeType:
|
|
case NodeTypeBlock:
|
|
case NodeTypeExternBlock:
|
|
case NodeTypeDirective:
|
|
case NodeTypeReturnExpr:
|
|
case NodeTypeVariableDeclaration:
|
|
case NodeTypeNumberLiteral:
|
|
case NodeTypeStringLiteral:
|
|
case NodeTypeCharLiteral:
|
|
case NodeTypeUnreachable:
|
|
case NodeTypeSymbol:
|
|
case NodeTypePrefixOpExpr:
|
|
case NodeTypeUse:
|
|
case NodeTypeVoid:
|
|
case NodeTypeBoolLiteral:
|
|
case NodeTypeNullLiteral:
|
|
case NodeTypeIfBoolExpr:
|
|
case NodeTypeIfVarExpr:
|
|
case NodeTypeLabel:
|
|
case NodeTypeGoto:
|
|
case NodeTypeBreak:
|
|
case NodeTypeContinue:
|
|
case NodeTypeAsmExpr:
|
|
case NodeTypeStructDecl:
|
|
case NodeTypeStructField:
|
|
case NodeTypeStructValueExpr:
|
|
case NodeTypeStructValueField:
|
|
case NodeTypeWhileExpr:
|
|
case NodeTypeCompilerFnExpr:
|
|
case NodeTypeCompilerFnType:
|
|
return node;
|
|
}
|
|
zig_panic("unreachable");
|
|
}
|
|
|
|
void add_node_error(CodeGen *g, AstNode *node, Buf *msg) {
|
|
ErrorMsg *err = allocate<ErrorMsg>(1);
|
|
err->line_start = node->line;
|
|
err->column_start = node->column;
|
|
err->line_end = -1;
|
|
err->column_end = -1;
|
|
err->msg = msg;
|
|
err->path = node->owner->path;
|
|
err->source = node->owner->source_code;
|
|
err->line_offsets = node->owner->line_offsets;
|
|
|
|
g->errors.append(err);
|
|
}
|
|
|
|
static int parse_version_string(Buf *buf, int *major, int *minor, int *patch) {
|
|
char *dot1 = strstr(buf_ptr(buf), ".");
|
|
if (!dot1)
|
|
return ErrorInvalidFormat;
|
|
char *dot2 = strstr(dot1 + 1, ".");
|
|
if (!dot2)
|
|
return ErrorInvalidFormat;
|
|
|
|
*major = (int)strtol(buf_ptr(buf), nullptr, 10);
|
|
*minor = (int)strtol(dot1 + 1, nullptr, 10);
|
|
*patch = (int)strtol(dot2 + 1, nullptr, 10);
|
|
|
|
return ErrorNone;
|
|
}
|
|
|
|
static void set_root_export_version(CodeGen *g, Buf *version_buf, AstNode *node) {
|
|
int err;
|
|
if ((err = parse_version_string(version_buf, &g->version_major, &g->version_minor, &g->version_patch))) {
|
|
add_node_error(g, node,
|
|
buf_sprintf("invalid version string"));
|
|
}
|
|
}
|
|
|
|
TypeTableEntry *new_type_table_entry(TypeTableEntryId id) {
|
|
TypeTableEntry *entry = allocate<TypeTableEntry>(1);
|
|
entry->arrays_by_size.init(2);
|
|
entry->id = id;
|
|
|
|
switch (id) {
|
|
case TypeTableEntryIdInvalid:
|
|
case TypeTableEntryIdMetaType:
|
|
case TypeTableEntryIdVoid:
|
|
case TypeTableEntryIdBool:
|
|
case TypeTableEntryIdUnreachable:
|
|
case TypeTableEntryIdInt:
|
|
case TypeTableEntryIdFloat:
|
|
case TypeTableEntryIdPointer:
|
|
case TypeTableEntryIdArray:
|
|
case TypeTableEntryIdNumberLiteral:
|
|
case TypeTableEntryIdMaybe:
|
|
// nothing to init
|
|
break;
|
|
case TypeTableEntryIdStruct:
|
|
entry->data.structure.fn_table.init(8);
|
|
break;
|
|
case TypeTableEntryIdEnum:
|
|
entry->data.enumeration.fn_table.init(8);
|
|
break;
|
|
|
|
}
|
|
|
|
return entry;
|
|
}
|
|
|
|
static NumLit get_number_literal_kind_unsigned(uint64_t x) {
|
|
if (x <= UINT8_MAX) {
|
|
return NumLitU8;
|
|
} else if (x <= UINT16_MAX) {
|
|
return NumLitU16;
|
|
} else if (x <= UINT32_MAX) {
|
|
return NumLitU32;
|
|
} else {
|
|
return NumLitU64;
|
|
}
|
|
}
|
|
|
|
static TypeTableEntry *get_number_literal_type_unsigned(CodeGen *g, uint64_t x) {
|
|
return g->num_lit_types[get_number_literal_kind_unsigned(x)];
|
|
}
|
|
|
|
static TypeTableEntry *get_int_type_unsigned(CodeGen *g, uint64_t x) {
|
|
switch (get_number_literal_kind_unsigned(x)) {
|
|
case NumLitU8:
|
|
return g->builtin_types.entry_u8;
|
|
case NumLitU16:
|
|
return g->builtin_types.entry_u16;
|
|
case NumLitU32:
|
|
return g->builtin_types.entry_u32;
|
|
case NumLitU64:
|
|
return g->builtin_types.entry_u64;
|
|
default:
|
|
zig_unreachable();
|
|
}
|
|
}
|
|
|
|
static TypeTableEntry *get_meta_type(CodeGen *g, TypeTableEntry *child_type) {
|
|
if (child_type->meta_parent) {
|
|
return child_type->meta_parent;
|
|
} else {
|
|
TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdMetaType);
|
|
buf_resize(&entry->name, 0);
|
|
buf_appendf(&entry->name, "(%s declaration)", buf_ptr(&child_type->name));
|
|
|
|
entry->data.meta_type.child_type = child_type;
|
|
child_type->meta_parent = entry;
|
|
return entry;
|
|
}
|
|
}
|
|
|
|
TypeTableEntry *get_pointer_to_type(CodeGen *g, TypeTableEntry *child_type, bool is_const, bool is_noalias) {
|
|
TypeTableEntry **parent_pointer = &child_type->pointer_parent[(is_const ? 1 : 0)][(is_noalias ? 1 : 0)];
|
|
if (*parent_pointer) {
|
|
return *parent_pointer;
|
|
} else {
|
|
TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdPointer);
|
|
entry->type_ref = LLVMPointerType(child_type->type_ref, 0);
|
|
|
|
const char *const_str = is_const ? "const " : "";
|
|
const char *noalias_str = is_noalias ? "noalias " : "";
|
|
buf_resize(&entry->name, 0);
|
|
buf_appendf(&entry->name, "&%s%s%s", const_str, noalias_str, buf_ptr(&child_type->name));
|
|
|
|
entry->size_in_bits = g->pointer_size_bytes * 8;
|
|
entry->align_in_bits = g->pointer_size_bytes * 8;
|
|
assert(child_type->di_type);
|
|
entry->di_type = LLVMZigCreateDebugPointerType(g->dbuilder, child_type->di_type,
|
|
entry->size_in_bits, entry->align_in_bits, buf_ptr(&entry->name));
|
|
entry->data.pointer.child_type = child_type;
|
|
entry->data.pointer.is_const = is_const;
|
|
entry->data.pointer.is_noalias = is_noalias;
|
|
|
|
*parent_pointer = entry;
|
|
return entry;
|
|
}
|
|
}
|
|
|
|
static TypeTableEntry *get_maybe_type(CodeGen *g, ImportTableEntry *import, TypeTableEntry *child_type) {
|
|
if (child_type->maybe_parent) {
|
|
TypeTableEntry *entry = child_type->maybe_parent;
|
|
return entry;
|
|
} else {
|
|
TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdMaybe);
|
|
// create a struct with a boolean whether this is the null value
|
|
assert(child_type->type_ref);
|
|
LLVMTypeRef elem_types[] = {
|
|
child_type->type_ref,
|
|
LLVMInt1Type(),
|
|
};
|
|
entry->type_ref = LLVMStructType(elem_types, 2, false);
|
|
buf_resize(&entry->name, 0);
|
|
buf_appendf(&entry->name, "?%s", buf_ptr(&child_type->name));
|
|
entry->size_in_bits = child_type->size_in_bits + 8;
|
|
entry->align_in_bits = child_type->align_in_bits;
|
|
assert(child_type->di_type);
|
|
|
|
|
|
LLVMZigDIScope *compile_unit_scope = LLVMZigCompileUnitToScope(g->compile_unit);
|
|
LLVMZigDIFile *di_file = nullptr;
|
|
unsigned line = 0;
|
|
entry->di_type = LLVMZigCreateReplaceableCompositeType(g->dbuilder,
|
|
LLVMZigTag_DW_structure_type(), buf_ptr(&entry->name),
|
|
compile_unit_scope, di_file, line);
|
|
|
|
LLVMZigDIType *di_element_types[] = {
|
|
LLVMZigCreateDebugMemberType(g->dbuilder, LLVMZigTypeToScope(entry->di_type),
|
|
"val", di_file, line, child_type->size_in_bits, child_type->align_in_bits, 0, 0,
|
|
child_type->di_type),
|
|
LLVMZigCreateDebugMemberType(g->dbuilder, LLVMZigTypeToScope(entry->di_type),
|
|
"maybe", di_file, line, 8, 8, 8, 0,
|
|
child_type->di_type),
|
|
};
|
|
LLVMZigDIType *replacement_di_type = LLVMZigCreateDebugStructType(g->dbuilder,
|
|
compile_unit_scope,
|
|
buf_ptr(&entry->name),
|
|
di_file, line, entry->size_in_bits, entry->align_in_bits, 0,
|
|
nullptr, di_element_types, 2, 0, nullptr, "");
|
|
|
|
LLVMZigReplaceTemporary(g->dbuilder, entry->di_type, replacement_di_type);
|
|
entry->di_type = replacement_di_type;
|
|
|
|
entry->data.maybe.child_type = child_type;
|
|
|
|
child_type->maybe_parent = entry;
|
|
return entry;
|
|
}
|
|
}
|
|
|
|
static TypeTableEntry *get_array_type(CodeGen *g, ImportTableEntry *import,
|
|
TypeTableEntry *child_type, uint64_t array_size)
|
|
{
|
|
auto existing_entry = child_type->arrays_by_size.maybe_get(array_size);
|
|
if (existing_entry) {
|
|
TypeTableEntry *entry = existing_entry->value;
|
|
return entry;
|
|
} else {
|
|
TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdArray);
|
|
entry->type_ref = LLVMArrayType(child_type->type_ref, array_size);
|
|
buf_resize(&entry->name, 0);
|
|
buf_appendf(&entry->name, "[%" PRIu64 "]%s", array_size, buf_ptr(&child_type->name));
|
|
|
|
entry->size_in_bits = child_type->size_in_bits * array_size;
|
|
entry->align_in_bits = child_type->align_in_bits;
|
|
|
|
entry->di_type = LLVMZigCreateDebugArrayType(g->dbuilder, entry->size_in_bits,
|
|
entry->align_in_bits, child_type->di_type, array_size);
|
|
entry->data.array.child_type = child_type;
|
|
entry->data.array.len = array_size;
|
|
|
|
child_type->arrays_by_size.put(array_size, entry);
|
|
return entry;
|
|
}
|
|
}
|
|
|
|
static TypeTableEntry *get_unknown_size_array_type(CodeGen *g, ImportTableEntry *import,
|
|
TypeTableEntry *child_type, bool is_const, bool is_noalias)
|
|
{
|
|
TypeTableEntry **parent_pointer = &child_type->unknown_size_array_parent[(is_const ? 1 : 0)][(is_noalias ? 1 : 0)];
|
|
if (*parent_pointer) {
|
|
return *parent_pointer;
|
|
} else {
|
|
TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdStruct);
|
|
|
|
buf_resize(&entry->name, 0);
|
|
buf_appendf(&entry->name, "[]%s", buf_ptr(&child_type->name));
|
|
entry->type_ref = LLVMStructCreateNamed(LLVMGetGlobalContext(), buf_ptr(&entry->name));
|
|
|
|
TypeTableEntry *pointer_type = get_pointer_to_type(g, child_type, is_const, is_noalias);
|
|
|
|
unsigned element_count = 2;
|
|
LLVMTypeRef element_types[] = {
|
|
pointer_type->type_ref,
|
|
g->builtin_types.entry_usize->type_ref,
|
|
};
|
|
LLVMStructSetBody(entry->type_ref, element_types, element_count, false);
|
|
|
|
entry->size_in_bits = g->pointer_size_bytes * 2 * 8;
|
|
entry->align_in_bits = g->pointer_size_bytes * 8;
|
|
entry->data.structure.is_packed = false;
|
|
entry->data.structure.is_unknown_size_array = true;
|
|
entry->data.structure.field_count = element_count;
|
|
entry->data.structure.fields = allocate<TypeStructField>(element_count);
|
|
entry->data.structure.fields[0].name = buf_create_from_str("ptr");
|
|
entry->data.structure.fields[0].type_entry = pointer_type;
|
|
entry->data.structure.fields[0].src_index = 0;
|
|
entry->data.structure.fields[0].gen_index = 0;
|
|
entry->data.structure.fields[1].name = buf_create_from_str("len");
|
|
entry->data.structure.fields[1].type_entry = g->builtin_types.entry_usize;
|
|
entry->data.structure.fields[1].src_index = 1;
|
|
entry->data.structure.fields[1].gen_index = 1;
|
|
|
|
LLVMZigDIType *di_element_types[] = {
|
|
pointer_type->di_type,
|
|
g->builtin_types.entry_usize->di_type,
|
|
};
|
|
LLVMZigDIScope *compile_unit_scope = LLVMZigCompileUnitToScope(g->compile_unit);
|
|
entry->di_type = LLVMZigCreateDebugStructType(g->dbuilder, compile_unit_scope,
|
|
buf_ptr(&entry->name), g->dummy_di_file, 0, entry->size_in_bits, entry->align_in_bits, 0,
|
|
nullptr, di_element_types, element_count, 0, nullptr, "");
|
|
|
|
*parent_pointer = entry;
|
|
return entry;
|
|
}
|
|
}
|
|
|
|
static TypeTableEntry *eval_const_expr_bin_op(CodeGen *g, BlockContext *context,
|
|
AstNode *node, AstNodeNumberLiteral *out_number_literal)
|
|
{
|
|
AstNodeNumberLiteral op1_lit;
|
|
AstNodeNumberLiteral op2_lit;
|
|
TypeTableEntry *op1_type = eval_const_expr(g, context, node->data.bin_op_expr.op1, &op1_lit);
|
|
TypeTableEntry *op2_type = eval_const_expr(g, context, node->data.bin_op_expr.op1, &op2_lit);
|
|
|
|
if (op1_type->id == TypeTableEntryIdInvalid ||
|
|
op2_type->id == TypeTableEntryIdInvalid)
|
|
{
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
|
|
// TODO complete more of this function instead of returning invalid
|
|
// returning invalid makes the "unable to evaluate constant expression" error
|
|
|
|
switch (node->data.bin_op_expr.bin_op) {
|
|
case BinOpTypeCmpNotEq:
|
|
{
|
|
if (is_num_lit_unsigned(op1_lit.kind) &&
|
|
is_num_lit_unsigned(op2_lit.kind))
|
|
{
|
|
out_number_literal->kind = NumLitU8;
|
|
out_number_literal->overflow = false;
|
|
out_number_literal->data.x_uint = (op1_lit.data.x_uint != op2_lit.data.x_uint);
|
|
return get_resolved_expr(node)->type_entry;
|
|
} else {
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
}
|
|
case BinOpTypeCmpLessThan:
|
|
{
|
|
if (is_num_lit_unsigned(op1_lit.kind) &&
|
|
is_num_lit_unsigned(op2_lit.kind))
|
|
{
|
|
out_number_literal->kind = NumLitU8;
|
|
out_number_literal->overflow = false;
|
|
out_number_literal->data.x_uint = (op1_lit.data.x_uint < op2_lit.data.x_uint);
|
|
return get_resolved_expr(node)->type_entry;
|
|
} else {
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
}
|
|
case BinOpTypeMod:
|
|
{
|
|
if (is_num_lit_unsigned(op1_lit.kind) &&
|
|
is_num_lit_unsigned(op2_lit.kind))
|
|
{
|
|
out_number_literal->kind = NumLitU64;
|
|
out_number_literal->overflow = false;
|
|
out_number_literal->data.x_uint = (op1_lit.data.x_uint % op2_lit.data.x_uint);
|
|
return get_resolved_expr(node)->type_entry;
|
|
} else {
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
}
|
|
case BinOpTypeBoolOr:
|
|
case BinOpTypeBoolAnd:
|
|
case BinOpTypeCmpEq:
|
|
case BinOpTypeCmpGreaterThan:
|
|
case BinOpTypeCmpLessOrEq:
|
|
case BinOpTypeCmpGreaterOrEq:
|
|
case BinOpTypeBinOr:
|
|
case BinOpTypeBinXor:
|
|
case BinOpTypeBinAnd:
|
|
case BinOpTypeBitShiftLeft:
|
|
case BinOpTypeBitShiftRight:
|
|
case BinOpTypeAdd:
|
|
case BinOpTypeSub:
|
|
case BinOpTypeMult:
|
|
case BinOpTypeDiv:
|
|
case BinOpTypeUnwrapMaybe:
|
|
return g->builtin_types.entry_invalid;
|
|
case BinOpTypeInvalid:
|
|
case BinOpTypeAssign:
|
|
case BinOpTypeAssignTimes:
|
|
case BinOpTypeAssignDiv:
|
|
case BinOpTypeAssignMod:
|
|
case BinOpTypeAssignPlus:
|
|
case BinOpTypeAssignMinus:
|
|
case BinOpTypeAssignBitShiftLeft:
|
|
case BinOpTypeAssignBitShiftRight:
|
|
case BinOpTypeAssignBitAnd:
|
|
case BinOpTypeAssignBitXor:
|
|
case BinOpTypeAssignBitOr:
|
|
case BinOpTypeAssignBoolAnd:
|
|
case BinOpTypeAssignBoolOr:
|
|
zig_unreachable();
|
|
}
|
|
zig_unreachable();
|
|
}
|
|
|
|
static TypeTableEntry *eval_const_expr(CodeGen *g, BlockContext *context,
|
|
AstNode *node, AstNodeNumberLiteral *out_number_literal)
|
|
{
|
|
switch (node->type) {
|
|
case NodeTypeNumberLiteral:
|
|
*out_number_literal = node->data.number_literal;
|
|
return get_resolved_expr(node)->type_entry;
|
|
case NodeTypeBoolLiteral:
|
|
out_number_literal->data.x_uint = node->data.bool_literal.value ? 1 : 0;
|
|
return get_resolved_expr(node)->type_entry;
|
|
case NodeTypeNullLiteral:
|
|
return get_resolved_expr(node)->type_entry;
|
|
case NodeTypeBinOpExpr:
|
|
return eval_const_expr_bin_op(g, context, node, out_number_literal);
|
|
case NodeTypeCompilerFnType:
|
|
{
|
|
Buf *name = &node->data.compiler_fn_type.name;
|
|
TypeTableEntry *expr_type = get_resolved_expr(node)->type_entry;
|
|
if (buf_eql_str(name, "sizeof")) {
|
|
TypeTableEntry *target_type = node->data.compiler_fn_type.type->data.type.entry;
|
|
out_number_literal->overflow = false;
|
|
out_number_literal->data.x_uint = target_type->size_in_bits / 8;
|
|
out_number_literal->kind = get_number_literal_kind_unsigned(out_number_literal->data.x_uint);
|
|
|
|
return expr_type;
|
|
} else if (buf_eql_str(name, "max_value")) {
|
|
zig_panic("TODO eval_const_expr max_value");
|
|
} else if (buf_eql_str(name, "min_value")) {
|
|
zig_panic("TODO eval_const_expr min_value");
|
|
} else if (buf_eql_str(name, "value_count")) {
|
|
zig_panic("TODO eval_const_expr value_count");
|
|
} else {
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
break;
|
|
}
|
|
case NodeTypeSymbol:
|
|
{
|
|
VariableTableEntry *var = find_variable(context, &node->data.symbol_expr.symbol);
|
|
assert(var);
|
|
AstNode *decl_node = var->decl_node;
|
|
AstNode *expr_node = decl_node->data.variable_declaration.expr;
|
|
BlockContext *next_context = get_resolved_expr(expr_node)->block_context;
|
|
return eval_const_expr(g, next_context, expr_node, out_number_literal);
|
|
}
|
|
default:
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
}
|
|
|
|
static TypeTableEntry *resolve_type(CodeGen *g, AstNode *node, ImportTableEntry *import,
|
|
BlockContext *context, bool noalias_allowed)
|
|
{
|
|
assert(node->type == NodeTypeType);
|
|
switch (node->data.type.type) {
|
|
case AstNodeTypeTypePrimitive:
|
|
{
|
|
Buf *name = &node->data.type.primitive_name;
|
|
auto table_entry = import->block_context->type_table.maybe_get(name);
|
|
if (!table_entry) {
|
|
table_entry = g->primitive_type_table.maybe_get(name);
|
|
}
|
|
if (table_entry) {
|
|
node->data.type.entry = table_entry->value;
|
|
} else {
|
|
add_node_error(g, node,
|
|
buf_sprintf("invalid type name: '%s'", buf_ptr(name)));
|
|
node->data.type.entry = g->builtin_types.entry_invalid;
|
|
}
|
|
return node->data.type.entry;
|
|
}
|
|
case AstNodeTypeTypePointer:
|
|
{
|
|
bool use_noalias = false;
|
|
if (node->data.type.is_noalias) {
|
|
if (!noalias_allowed) {
|
|
add_node_error(g, node,
|
|
buf_create_from_str("invalid noalias qualifier"));
|
|
} else {
|
|
use_noalias = true;
|
|
}
|
|
}
|
|
|
|
resolve_type(g, node->data.type.child_type, import, context, false);
|
|
TypeTableEntry *child_type = node->data.type.child_type->data.type.entry;
|
|
assert(child_type);
|
|
if (child_type->id == TypeTableEntryIdUnreachable) {
|
|
add_node_error(g, node,
|
|
buf_create_from_str("pointer to unreachable not allowed"));
|
|
node->data.type.entry = g->builtin_types.entry_invalid;
|
|
return node->data.type.entry;
|
|
} else if (child_type->id == TypeTableEntryIdInvalid) {
|
|
node->data.type.entry = child_type;
|
|
return child_type;
|
|
} else {
|
|
node->data.type.entry = get_pointer_to_type(g, child_type, node->data.type.is_const, use_noalias);
|
|
return node->data.type.entry;
|
|
}
|
|
}
|
|
case AstNodeTypeTypeArray:
|
|
{
|
|
AstNode *size_node = node->data.type.array_size;
|
|
|
|
bool use_noalias = false;
|
|
if (node->data.type.is_noalias) {
|
|
if (!noalias_allowed || size_node) {
|
|
add_node_error(g, node,
|
|
buf_create_from_str("invalid noalias qualifier"));
|
|
} else {
|
|
use_noalias = true;
|
|
}
|
|
}
|
|
|
|
TypeTableEntry *child_type = resolve_type(g, node->data.type.child_type, import, context, false);
|
|
if (child_type->id == TypeTableEntryIdUnreachable) {
|
|
add_node_error(g, node,
|
|
buf_create_from_str("array of unreachable not allowed"));
|
|
node->data.type.entry = g->builtin_types.entry_invalid;
|
|
return node->data.type.entry;
|
|
}
|
|
|
|
if (size_node) {
|
|
TypeTableEntry *size_type = analyze_expression(g, import, context,
|
|
g->builtin_types.entry_usize, size_node);
|
|
if (size_type->id == TypeTableEntryIdInvalid) {
|
|
node->data.type.entry = g->builtin_types.entry_invalid;
|
|
return node->data.type.entry;
|
|
}
|
|
|
|
AstNodeNumberLiteral number_literal;
|
|
TypeTableEntry *resolved_type = eval_const_expr(g, context, size_node, &number_literal);
|
|
|
|
if (resolved_type->id == TypeTableEntryIdInt) {
|
|
if (resolved_type->data.integral.is_signed) {
|
|
add_node_error(g, size_node,
|
|
buf_create_from_str("array size must be unsigned integer"));
|
|
node->data.type.entry = g->builtin_types.entry_invalid;
|
|
} else {
|
|
node->data.type.entry = get_array_type(g, import, child_type, number_literal.data.x_uint);
|
|
}
|
|
} else {
|
|
add_node_error(g, size_node,
|
|
buf_create_from_str("unable to resolve constant expression"));
|
|
node->data.type.entry = g->builtin_types.entry_invalid;
|
|
}
|
|
return node->data.type.entry;
|
|
} else {
|
|
node->data.type.entry = get_unknown_size_array_type(g, import, child_type,
|
|
node->data.type.is_const, use_noalias);
|
|
return node->data.type.entry;
|
|
}
|
|
|
|
}
|
|
case AstNodeTypeTypeMaybe:
|
|
{
|
|
resolve_type(g, node->data.type.child_type, import, context, false);
|
|
TypeTableEntry *child_type = node->data.type.child_type->data.type.entry;
|
|
assert(child_type);
|
|
if (child_type->id == TypeTableEntryIdUnreachable) {
|
|
add_node_error(g, node,
|
|
buf_create_from_str("maybe unreachable type not allowed"));
|
|
} else if (child_type->id == TypeTableEntryIdInvalid) {
|
|
return child_type;
|
|
}
|
|
node->data.type.entry = get_maybe_type(g, import, child_type);
|
|
return node->data.type.entry;
|
|
}
|
|
case AstNodeTypeTypeCompilerExpr:
|
|
{
|
|
AstNode *compiler_expr_node = node->data.type.compiler_expr;
|
|
Buf *fn_name = &compiler_expr_node->data.compiler_fn_expr.name;
|
|
if (buf_eql_str(fn_name, "typeof")) {
|
|
node->data.type.entry = analyze_expression(g, import, context, nullptr,
|
|
compiler_expr_node->data.compiler_fn_expr.expr);
|
|
} else {
|
|
add_node_error(g, node,
|
|
buf_sprintf("invalid compiler function: '%s'", buf_ptr(fn_name)));
|
|
node->data.type.entry = g->builtin_types.entry_invalid;
|
|
}
|
|
return node->data.type.entry;
|
|
}
|
|
}
|
|
zig_unreachable();
|
|
}
|
|
|
|
static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_table_entry,
|
|
ImportTableEntry *import)
|
|
{
|
|
assert(node->type == NodeTypeFnProto);
|
|
|
|
for (int i = 0; i < node->data.fn_proto.directives->length; i += 1) {
|
|
AstNode *directive_node = node->data.fn_proto.directives->at(i);
|
|
Buf *name = &directive_node->data.directive.name;
|
|
|
|
if (buf_eql_str(name, "attribute")) {
|
|
Buf *attr_name = &directive_node->data.directive.param;
|
|
if (fn_table_entry->fn_def_node) {
|
|
if (buf_eql_str(attr_name, "naked")) {
|
|
fn_table_entry->fn_attr_list.append(FnAttrIdNaked);
|
|
} else if (buf_eql_str(attr_name, "alwaysinline")) {
|
|
fn_table_entry->fn_attr_list.append(FnAttrIdAlwaysInline);
|
|
} else {
|
|
add_node_error(g, directive_node,
|
|
buf_sprintf("invalid function attribute: '%s'", buf_ptr(name)));
|
|
}
|
|
} else {
|
|
add_node_error(g, directive_node,
|
|
buf_sprintf("invalid function attribute: '%s'", buf_ptr(name)));
|
|
}
|
|
} else {
|
|
add_node_error(g, directive_node,
|
|
buf_sprintf("invalid directive: '%s'", buf_ptr(name)));
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < node->data.fn_proto.params.length; i += 1) {
|
|
AstNode *child = node->data.fn_proto.params.at(i);
|
|
assert(child->type == NodeTypeParamDecl);
|
|
TypeTableEntry *type_entry = resolve_type(g, child->data.param_decl.type,
|
|
import, import->block_context, true);
|
|
if (type_entry->id == TypeTableEntryIdUnreachable) {
|
|
add_node_error(g, child->data.param_decl.type,
|
|
buf_sprintf("parameter of type 'unreachable' not allowed"));
|
|
} else if (type_entry->id == TypeTableEntryIdVoid) {
|
|
if (node->data.fn_proto.visib_mod == VisibModExport) {
|
|
add_node_error(g, child->data.param_decl.type,
|
|
buf_sprintf("parameter of type 'void' not allowed on exported functions"));
|
|
}
|
|
}
|
|
}
|
|
|
|
resolve_type(g, node->data.fn_proto.return_type, import, import->block_context, true);
|
|
}
|
|
|
|
static void preview_function_labels(CodeGen *g, AstNode *node, FnTableEntry *fn_table_entry) {
|
|
assert(node->type == NodeTypeBlock);
|
|
|
|
for (int i = 0; i < node->data.block.statements.length; i += 1) {
|
|
AstNode *label_node = node->data.block.statements.at(i);
|
|
if (label_node->type != NodeTypeLabel)
|
|
continue;
|
|
|
|
LabelTableEntry *label_entry = allocate<LabelTableEntry>(1);
|
|
label_entry->label_node = label_node;
|
|
Buf *name = &label_node->data.label.name;
|
|
fn_table_entry->label_table.put(name, label_entry);
|
|
|
|
label_node->data.label.label_entry = label_entry;
|
|
}
|
|
}
|
|
|
|
static void resolve_enum_type(CodeGen *g, ImportTableEntry *import, TypeTableEntry *enum_type) {
|
|
assert(enum_type->id == TypeTableEntryIdEnum);
|
|
|
|
AstNode *decl_node = enum_type->data.enumeration.decl_node;
|
|
|
|
if (enum_type->data.enumeration.embedded_in_current) {
|
|
if (!enum_type->data.enumeration.reported_infinite_err) {
|
|
enum_type->data.enumeration.reported_infinite_err = true;
|
|
add_node_error(g, decl_node, buf_sprintf("enum has infinite size"));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (enum_type->data.enumeration.fields) {
|
|
// we already resolved this type. skip
|
|
return;
|
|
}
|
|
|
|
assert(enum_type->di_type);
|
|
|
|
uint32_t field_count = decl_node->data.struct_decl.fields.length;
|
|
|
|
enum_type->data.enumeration.field_count = field_count;
|
|
enum_type->data.enumeration.fields = allocate<TypeEnumField>(field_count);
|
|
LLVMZigDIEnumerator **di_enumerators = allocate<LLVMZigDIEnumerator*>(field_count);
|
|
|
|
// we possibly allocate too much here since gen_field_count can be lower than field_count.
|
|
// the only problem is potential wasted space though.
|
|
LLVMZigDIType **union_inner_di_types = allocate<LLVMZigDIType*>(field_count);
|
|
|
|
TypeTableEntry *biggest_union_member = nullptr;
|
|
uint64_t biggest_align_in_bits = 0;
|
|
uint64_t biggest_union_member_size_in_bits = 0;
|
|
|
|
// set temporary flag
|
|
enum_type->data.enumeration.embedded_in_current = true;
|
|
|
|
int gen_field_index = 0;
|
|
for (uint32_t i = 0; i < field_count; i += 1) {
|
|
AstNode *field_node = decl_node->data.struct_decl.fields.at(i);
|
|
TypeEnumField *type_enum_field = &enum_type->data.enumeration.fields[i];
|
|
type_enum_field->name = &field_node->data.struct_field.name;
|
|
type_enum_field->type_entry = resolve_type(g, field_node->data.struct_field.type,
|
|
import, import->block_context, false);
|
|
type_enum_field->value = i;
|
|
|
|
di_enumerators[i] = LLVMZigCreateDebugEnumerator(g->dbuilder, buf_ptr(type_enum_field->name), i);
|
|
|
|
if (type_enum_field->type_entry->id == TypeTableEntryIdStruct) {
|
|
resolve_struct_type(g, import, type_enum_field->type_entry);
|
|
} else if (type_enum_field->type_entry->id == TypeTableEntryIdEnum) {
|
|
resolve_enum_type(g, import, type_enum_field->type_entry);
|
|
} else if (type_enum_field->type_entry->id == TypeTableEntryIdInvalid) {
|
|
enum_type->data.enumeration.is_invalid = true;
|
|
continue;
|
|
} else if (type_enum_field->type_entry->id == TypeTableEntryIdVoid) {
|
|
continue;
|
|
}
|
|
|
|
union_inner_di_types[gen_field_index] = LLVMZigCreateDebugMemberType(g->dbuilder,
|
|
LLVMZigTypeToScope(enum_type->di_type), buf_ptr(type_enum_field->name),
|
|
import->di_file, field_node->line + 1,
|
|
type_enum_field->type_entry->size_in_bits,
|
|
type_enum_field->type_entry->align_in_bits,
|
|
0, 0, type_enum_field->type_entry->di_type);
|
|
|
|
biggest_align_in_bits = max(biggest_align_in_bits, type_enum_field->type_entry->align_in_bits);
|
|
|
|
if (!biggest_union_member ||
|
|
type_enum_field->type_entry->size_in_bits > biggest_union_member->size_in_bits)
|
|
{
|
|
biggest_union_member = type_enum_field->type_entry;
|
|
biggest_union_member_size_in_bits = biggest_union_member->size_in_bits;
|
|
}
|
|
|
|
gen_field_index += 1;
|
|
}
|
|
|
|
// unset temporary flag
|
|
enum_type->data.enumeration.embedded_in_current = false;
|
|
|
|
if (!enum_type->data.enumeration.is_invalid) {
|
|
enum_type->data.enumeration.gen_field_count = gen_field_index;
|
|
|
|
uint64_t tag_size_in_bits = num_lit_bit_count(get_number_literal_kind_unsigned(field_count));
|
|
enum_type->align_in_bits = tag_size_in_bits;
|
|
enum_type->size_in_bits = tag_size_in_bits + biggest_union_member_size_in_bits;
|
|
TypeTableEntry *tag_type_entry = get_int_type_unsigned(g, field_count);
|
|
enum_type->data.enumeration.tag_type = tag_type_entry;
|
|
|
|
if (biggest_union_member) {
|
|
// create llvm type for union
|
|
LLVMTypeRef union_element_type = biggest_union_member->type_ref;
|
|
LLVMTypeRef union_type_ref = LLVMStructType(&union_element_type, 1, false);
|
|
|
|
// create llvm type for root struct
|
|
LLVMTypeRef root_struct_element_types[] = {
|
|
tag_type_entry->type_ref,
|
|
union_type_ref,
|
|
};
|
|
LLVMStructSetBody(enum_type->type_ref, root_struct_element_types, 2, false);
|
|
|
|
// create debug type for tag
|
|
LLVMZigDIType *tag_di_type = LLVMZigCreateDebugEnumerationType(g->dbuilder,
|
|
LLVMZigTypeToScope(enum_type->di_type), "AnonEnum", import->di_file, decl_node->line + 1,
|
|
tag_type_entry->size_in_bits, tag_type_entry->align_in_bits, di_enumerators, field_count,
|
|
tag_type_entry->di_type, "");
|
|
|
|
// create debug type for union
|
|
LLVMZigDIType *union_di_type = LLVMZigCreateDebugUnionType(g->dbuilder,
|
|
LLVMZigTypeToScope(enum_type->di_type), "AnonUnion", import->di_file, decl_node->line + 1,
|
|
biggest_union_member->size_in_bits, biggest_align_in_bits, 0, union_inner_di_types,
|
|
gen_field_index, 0, "");
|
|
|
|
// create debug types for members of root struct
|
|
LLVMZigDIType *tag_member_di_type = LLVMZigCreateDebugMemberType(g->dbuilder,
|
|
LLVMZigTypeToScope(enum_type->di_type), "tag_field",
|
|
import->di_file, decl_node->line + 1,
|
|
tag_type_entry->size_in_bits,
|
|
tag_type_entry->align_in_bits,
|
|
0, 0, tag_di_type);
|
|
LLVMZigDIType *union_member_di_type = LLVMZigCreateDebugMemberType(g->dbuilder,
|
|
LLVMZigTypeToScope(enum_type->di_type), "union_field",
|
|
import->di_file, decl_node->line + 1,
|
|
biggest_union_member->size_in_bits,
|
|
biggest_align_in_bits,
|
|
tag_type_entry->size_in_bits, 0, union_di_type);
|
|
|
|
// create debug type for root struct
|
|
LLVMZigDIType *di_root_members[] = {
|
|
tag_member_di_type,
|
|
union_member_di_type,
|
|
};
|
|
|
|
|
|
LLVMZigDIType *replacement_di_type = LLVMZigCreateDebugStructType(g->dbuilder,
|
|
LLVMZigFileToScope(import->di_file),
|
|
buf_ptr(&decl_node->data.struct_decl.name),
|
|
import->di_file, decl_node->line + 1, enum_type->size_in_bits, enum_type->align_in_bits, 0,
|
|
nullptr, di_root_members, 2, 0, nullptr, "");
|
|
|
|
LLVMZigReplaceTemporary(g->dbuilder, enum_type->di_type, replacement_di_type);
|
|
enum_type->di_type = replacement_di_type;
|
|
} else {
|
|
// create llvm type for root struct
|
|
enum_type->type_ref = tag_type_entry->type_ref;
|
|
|
|
// create debug type for tag
|
|
LLVMZigDIType *tag_di_type = LLVMZigCreateDebugEnumerationType(g->dbuilder,
|
|
LLVMZigFileToScope(import->di_file), buf_ptr(&decl_node->data.struct_decl.name),
|
|
import->di_file, decl_node->line + 1,
|
|
tag_type_entry->size_in_bits, tag_type_entry->align_in_bits, di_enumerators, field_count,
|
|
tag_type_entry->di_type, "");
|
|
|
|
LLVMZigReplaceTemporary(g->dbuilder, enum_type->di_type, tag_di_type);
|
|
enum_type->di_type = tag_di_type;
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
static void resolve_struct_type(CodeGen *g, ImportTableEntry *import, TypeTableEntry *struct_type) {
|
|
assert(struct_type->id == TypeTableEntryIdStruct);
|
|
|
|
AstNode *decl_node = struct_type->data.structure.decl_node;
|
|
|
|
if (struct_type->data.structure.embedded_in_current) {
|
|
if (!struct_type->data.structure.reported_infinite_err) {
|
|
struct_type->data.structure.reported_infinite_err = true;
|
|
add_node_error(g, decl_node,
|
|
buf_sprintf("struct has infinite size"));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (struct_type->data.structure.fields) {
|
|
// we already resolved this type. skip
|
|
return;
|
|
}
|
|
|
|
|
|
assert(struct_type->di_type);
|
|
|
|
int field_count = decl_node->data.struct_decl.fields.length;
|
|
|
|
struct_type->data.structure.field_count = field_count;
|
|
struct_type->data.structure.fields = allocate<TypeStructField>(field_count);
|
|
|
|
// we possibly allocate too much here since gen_field_count can be lower than field_count.
|
|
// the only problem is potential wasted space though.
|
|
LLVMTypeRef *element_types = allocate<LLVMTypeRef>(field_count);
|
|
LLVMZigDIType **di_element_types = allocate<LLVMZigDIType*>(field_count);
|
|
|
|
uint64_t total_size_in_bits = 0;
|
|
uint64_t first_field_align_in_bits = 0;
|
|
uint64_t offset_in_bits = 0;
|
|
|
|
// this field should be set to true only during the recursive calls to resolve_struct_type
|
|
struct_type->data.structure.embedded_in_current = true;
|
|
|
|
int gen_field_index = 0;
|
|
for (int i = 0; i < field_count; i += 1) {
|
|
AstNode *field_node = decl_node->data.struct_decl.fields.at(i);
|
|
TypeStructField *type_struct_field = &struct_type->data.structure.fields[i];
|
|
type_struct_field->name = &field_node->data.struct_field.name;
|
|
type_struct_field->type_entry = resolve_type(g, field_node->data.struct_field.type,
|
|
import, import->block_context, false);
|
|
type_struct_field->src_index = i;
|
|
type_struct_field->gen_index = -1;
|
|
|
|
if (type_struct_field->type_entry->id == TypeTableEntryIdStruct) {
|
|
resolve_struct_type(g, import, type_struct_field->type_entry);
|
|
} else if (type_struct_field->type_entry->id == TypeTableEntryIdEnum) {
|
|
resolve_enum_type(g, import, type_struct_field->type_entry);
|
|
} else if (type_struct_field->type_entry->id == TypeTableEntryIdInvalid) {
|
|
struct_type->data.structure.is_invalid = true;
|
|
continue;
|
|
} else if (type_struct_field->type_entry->id == TypeTableEntryIdVoid) {
|
|
continue;
|
|
}
|
|
|
|
type_struct_field->gen_index = gen_field_index;
|
|
|
|
di_element_types[gen_field_index] = LLVMZigCreateDebugMemberType(g->dbuilder,
|
|
LLVMZigTypeToScope(struct_type->di_type), buf_ptr(type_struct_field->name),
|
|
import->di_file, field_node->line + 1,
|
|
type_struct_field->type_entry->size_in_bits,
|
|
type_struct_field->type_entry->align_in_bits,
|
|
offset_in_bits, 0, type_struct_field->type_entry->di_type);
|
|
|
|
element_types[gen_field_index] = type_struct_field->type_entry->type_ref;
|
|
assert(di_element_types[gen_field_index]);
|
|
assert(element_types[gen_field_index]);
|
|
|
|
total_size_in_bits += type_struct_field->type_entry->size_in_bits;
|
|
if (first_field_align_in_bits == 0) {
|
|
first_field_align_in_bits = type_struct_field->type_entry->align_in_bits;
|
|
}
|
|
offset_in_bits += type_struct_field->type_entry->size_in_bits;
|
|
|
|
gen_field_index += 1;
|
|
}
|
|
struct_type->data.structure.embedded_in_current = false;
|
|
|
|
if (!struct_type->data.structure.is_invalid) {
|
|
|
|
LLVMStructSetBody(struct_type->type_ref, element_types, gen_field_index, false);
|
|
|
|
struct_type->align_in_bits = first_field_align_in_bits;
|
|
struct_type->size_in_bits = total_size_in_bits;
|
|
|
|
LLVMZigDIType *replacement_di_type = LLVMZigCreateDebugStructType(g->dbuilder,
|
|
LLVMZigFileToScope(import->di_file),
|
|
buf_ptr(&decl_node->data.struct_decl.name),
|
|
import->di_file, decl_node->line + 1, struct_type->size_in_bits, struct_type->align_in_bits, 0,
|
|
nullptr, di_element_types, gen_field_index, 0, nullptr, "");
|
|
|
|
LLVMZigReplaceTemporary(g->dbuilder, struct_type->di_type, replacement_di_type);
|
|
struct_type->di_type = replacement_di_type;
|
|
}
|
|
}
|
|
|
|
static void preview_fn_proto(CodeGen *g, ImportTableEntry *import,
|
|
AstNode *proto_node)
|
|
{
|
|
AstNode *fn_def_node = proto_node->data.fn_proto.fn_def_node;
|
|
AstNode *extern_node = proto_node->data.fn_proto.extern_node;
|
|
AstNode *struct_node = proto_node->data.fn_proto.struct_node;
|
|
TypeTableEntry *struct_type;
|
|
if (struct_node) {
|
|
assert(struct_node->type == NodeTypeStructDecl);
|
|
struct_type = struct_node->data.struct_decl.type_entry;
|
|
} else {
|
|
struct_type = nullptr;
|
|
}
|
|
|
|
Buf *proto_name = &proto_node->data.fn_proto.name;
|
|
|
|
auto fn_table = struct_type ? &struct_type->data.structure.fn_table : &import->fn_table;
|
|
|
|
auto entry = fn_table->maybe_get(proto_name);
|
|
bool skip = false;
|
|
bool is_internal = (proto_node->data.fn_proto.visib_mod != VisibModExport);
|
|
bool is_c_compat = !is_internal || extern_node;
|
|
bool is_pub = (proto_node->data.fn_proto.visib_mod != VisibModPrivate);
|
|
if (entry) {
|
|
add_node_error(g, proto_node,
|
|
buf_sprintf("redefinition of '%s'", buf_ptr(proto_name)));
|
|
proto_node->data.fn_proto.skip = true;
|
|
skip = true;
|
|
} else if (is_pub) {
|
|
// TODO is this else if branch a mistake?
|
|
auto entry = fn_table->maybe_get(proto_name);
|
|
if (entry) {
|
|
add_node_error(g, proto_node, buf_sprintf("redefinition of '%s'", buf_ptr(proto_name)));
|
|
proto_node->data.fn_proto.skip = true;
|
|
skip = true;
|
|
}
|
|
}
|
|
if (!extern_node && proto_node->data.fn_proto.is_var_args) {
|
|
add_node_error(g, proto_node,
|
|
buf_sprintf("variadic arguments only allowed in extern functions"));
|
|
}
|
|
if (skip) {
|
|
return;
|
|
}
|
|
|
|
FnTableEntry *fn_table_entry = allocate<FnTableEntry>(1);
|
|
fn_table_entry->import_entry = import;
|
|
fn_table_entry->proto_node = proto_node;
|
|
fn_table_entry->fn_def_node = fn_def_node;
|
|
fn_table_entry->internal_linkage = !is_c_compat;
|
|
fn_table_entry->is_extern = extern_node;
|
|
fn_table_entry->calling_convention = is_c_compat ? LLVMCCallConv : LLVMFastCallConv;
|
|
fn_table_entry->label_table.init(8);
|
|
fn_table_entry->member_of_struct = struct_type;
|
|
|
|
if (struct_type) {
|
|
buf_resize(&fn_table_entry->symbol_name, 0);
|
|
buf_appendf(&fn_table_entry->symbol_name, "%s_%s",
|
|
buf_ptr(&struct_type->name),
|
|
buf_ptr(proto_name));
|
|
} else {
|
|
buf_init_from_buf(&fn_table_entry->symbol_name, proto_name);
|
|
}
|
|
|
|
g->fn_protos.append(fn_table_entry);
|
|
|
|
if (!extern_node) {
|
|
g->fn_defs.append(fn_table_entry);
|
|
}
|
|
|
|
fn_table->put(proto_name, fn_table_entry);
|
|
|
|
if (!struct_type &&
|
|
g->bootstrap_import &&
|
|
import == g->root_import && buf_eql_str(proto_name, "main"))
|
|
{
|
|
g->bootstrap_import->fn_table.put(proto_name, fn_table_entry);
|
|
}
|
|
|
|
resolve_function_proto(g, proto_node, fn_table_entry, import);
|
|
|
|
|
|
proto_node->data.fn_proto.fn_table_entry = fn_table_entry;
|
|
|
|
if (fn_def_node) {
|
|
preview_function_labels(g, fn_def_node->data.fn_def.body, fn_table_entry);
|
|
}
|
|
|
|
if (is_pub && !struct_type) {
|
|
for (int i = 0; i < import->importers.length; i += 1) {
|
|
ImporterInfo importer = import->importers.at(i);
|
|
auto table_entry = importer.import->fn_table.maybe_get(proto_name);
|
|
if (table_entry) {
|
|
add_node_error(g, importer.source_node,
|
|
buf_sprintf("import of function '%s' overrides existing definition",
|
|
buf_ptr(proto_name)));
|
|
} else {
|
|
importer.import->fn_table.put(proto_name, fn_table_entry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void resolve_top_level_decl(CodeGen *g, ImportTableEntry *import, AstNode *node) {
|
|
switch (node->type) {
|
|
case NodeTypeExternBlock:
|
|
for (int i = 0; i < node->data.extern_block.directives->length; i += 1) {
|
|
AstNode *directive_node = node->data.extern_block.directives->at(i);
|
|
Buf *name = &directive_node->data.directive.name;
|
|
Buf *param = &directive_node->data.directive.param;
|
|
if (buf_eql_str(name, "link")) {
|
|
g->link_table.put(param, true);
|
|
} else {
|
|
add_node_error(g, directive_node,
|
|
buf_sprintf("invalid directive: '%s'", buf_ptr(name)));
|
|
}
|
|
}
|
|
break;
|
|
case NodeTypeFnProto:
|
|
preview_fn_proto(g, import, node);
|
|
break;
|
|
case NodeTypeRootExportDecl:
|
|
if (import == g->root_import) {
|
|
for (int i = 0; i < node->data.root_export_decl.directives->length; i += 1) {
|
|
AstNode *directive_node = node->data.root_export_decl.directives->at(i);
|
|
Buf *name = &directive_node->data.directive.name;
|
|
Buf *param = &directive_node->data.directive.param;
|
|
if (buf_eql_str(name, "version")) {
|
|
set_root_export_version(g, param, directive_node);
|
|
} else {
|
|
add_node_error(g, directive_node,
|
|
buf_sprintf("invalid directive: '%s'", buf_ptr(name)));
|
|
}
|
|
}
|
|
|
|
if (g->root_export_decl) {
|
|
add_node_error(g, node,
|
|
buf_sprintf("only one root export declaration allowed"));
|
|
} else {
|
|
g->root_export_decl = node;
|
|
|
|
if (!g->root_out_name)
|
|
g->root_out_name = &node->data.root_export_decl.name;
|
|
|
|
Buf *out_type = &node->data.root_export_decl.type;
|
|
OutType export_out_type;
|
|
if (buf_eql_str(out_type, "executable")) {
|
|
export_out_type = OutTypeExe;
|
|
} else if (buf_eql_str(out_type, "library")) {
|
|
export_out_type = OutTypeLib;
|
|
} else if (buf_eql_str(out_type, "object")) {
|
|
export_out_type = OutTypeObj;
|
|
} else {
|
|
add_node_error(g, node,
|
|
buf_sprintf("invalid export type: '%s'", buf_ptr(out_type)));
|
|
}
|
|
if (g->out_type == OutTypeUnknown)
|
|
g->out_type = export_out_type;
|
|
}
|
|
} else {
|
|
add_node_error(g, node,
|
|
buf_sprintf("root export declaration only valid in root source file"));
|
|
}
|
|
break;
|
|
case NodeTypeStructDecl:
|
|
{
|
|
TypeTableEntry *type_entry = node->data.struct_decl.type_entry;
|
|
|
|
// struct/enum member fns will get resolved independently
|
|
|
|
switch (node->data.struct_decl.kind) {
|
|
case ContainerKindStruct:
|
|
resolve_struct_type(g, import, type_entry);
|
|
break;
|
|
case ContainerKindEnum:
|
|
resolve_enum_type(g, import, type_entry);
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case NodeTypeVariableDeclaration:
|
|
{
|
|
VariableTableEntry *var = analyze_variable_declaration(g, import, import->block_context,
|
|
nullptr, node);
|
|
g->global_vars.append(var);
|
|
break;
|
|
}
|
|
case NodeTypeUse:
|
|
// nothing to do here
|
|
break;
|
|
case NodeTypeFnDef:
|
|
case NodeTypeDirective:
|
|
case NodeTypeParamDecl:
|
|
case NodeTypeType:
|
|
case NodeTypeFnDecl:
|
|
case NodeTypeReturnExpr:
|
|
case NodeTypeRoot:
|
|
case NodeTypeBlock:
|
|
case NodeTypeBinOpExpr:
|
|
case NodeTypeFnCallExpr:
|
|
case NodeTypeArrayAccessExpr:
|
|
case NodeTypeSliceExpr:
|
|
case NodeTypeNumberLiteral:
|
|
case NodeTypeStringLiteral:
|
|
case NodeTypeCharLiteral:
|
|
case NodeTypeUnreachable:
|
|
case NodeTypeVoid:
|
|
case NodeTypeBoolLiteral:
|
|
case NodeTypeNullLiteral:
|
|
case NodeTypeSymbol:
|
|
case NodeTypeCastExpr:
|
|
case NodeTypePrefixOpExpr:
|
|
case NodeTypeIfBoolExpr:
|
|
case NodeTypeIfVarExpr:
|
|
case NodeTypeWhileExpr:
|
|
case NodeTypeLabel:
|
|
case NodeTypeGoto:
|
|
case NodeTypeBreak:
|
|
case NodeTypeContinue:
|
|
case NodeTypeAsmExpr:
|
|
case NodeTypeFieldAccessExpr:
|
|
case NodeTypeStructField:
|
|
case NodeTypeStructValueExpr:
|
|
case NodeTypeStructValueField:
|
|
case NodeTypeCompilerFnExpr:
|
|
case NodeTypeCompilerFnType:
|
|
zig_unreachable();
|
|
}
|
|
}
|
|
|
|
static FnTableEntry *get_context_fn_entry(BlockContext *context) {
|
|
assert(context->fn_entry);
|
|
return context->fn_entry;
|
|
}
|
|
|
|
static TypeTableEntry *get_return_type(BlockContext *context) {
|
|
FnTableEntry *fn_entry = get_context_fn_entry(context);
|
|
AstNode *fn_proto_node = fn_entry->proto_node;
|
|
assert(fn_proto_node->type == NodeTypeFnProto);
|
|
AstNode *return_type_node = fn_proto_node->data.fn_proto.return_type;
|
|
assert(return_type_node->type == NodeTypeType);
|
|
return return_type_node->data.type.entry;
|
|
}
|
|
|
|
static bool num_lit_fits_in_other_type(CodeGen *g, TypeTableEntry *literal_type, TypeTableEntry *other_type) {
|
|
NumLit num_lit = literal_type->data.num_lit.kind;
|
|
uint64_t lit_size_in_bits = num_lit_bit_count(num_lit);
|
|
|
|
switch (other_type->id) {
|
|
case TypeTableEntryIdInvalid:
|
|
case TypeTableEntryIdNumberLiteral:
|
|
zig_unreachable();
|
|
case TypeTableEntryIdVoid:
|
|
case TypeTableEntryIdBool:
|
|
case TypeTableEntryIdUnreachable:
|
|
case TypeTableEntryIdPointer:
|
|
case TypeTableEntryIdArray:
|
|
case TypeTableEntryIdStruct:
|
|
case TypeTableEntryIdEnum:
|
|
case TypeTableEntryIdMetaType:
|
|
return false;
|
|
case TypeTableEntryIdInt:
|
|
if (is_num_lit_unsigned(num_lit)) {
|
|
return lit_size_in_bits <= other_type->size_in_bits;
|
|
} else {
|
|
return false;
|
|
}
|
|
case TypeTableEntryIdFloat:
|
|
if (is_num_lit_float(num_lit)) {
|
|
return lit_size_in_bits <= other_type->size_in_bits;
|
|
} else if (other_type->size_in_bits == 32) {
|
|
return lit_size_in_bits < 24;
|
|
} else if (other_type->size_in_bits == 64) {
|
|
return lit_size_in_bits < 53;
|
|
} else {
|
|
return false;
|
|
}
|
|
case TypeTableEntryIdMaybe:
|
|
return false;
|
|
}
|
|
zig_unreachable();
|
|
}
|
|
|
|
static TypeTableEntry *resolve_rhs_number_literal(CodeGen *g, AstNode *non_literal_node,
|
|
TypeTableEntry *non_literal_type, AstNode *literal_node, TypeTableEntry *literal_type)
|
|
{
|
|
NumLitCodeGen *num_lit_codegen = get_resolved_num_lit(literal_node);
|
|
|
|
if (non_literal_type && num_lit_fits_in_other_type(g, literal_type, non_literal_type)) {
|
|
assert(!num_lit_codegen->resolved_type);
|
|
num_lit_codegen->resolved_type = non_literal_type;
|
|
return non_literal_type;
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
static TypeTableEntry * resolve_number_literals(CodeGen *g, AstNode *node1, AstNode *node2,
|
|
TypeTableEntry *type1, TypeTableEntry *type2)
|
|
{
|
|
if (type1->id == TypeTableEntryIdNumberLiteral &&
|
|
type2->id == TypeTableEntryIdNumberLiteral)
|
|
{
|
|
NumLitCodeGen *codegen_num_lit_1 = get_resolved_num_lit(node1);
|
|
NumLitCodeGen *codegen_num_lit_2 = get_resolved_num_lit(node2);
|
|
|
|
assert(!codegen_num_lit_1->resolved_type);
|
|
assert(!codegen_num_lit_2->resolved_type);
|
|
|
|
if (is_num_lit_float(type1->data.num_lit.kind) &&
|
|
is_num_lit_float(type2->data.num_lit.kind))
|
|
{
|
|
codegen_num_lit_1->resolved_type = g->builtin_types.entry_f64;
|
|
codegen_num_lit_2->resolved_type = g->builtin_types.entry_f64;
|
|
return g->builtin_types.entry_f64;
|
|
} else if (is_num_lit_unsigned(type1->data.num_lit.kind) &&
|
|
is_num_lit_unsigned(type2->data.num_lit.kind))
|
|
{
|
|
codegen_num_lit_1->resolved_type = g->builtin_types.entry_u64;
|
|
codegen_num_lit_2->resolved_type = g->builtin_types.entry_u64;
|
|
return g->builtin_types.entry_u64;
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
} else if (type1->id == TypeTableEntryIdNumberLiteral) {
|
|
return resolve_rhs_number_literal(g, node2, type2, node1, type1);
|
|
} else {
|
|
assert(type2->id == TypeTableEntryIdNumberLiteral);
|
|
return resolve_rhs_number_literal(g, node1, type1, node2, type2);
|
|
}
|
|
}
|
|
|
|
static TypeTableEntry *determine_peer_type_compatibility(CodeGen *g, AstNode *node,
|
|
TypeTableEntry *type1, TypeTableEntry *type2, AstNode *node1, AstNode *node2)
|
|
{
|
|
if (type1->id == TypeTableEntryIdInvalid ||
|
|
type2->id == TypeTableEntryIdInvalid)
|
|
{
|
|
return type1;
|
|
} else if (type1->id == TypeTableEntryIdUnreachable) {
|
|
return type2;
|
|
} else if (type2->id == TypeTableEntryIdUnreachable) {
|
|
return type1;
|
|
} else if (type1->id == TypeTableEntryIdInt &&
|
|
type2->id == TypeTableEntryIdInt &&
|
|
type1->data.integral.is_signed == type2->data.integral.is_signed)
|
|
{
|
|
return (type1->size_in_bits > type2->size_in_bits) ? type1 : type2;
|
|
} else if (type1->id == TypeTableEntryIdFloat &&
|
|
type2->id == TypeTableEntryIdFloat)
|
|
{
|
|
return (type1->size_in_bits > type2->size_in_bits) ? type1 : type2;
|
|
} else if (type1->id == TypeTableEntryIdArray &&
|
|
type2->id == TypeTableEntryIdArray &&
|
|
type1 == type2)
|
|
{
|
|
return type1;
|
|
} else if (type1->id == TypeTableEntryIdNumberLiteral ||
|
|
type2->id == TypeTableEntryIdNumberLiteral)
|
|
{
|
|
TypeTableEntry *resolved_type = resolve_number_literals(g, node1, node2, type1, type2);
|
|
if (resolved_type)
|
|
return resolved_type;
|
|
} else if (type1 == type2) {
|
|
return type1;
|
|
}
|
|
|
|
add_node_error(g, node,
|
|
buf_sprintf("incompatible types: '%s' and '%s'",
|
|
buf_ptr(&type1->name), buf_ptr(&type2->name)));
|
|
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
|
|
static TypeTableEntry *resolve_type_compatibility(CodeGen *g, BlockContext *context, AstNode *node,
|
|
TypeTableEntry *expected_type, TypeTableEntry *actual_type)
|
|
{
|
|
if (expected_type == nullptr)
|
|
return actual_type; // anything will do
|
|
if (expected_type == actual_type)
|
|
return expected_type; // match
|
|
if (expected_type->id == TypeTableEntryIdInvalid || actual_type->id == TypeTableEntryIdInvalid)
|
|
return expected_type; // already complained
|
|
if (actual_type->id == TypeTableEntryIdUnreachable)
|
|
return actual_type; // sorry toots; gotta run. good luck with that expected type.
|
|
|
|
if (actual_type->id == TypeTableEntryIdNumberLiteral &&
|
|
num_lit_fits_in_other_type(g, actual_type, expected_type))
|
|
{
|
|
NumLitCodeGen *num_lit_code_gen = get_resolved_num_lit(node);
|
|
assert(!num_lit_code_gen->resolved_type ||
|
|
num_lit_code_gen->resolved_type == expected_type);
|
|
num_lit_code_gen->resolved_type = expected_type;
|
|
return expected_type;
|
|
}
|
|
|
|
if (expected_type->id == TypeTableEntryIdMaybe &&
|
|
actual_type->id == TypeTableEntryIdMaybe)
|
|
{
|
|
TypeTableEntry *expected_child = expected_type->data.maybe.child_type;
|
|
TypeTableEntry *actual_child = actual_type->data.maybe.child_type;
|
|
return resolve_type_compatibility(g, context, node, expected_child, actual_child);
|
|
}
|
|
|
|
// implicit conversion from non maybe type to maybe type
|
|
if (expected_type->id == TypeTableEntryIdMaybe) {
|
|
TypeTableEntry *resolved_type = resolve_type_compatibility(g, context, node,
|
|
expected_type->data.maybe.child_type, actual_type);
|
|
if (resolved_type->id == TypeTableEntryIdInvalid) {
|
|
return resolved_type;
|
|
}
|
|
Expr *expr = get_resolved_expr(node);
|
|
expr->implicit_maybe_cast.op = CastOpMaybeWrap;
|
|
expr->implicit_maybe_cast.after_type = expected_type;
|
|
expr->implicit_maybe_cast.source_node = node;
|
|
context->cast_expr_alloca_list.append(&expr->implicit_maybe_cast);
|
|
return expected_type;
|
|
}
|
|
|
|
// implicit widening conversion
|
|
if (expected_type->id == TypeTableEntryIdInt &&
|
|
actual_type->id == TypeTableEntryIdInt &&
|
|
expected_type->data.integral.is_signed == actual_type->data.integral.is_signed &&
|
|
expected_type->size_in_bits > actual_type->size_in_bits)
|
|
{
|
|
Expr *expr = get_resolved_expr(node);
|
|
expr->implicit_cast.after_type = expected_type;
|
|
expr->implicit_cast.op = CastOpIntWidenOrShorten;
|
|
expr->implicit_cast.source_node = node;
|
|
return expected_type;
|
|
}
|
|
|
|
// implicit constant sized array to unknown size array conversion
|
|
if (expected_type->id == TypeTableEntryIdStruct &&
|
|
expected_type->data.structure.is_unknown_size_array &&
|
|
actual_type->id == TypeTableEntryIdArray &&
|
|
actual_type->data.array.child_type == expected_type->data.structure.fields[0].type_entry->data.pointer.child_type)
|
|
{
|
|
Expr *expr = get_resolved_expr(node);
|
|
expr->implicit_cast.after_type = expected_type;
|
|
expr->implicit_cast.op = CastOpToUnknownSizeArray;
|
|
expr->implicit_cast.source_node = node;
|
|
context->cast_expr_alloca_list.append(&expr->implicit_cast);
|
|
return expected_type;
|
|
}
|
|
|
|
// implicit non-const to const and ignore noalias
|
|
if (expected_type->id == TypeTableEntryIdPointer &&
|
|
actual_type->id == TypeTableEntryIdPointer &&
|
|
(!actual_type->data.pointer.is_const || expected_type->data.pointer.is_const))
|
|
{
|
|
TypeTableEntry *resolved_type = resolve_type_compatibility(g, context, node,
|
|
expected_type->data.pointer.child_type,
|
|
actual_type->data.pointer.child_type);
|
|
if (resolved_type->id == TypeTableEntryIdInvalid) {
|
|
return resolved_type;
|
|
}
|
|
return expected_type;
|
|
}
|
|
|
|
add_node_error(g, first_executing_node(node),
|
|
buf_sprintf("expected type '%s', got '%s'",
|
|
buf_ptr(&expected_type->name),
|
|
buf_ptr(&actual_type->name)));
|
|
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
|
|
static TypeTableEntry *resolve_peer_type_compatibility(CodeGen *g, BlockContext *block_context,
|
|
AstNode *parent_node,
|
|
AstNode *child1, AstNode *child2,
|
|
TypeTableEntry *type1, TypeTableEntry *type2)
|
|
{
|
|
assert(type1);
|
|
assert(type2);
|
|
|
|
TypeTableEntry *parent_type = determine_peer_type_compatibility(g, parent_node, type1, type2, child1, child2);
|
|
|
|
if (parent_type->id == TypeTableEntryIdInvalid) {
|
|
return parent_type;
|
|
}
|
|
|
|
resolve_type_compatibility(g, block_context, child1, parent_type, type1);
|
|
resolve_type_compatibility(g, block_context, child2, parent_type, type2);
|
|
|
|
return parent_type;
|
|
}
|
|
|
|
BlockContext *new_block_context(AstNode *node, BlockContext *parent) {
|
|
BlockContext *context = allocate<BlockContext>(1);
|
|
context->node = node;
|
|
context->parent = parent;
|
|
context->variable_table.init(8);
|
|
context->type_table.init(8);
|
|
|
|
if (parent) {
|
|
if (parent->next_child_parent_loop_node) {
|
|
context->parent_loop_node = parent->next_child_parent_loop_node;
|
|
parent->next_child_parent_loop_node = nullptr;
|
|
} else {
|
|
context->parent_loop_node = parent->parent_loop_node;
|
|
}
|
|
}
|
|
|
|
if (node && node->type == NodeTypeFnDef) {
|
|
AstNode *fn_proto_node = node->data.fn_def.fn_proto;
|
|
context->fn_entry = fn_proto_node->data.fn_proto.fn_table_entry;
|
|
} else if (parent) {
|
|
context->fn_entry = parent->fn_entry;
|
|
}
|
|
|
|
if (context->fn_entry) {
|
|
context->fn_entry->all_block_contexts.append(context);
|
|
}
|
|
|
|
return context;
|
|
}
|
|
|
|
static VariableTableEntry *find_local_variable(BlockContext *context, Buf *name) {
|
|
while (context && context->fn_entry) {
|
|
auto entry = context->variable_table.maybe_get(name);
|
|
if (entry != nullptr)
|
|
return entry->value;
|
|
|
|
context = context->parent;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
VariableTableEntry *find_variable(BlockContext *context, Buf *name) {
|
|
while (context) {
|
|
auto entry = context->variable_table.maybe_get(name);
|
|
if (entry != nullptr)
|
|
return entry->value;
|
|
|
|
context = context->parent;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
TypeTableEntry *find_container(BlockContext *context, Buf *name) {
|
|
while (context) {
|
|
auto entry = context->type_table.maybe_get(name);
|
|
if (entry != nullptr)
|
|
return entry->value;
|
|
|
|
context = context->parent;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static TypeEnumField *get_enum_field(TypeTableEntry *enum_type, Buf *name) {
|
|
for (int i = 0; i < enum_type->data.enumeration.field_count; i += 1) {
|
|
TypeEnumField *type_enum_field = &enum_type->data.enumeration.fields[i];
|
|
if (buf_eql_buf(type_enum_field->name, name)) {
|
|
return type_enum_field;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static TypeStructField *get_struct_field(TypeTableEntry *struct_type, Buf *name) {
|
|
for (int i = 0; i < struct_type->data.structure.field_count; i += 1) {
|
|
TypeStructField *type_struct_field = &struct_type->data.structure.fields[i];
|
|
if (buf_eql_buf(type_struct_field->name, name)) {
|
|
return type_struct_field;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static TypeTableEntry *analyze_enum_value_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
AstNode *field_access_node, AstNode *value_node, TypeTableEntry *enum_type, Buf *field_name)
|
|
{
|
|
assert(field_access_node->type == NodeTypeFieldAccessExpr);
|
|
|
|
TypeEnumField *type_enum_field = get_enum_field(enum_type, field_name);
|
|
field_access_node->data.field_access_expr.type_enum_field = type_enum_field;
|
|
|
|
if (type_enum_field) {
|
|
if (value_node) {
|
|
analyze_expression(g, import, context, type_enum_field->type_entry, value_node);
|
|
|
|
StructValExprCodeGen *codegen = &field_access_node->data.field_access_expr.resolved_struct_val_expr;
|
|
codegen->type_entry = enum_type;
|
|
codegen->source_node = field_access_node;
|
|
context->struct_val_expr_alloca_list.append(codegen);
|
|
} else if (type_enum_field->type_entry->id != TypeTableEntryIdVoid) {
|
|
add_node_error(g, field_access_node,
|
|
buf_sprintf("enum value '%s.%s' requires parameter of type '%s'",
|
|
buf_ptr(&enum_type->name),
|
|
buf_ptr(field_name),
|
|
buf_ptr(&type_enum_field->type_entry->name)));
|
|
}
|
|
} else {
|
|
add_node_error(g, field_access_node,
|
|
buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name),
|
|
buf_ptr(&enum_type->name)));
|
|
}
|
|
return enum_type;
|
|
}
|
|
|
|
static TypeTableEntry *analyze_field_access_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
AstNode *node)
|
|
{
|
|
assert(node->type == NodeTypeFieldAccessExpr);
|
|
|
|
AstNode *struct_expr_node = node->data.field_access_expr.struct_expr;
|
|
TypeTableEntry *struct_type = analyze_expression(g, import, context, nullptr, struct_expr_node);
|
|
|
|
TypeTableEntry *return_type;
|
|
|
|
if (struct_type->id == TypeTableEntryIdStruct || (struct_type->id == TypeTableEntryIdPointer &&
|
|
struct_type->data.pointer.child_type->id == TypeTableEntryIdStruct))
|
|
{
|
|
Buf *field_name = &node->data.field_access_expr.field_name;
|
|
|
|
TypeTableEntry *bare_struct_type = (struct_type->id == TypeTableEntryIdStruct) ?
|
|
struct_type : struct_type->data.pointer.child_type;
|
|
|
|
node->data.field_access_expr.type_struct_field = get_struct_field(bare_struct_type, field_name);
|
|
if (node->data.field_access_expr.type_struct_field) {
|
|
return_type = node->data.field_access_expr.type_struct_field->type_entry;
|
|
} else {
|
|
add_node_error(g, node,
|
|
buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name), buf_ptr(&struct_type->name)));
|
|
return_type = g->builtin_types.entry_invalid;
|
|
}
|
|
} else if (struct_type->id == TypeTableEntryIdArray) {
|
|
Buf *name = &node->data.field_access_expr.field_name;
|
|
if (buf_eql_str(name, "len")) {
|
|
return_type = g->builtin_types.entry_usize;
|
|
} else if (buf_eql_str(name, "ptr")) {
|
|
// TODO determine whether the pointer should be const
|
|
return_type = get_pointer_to_type(g, struct_type->data.array.child_type, false, false);
|
|
} else {
|
|
add_node_error(g, node,
|
|
buf_sprintf("no member named '%s' in '%s'", buf_ptr(name),
|
|
buf_ptr(&struct_type->name)));
|
|
return_type = g->builtin_types.entry_invalid;
|
|
}
|
|
} else if (struct_type->id == TypeTableEntryIdMetaType &&
|
|
struct_type->data.meta_type.child_type->id == TypeTableEntryIdEnum)
|
|
{
|
|
TypeTableEntry *enum_type = struct_type->data.meta_type.child_type;
|
|
Buf *field_name = &node->data.field_access_expr.field_name;
|
|
return_type = analyze_enum_value_expr(g, import, context, node, nullptr, enum_type, field_name);
|
|
} else {
|
|
if (struct_type->id != TypeTableEntryIdInvalid) {
|
|
add_node_error(g, node,
|
|
buf_sprintf("type '%s' does not support field access", buf_ptr(&struct_type->name)));
|
|
}
|
|
return_type = g->builtin_types.entry_invalid;
|
|
}
|
|
|
|
return return_type;
|
|
}
|
|
|
|
static TypeTableEntry *analyze_slice_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
AstNode *node)
|
|
{
|
|
assert(node->type == NodeTypeSliceExpr);
|
|
|
|
TypeTableEntry *array_type = analyze_expression(g, import, context, nullptr,
|
|
node->data.slice_expr.array_ref_expr);
|
|
|
|
TypeTableEntry *return_type;
|
|
|
|
if (array_type->id == TypeTableEntryIdInvalid) {
|
|
return_type = g->builtin_types.entry_invalid;
|
|
} else if (array_type->id == TypeTableEntryIdArray) {
|
|
return_type = get_unknown_size_array_type(g, import, array_type->data.array.child_type,
|
|
node->data.slice_expr.is_const, false);
|
|
} else if (array_type->id == TypeTableEntryIdPointer) {
|
|
return_type = get_unknown_size_array_type(g, import, array_type->data.pointer.child_type,
|
|
node->data.slice_expr.is_const, false);
|
|
} else if (array_type->id == TypeTableEntryIdStruct &&
|
|
array_type->data.structure.is_unknown_size_array)
|
|
{
|
|
return_type = get_unknown_size_array_type(g, import,
|
|
array_type->data.structure.fields[0].type_entry->data.pointer.child_type,
|
|
node->data.slice_expr.is_const, false);
|
|
} else {
|
|
add_node_error(g, node,
|
|
buf_sprintf("slice of non-array type '%s'", buf_ptr(&array_type->name)));
|
|
return_type = g->builtin_types.entry_invalid;
|
|
}
|
|
|
|
if (return_type->id != TypeTableEntryIdInvalid) {
|
|
node->data.slice_expr.resolved_struct_val_expr.type_entry = return_type;
|
|
node->data.slice_expr.resolved_struct_val_expr.source_node = node;
|
|
context->struct_val_expr_alloca_list.append(&node->data.slice_expr.resolved_struct_val_expr);
|
|
}
|
|
|
|
analyze_expression(g, import, context, g->builtin_types.entry_usize, node->data.slice_expr.start);
|
|
|
|
if (node->data.slice_expr.end) {
|
|
analyze_expression(g, import, context, g->builtin_types.entry_usize, node->data.slice_expr.end);
|
|
}
|
|
|
|
return return_type;
|
|
}
|
|
|
|
static TypeTableEntry *analyze_array_access_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
AstNode *node)
|
|
{
|
|
TypeTableEntry *array_type = analyze_expression(g, import, context, nullptr,
|
|
node->data.array_access_expr.array_ref_expr);
|
|
|
|
TypeTableEntry *return_type;
|
|
|
|
if (array_type->id == TypeTableEntryIdInvalid) {
|
|
return_type = g->builtin_types.entry_invalid;
|
|
} else if (array_type->id == TypeTableEntryIdArray) {
|
|
return_type = array_type->data.array.child_type;
|
|
} else if (array_type->id == TypeTableEntryIdPointer) {
|
|
return_type = array_type->data.pointer.child_type;
|
|
} else if (array_type->id == TypeTableEntryIdStruct &&
|
|
array_type->data.structure.is_unknown_size_array)
|
|
{
|
|
return_type = array_type->data.structure.fields[0].type_entry->data.pointer.child_type;
|
|
} else {
|
|
add_node_error(g, node,
|
|
buf_sprintf("array access of non-array type '%s'", buf_ptr(&array_type->name)));
|
|
return_type = g->builtin_types.entry_invalid;
|
|
}
|
|
|
|
analyze_expression(g, import, context, g->builtin_types.entry_usize, node->data.array_access_expr.subscript);
|
|
|
|
return return_type;
|
|
}
|
|
|
|
static TypeTableEntry *analyze_symbol_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
TypeTableEntry *expected_type, AstNode *node)
|
|
{
|
|
Buf *variable_name = &node->data.symbol_expr.symbol;
|
|
VariableTableEntry *var = find_variable(context, variable_name);
|
|
if (var) {
|
|
return var->type;
|
|
} else {
|
|
TypeTableEntry *container_type = find_container(context, variable_name);
|
|
if (container_type) {
|
|
return get_meta_type(g, container_type);
|
|
} else {
|
|
add_node_error(g, node,
|
|
buf_sprintf("use of undeclared identifier '%s'", buf_ptr(variable_name)));
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
}
|
|
}
|
|
|
|
static TypeTableEntry *analyze_variable_name(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
AstNode *node, Buf *variable_name)
|
|
{
|
|
VariableTableEntry *var = find_variable(context, variable_name);
|
|
if (var) {
|
|
return var->type;
|
|
} else {
|
|
add_node_error(g, node,
|
|
buf_sprintf("use of undeclared identifier '%s'", buf_ptr(variable_name)));
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
}
|
|
|
|
static bool is_op_allowed(TypeTableEntry *type, BinOpType op) {
|
|
switch (op) {
|
|
case BinOpTypeAssign:
|
|
return true;
|
|
case BinOpTypeAssignTimes:
|
|
case BinOpTypeAssignDiv:
|
|
case BinOpTypeAssignMod:
|
|
return type->id == TypeTableEntryIdInt || type->id == TypeTableEntryIdFloat;
|
|
case BinOpTypeAssignPlus:
|
|
case BinOpTypeAssignMinus:
|
|
return type->id == TypeTableEntryIdInt ||
|
|
type->id == TypeTableEntryIdFloat ||
|
|
type->id == TypeTableEntryIdPointer;
|
|
case BinOpTypeAssignBitShiftLeft:
|
|
case BinOpTypeAssignBitShiftRight:
|
|
case BinOpTypeAssignBitAnd:
|
|
case BinOpTypeAssignBitXor:
|
|
case BinOpTypeAssignBitOr:
|
|
return type->id == TypeTableEntryIdInt;
|
|
case BinOpTypeAssignBoolAnd:
|
|
case BinOpTypeAssignBoolOr:
|
|
return type->id == TypeTableEntryIdBool;
|
|
|
|
case BinOpTypeInvalid:
|
|
case BinOpTypeBoolOr:
|
|
case BinOpTypeBoolAnd:
|
|
case BinOpTypeCmpEq:
|
|
case BinOpTypeCmpNotEq:
|
|
case BinOpTypeCmpLessThan:
|
|
case BinOpTypeCmpGreaterThan:
|
|
case BinOpTypeCmpLessOrEq:
|
|
case BinOpTypeCmpGreaterOrEq:
|
|
case BinOpTypeBinOr:
|
|
case BinOpTypeBinXor:
|
|
case BinOpTypeBinAnd:
|
|
case BinOpTypeBitShiftLeft:
|
|
case BinOpTypeBitShiftRight:
|
|
case BinOpTypeAdd:
|
|
case BinOpTypeSub:
|
|
case BinOpTypeMult:
|
|
case BinOpTypeDiv:
|
|
case BinOpTypeMod:
|
|
case BinOpTypeUnwrapMaybe:
|
|
zig_unreachable();
|
|
}
|
|
zig_unreachable();
|
|
}
|
|
|
|
static TypeTableEntry *analyze_cast_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
TypeTableEntry *expected_type, AstNode *node)
|
|
{
|
|
assert(node->type == NodeTypeCastExpr);
|
|
|
|
TypeTableEntry *wanted_type = resolve_type(g, node->data.cast_expr.type, import, context, false);
|
|
TypeTableEntry *actual_type = analyze_expression(g, import, context, nullptr, node->data.cast_expr.expr);
|
|
|
|
if (wanted_type->id == TypeTableEntryIdInvalid ||
|
|
actual_type->id == TypeTableEntryIdInvalid)
|
|
{
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
|
|
Cast *cast = &node->data.cast_expr.cast;
|
|
cast->source_node = node;
|
|
cast->after_type = wanted_type;
|
|
|
|
if ((wanted_type == g->builtin_types.entry_isize || wanted_type == g->builtin_types.entry_usize) &&
|
|
actual_type->id == TypeTableEntryIdPointer)
|
|
{
|
|
cast->op = CastOpPtrToInt;
|
|
return wanted_type;
|
|
} else if (wanted_type->id == TypeTableEntryIdInt &&
|
|
actual_type->id == TypeTableEntryIdInt)
|
|
{
|
|
cast->op = CastOpIntWidenOrShorten;
|
|
return wanted_type;
|
|
} else if (wanted_type->id == TypeTableEntryIdStruct &&
|
|
wanted_type->data.structure.is_unknown_size_array &&
|
|
actual_type->id == TypeTableEntryIdArray &&
|
|
actual_type->data.array.child_type == wanted_type->data.structure.fields[0].type_entry)
|
|
{
|
|
cast->op = CastOpToUnknownSizeArray;
|
|
context->cast_expr_alloca_list.append(cast);
|
|
return wanted_type;
|
|
} else if (actual_type->id == TypeTableEntryIdNumberLiteral &&
|
|
num_lit_fits_in_other_type(g, actual_type, wanted_type))
|
|
{
|
|
AstNode *literal_node = node->data.cast_expr.expr;
|
|
NumLitCodeGen *codegen_num_lit = get_resolved_num_lit(literal_node);
|
|
assert(!codegen_num_lit->resolved_type);
|
|
codegen_num_lit->resolved_type = wanted_type;
|
|
cast->op = CastOpNothing;
|
|
return wanted_type;
|
|
} else if (actual_type->id == TypeTableEntryIdPointer &&
|
|
wanted_type->id == TypeTableEntryIdPointer)
|
|
{
|
|
cast->op = CastOpPointerReinterpret;
|
|
return wanted_type;
|
|
} else {
|
|
add_node_error(g, node,
|
|
buf_sprintf("invalid cast from type '%s' to '%s'",
|
|
buf_ptr(&actual_type->name),
|
|
buf_ptr(&wanted_type->name)));
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
}
|
|
|
|
enum LValPurpose {
|
|
LValPurposeAssign,
|
|
LValPurposeAddressOf,
|
|
};
|
|
|
|
static TypeTableEntry *analyze_lvalue(CodeGen *g, ImportTableEntry *import, BlockContext *block_context,
|
|
AstNode *lhs_node, LValPurpose purpose, bool is_ptr_const)
|
|
{
|
|
TypeTableEntry *expected_rhs_type = nullptr;
|
|
if (lhs_node->type == NodeTypeSymbol) {
|
|
Buf *name = &lhs_node->data.symbol_expr.symbol;
|
|
VariableTableEntry *var = find_variable(block_context, name);
|
|
if (var) {
|
|
if (purpose == LValPurposeAssign && var->is_const) {
|
|
add_node_error(g, lhs_node,
|
|
buf_sprintf("cannot assign to constant"));
|
|
expected_rhs_type = g->builtin_types.entry_invalid;
|
|
} else if (purpose == LValPurposeAddressOf && var->is_const && !is_ptr_const) {
|
|
add_node_error(g, lhs_node,
|
|
buf_sprintf("must use &const to get address of constant"));
|
|
expected_rhs_type = g->builtin_types.entry_invalid;
|
|
} else {
|
|
expected_rhs_type = var->type;
|
|
}
|
|
} else {
|
|
add_node_error(g, lhs_node,
|
|
buf_sprintf("use of undeclared identifier '%s'", buf_ptr(name)));
|
|
expected_rhs_type = g->builtin_types.entry_invalid;
|
|
}
|
|
} else if (lhs_node->type == NodeTypeArrayAccessExpr) {
|
|
expected_rhs_type = analyze_array_access_expr(g, import, block_context, lhs_node);
|
|
} else if (lhs_node->type == NodeTypeFieldAccessExpr) {
|
|
expected_rhs_type = analyze_field_access_expr(g, import, block_context, lhs_node);
|
|
} else if (lhs_node->type == NodeTypePrefixOpExpr &&
|
|
lhs_node->data.prefix_op_expr.prefix_op == PrefixOpDereference)
|
|
{
|
|
assert(purpose == LValPurposeAssign);
|
|
AstNode *target_node = lhs_node->data.prefix_op_expr.primary_expr;
|
|
TypeTableEntry *type_entry = analyze_expression(g, import, block_context, nullptr, target_node);
|
|
if (type_entry->id == TypeTableEntryIdInvalid) {
|
|
expected_rhs_type = type_entry;
|
|
} else if (type_entry->id == TypeTableEntryIdPointer) {
|
|
expected_rhs_type = type_entry->data.pointer.child_type;
|
|
} else {
|
|
add_node_error(g, target_node,
|
|
buf_sprintf("indirection requires pointer operand ('%s' invalid)",
|
|
buf_ptr(&type_entry->name)));
|
|
expected_rhs_type = g->builtin_types.entry_invalid;
|
|
}
|
|
} else {
|
|
if (purpose == LValPurposeAssign) {
|
|
add_node_error(g, lhs_node,
|
|
buf_sprintf("invalid assignment target"));
|
|
} else if (purpose == LValPurposeAddressOf) {
|
|
add_node_error(g, lhs_node,
|
|
buf_sprintf("invalid addressof target"));
|
|
}
|
|
expected_rhs_type = g->builtin_types.entry_invalid;
|
|
}
|
|
assert(expected_rhs_type);
|
|
return expected_rhs_type;
|
|
}
|
|
|
|
static TypeTableEntry *analyze_bin_op_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
TypeTableEntry *expected_type, AstNode *node)
|
|
{
|
|
switch (node->data.bin_op_expr.bin_op) {
|
|
case BinOpTypeAssign:
|
|
case BinOpTypeAssignTimes:
|
|
case BinOpTypeAssignDiv:
|
|
case BinOpTypeAssignMod:
|
|
case BinOpTypeAssignPlus:
|
|
case BinOpTypeAssignMinus:
|
|
case BinOpTypeAssignBitShiftLeft:
|
|
case BinOpTypeAssignBitShiftRight:
|
|
case BinOpTypeAssignBitAnd:
|
|
case BinOpTypeAssignBitXor:
|
|
case BinOpTypeAssignBitOr:
|
|
case BinOpTypeAssignBoolAnd:
|
|
case BinOpTypeAssignBoolOr:
|
|
{
|
|
AstNode *lhs_node = node->data.bin_op_expr.op1;
|
|
|
|
TypeTableEntry *expected_rhs_type = analyze_lvalue(g, import, context, lhs_node,
|
|
LValPurposeAssign, false);
|
|
if (!is_op_allowed(expected_rhs_type, node->data.bin_op_expr.bin_op)) {
|
|
if (expected_rhs_type->id != TypeTableEntryIdInvalid) {
|
|
add_node_error(g, lhs_node,
|
|
buf_sprintf("operator not allowed for type '%s'",
|
|
buf_ptr(&expected_rhs_type->name)));
|
|
}
|
|
}
|
|
|
|
analyze_expression(g, import, context, expected_rhs_type, node->data.bin_op_expr.op2);
|
|
return g->builtin_types.entry_void;
|
|
}
|
|
case BinOpTypeBoolOr:
|
|
case BinOpTypeBoolAnd:
|
|
analyze_expression(g, import, context, g->builtin_types.entry_bool, node->data.bin_op_expr.op1);
|
|
analyze_expression(g, import, context, g->builtin_types.entry_bool, node->data.bin_op_expr.op2);
|
|
return g->builtin_types.entry_bool;
|
|
case BinOpTypeCmpEq:
|
|
case BinOpTypeCmpNotEq:
|
|
case BinOpTypeCmpLessThan:
|
|
case BinOpTypeCmpGreaterThan:
|
|
case BinOpTypeCmpLessOrEq:
|
|
case BinOpTypeCmpGreaterOrEq:
|
|
{
|
|
AstNode *op1 = node->data.bin_op_expr.op1;
|
|
AstNode *op2 = node->data.bin_op_expr.op2;
|
|
TypeTableEntry *lhs_type = analyze_expression(g, import, context, nullptr, op1);
|
|
TypeTableEntry *rhs_type = analyze_expression(g, import, context, nullptr, op2);
|
|
|
|
resolve_peer_type_compatibility(g, context, node, op1, op2, lhs_type, rhs_type);
|
|
|
|
return g->builtin_types.entry_bool;
|
|
}
|
|
case BinOpTypeBinOr:
|
|
case BinOpTypeBinXor:
|
|
case BinOpTypeBinAnd:
|
|
case BinOpTypeBitShiftLeft:
|
|
case BinOpTypeBitShiftRight:
|
|
case BinOpTypeAdd:
|
|
case BinOpTypeSub:
|
|
case BinOpTypeMult:
|
|
case BinOpTypeDiv:
|
|
case BinOpTypeMod:
|
|
{
|
|
AstNode *op1 = node->data.bin_op_expr.op1;
|
|
AstNode *op2 = node->data.bin_op_expr.op2;
|
|
TypeTableEntry *lhs_type = analyze_expression(g, import, context, expected_type, op1);
|
|
TypeTableEntry *rhs_type = analyze_expression(g, import, context, expected_type, op2);
|
|
|
|
return resolve_peer_type_compatibility(g, context, node, op1, op2, lhs_type, rhs_type);
|
|
}
|
|
case BinOpTypeUnwrapMaybe:
|
|
{
|
|
AstNode *op1 = node->data.bin_op_expr.op1;
|
|
AstNode *op2 = node->data.bin_op_expr.op2;
|
|
TypeTableEntry *lhs_type = analyze_expression(g, import, context, nullptr, op1);
|
|
|
|
if (lhs_type->id == TypeTableEntryIdInvalid) {
|
|
return lhs_type;
|
|
} else if (lhs_type->id == TypeTableEntryIdMaybe) {
|
|
TypeTableEntry *child_type = lhs_type->data.maybe.child_type;
|
|
analyze_expression(g, import, context, child_type, op2);
|
|
return child_type;
|
|
} else {
|
|
add_node_error(g, op1,
|
|
buf_sprintf("expected maybe type, got '%s'",
|
|
buf_ptr(&lhs_type->name)));
|
|
}
|
|
}
|
|
case BinOpTypeInvalid:
|
|
zig_unreachable();
|
|
}
|
|
zig_unreachable();
|
|
}
|
|
|
|
static VariableTableEntry *analyze_variable_declaration_raw(CodeGen *g, ImportTableEntry *import,
|
|
BlockContext *context, AstNode *source_node,
|
|
AstNodeVariableDeclaration *variable_declaration,
|
|
bool expr_is_maybe)
|
|
{
|
|
TypeTableEntry *explicit_type = nullptr;
|
|
if (variable_declaration->type != nullptr) {
|
|
explicit_type = resolve_type(g, variable_declaration->type, import, context, false);
|
|
if (explicit_type->id == TypeTableEntryIdUnreachable) {
|
|
add_node_error(g, variable_declaration->type,
|
|
buf_sprintf("variable of type 'unreachable' not allowed"));
|
|
explicit_type = g->builtin_types.entry_invalid;
|
|
}
|
|
}
|
|
|
|
TypeTableEntry *implicit_type = nullptr;
|
|
if (variable_declaration->expr != nullptr) {
|
|
implicit_type = analyze_expression(g, import, context, explicit_type, variable_declaration->expr);
|
|
if (implicit_type->id == TypeTableEntryIdInvalid) {
|
|
// ignore the poison value
|
|
} else if (expr_is_maybe) {
|
|
if (implicit_type->id == TypeTableEntryIdMaybe) {
|
|
implicit_type = implicit_type->data.maybe.child_type;
|
|
} else {
|
|
add_node_error(g, variable_declaration->expr, buf_sprintf("expected maybe type"));
|
|
implicit_type = g->builtin_types.entry_invalid;
|
|
}
|
|
} else if (implicit_type->id == TypeTableEntryIdUnreachable) {
|
|
add_node_error(g, source_node,
|
|
buf_sprintf("variable initialization is unreachable"));
|
|
implicit_type = g->builtin_types.entry_invalid;
|
|
} else if (implicit_type->id == TypeTableEntryIdNumberLiteral) {
|
|
add_node_error(g, source_node,
|
|
buf_sprintf("unable to infer variable type"));
|
|
implicit_type = g->builtin_types.entry_invalid;
|
|
}
|
|
}
|
|
|
|
if (implicit_type == nullptr && variable_declaration->is_const) {
|
|
add_node_error(g, source_node, buf_sprintf("const variable missing initialization"));
|
|
implicit_type = g->builtin_types.entry_invalid;
|
|
}
|
|
|
|
TypeTableEntry *type = explicit_type != nullptr ? explicit_type : implicit_type;
|
|
assert(type != nullptr); // should have been caught by the parser
|
|
|
|
VariableTableEntry *existing_variable = find_local_variable(context, &variable_declaration->symbol);
|
|
if (existing_variable) {
|
|
add_node_error(g, source_node,
|
|
buf_sprintf("redeclaration of variable '%s'", buf_ptr(&variable_declaration->symbol)));
|
|
} else {
|
|
VariableTableEntry *variable_entry = allocate<VariableTableEntry>(1);
|
|
buf_init_from_buf(&variable_entry->name, &variable_declaration->symbol);
|
|
variable_entry->type = type;
|
|
variable_entry->is_const = variable_declaration->is_const;
|
|
variable_entry->is_ptr = true;
|
|
variable_entry->decl_node = source_node;
|
|
context->variable_table.put(&variable_entry->name, variable_entry);
|
|
|
|
bool is_pub = (variable_declaration->visib_mod != VisibModPrivate);
|
|
if (is_pub) {
|
|
for (int i = 0; i < import->importers.length; i += 1) {
|
|
ImporterInfo importer = import->importers.at(i);
|
|
auto table_entry = importer.import->block_context->variable_table.maybe_get(&variable_entry->name);
|
|
if (table_entry) {
|
|
add_node_error(g, importer.source_node,
|
|
buf_sprintf("import of variable '%s' overrides existing definition",
|
|
buf_ptr(&variable_entry->name)));
|
|
} else {
|
|
importer.import->block_context->variable_table.put(&variable_entry->name, variable_entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
return variable_entry;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static VariableTableEntry *analyze_variable_declaration(CodeGen *g, ImportTableEntry *import,
|
|
BlockContext *context, TypeTableEntry *expected_type, AstNode *node)
|
|
{
|
|
AstNodeVariableDeclaration *variable_declaration = &node->data.variable_declaration;
|
|
return analyze_variable_declaration_raw(g, import, context, node, variable_declaration, false);
|
|
}
|
|
|
|
static TypeTableEntry *analyze_null_literal_expr(CodeGen *g, ImportTableEntry *import,
|
|
BlockContext *block_context, TypeTableEntry *expected_type, AstNode *node)
|
|
{
|
|
assert(node->type == NodeTypeNullLiteral);
|
|
|
|
if (expected_type) {
|
|
assert(expected_type->id == TypeTableEntryIdMaybe);
|
|
|
|
node->data.null_literal.resolved_struct_val_expr.type_entry = expected_type;
|
|
node->data.null_literal.resolved_struct_val_expr.source_node = node;
|
|
block_context->struct_val_expr_alloca_list.append(&node->data.null_literal.resolved_struct_val_expr);
|
|
|
|
return expected_type;
|
|
} else {
|
|
add_node_error(g, node,
|
|
buf_sprintf("unable to determine null type"));
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
}
|
|
|
|
static TypeTableEntry *analyze_number_literal_expr(CodeGen *g, ImportTableEntry *import,
|
|
BlockContext *block_context, TypeTableEntry *expected_type, AstNode *node)
|
|
{
|
|
TypeTableEntry *num_lit_type = g->num_lit_types[node->data.number_literal.kind];
|
|
if (node->data.number_literal.overflow) {
|
|
add_node_error(g, node,
|
|
buf_sprintf("number literal too large to be represented in any type"));
|
|
return g->builtin_types.entry_invalid;
|
|
} else if (expected_type) {
|
|
NumLitCodeGen *codegen_num_lit = get_resolved_num_lit(node);
|
|
assert(!codegen_num_lit->resolved_type);
|
|
TypeTableEntry *after_implicit_cast_resolved_type =
|
|
resolve_type_compatibility(g, block_context, node, expected_type, num_lit_type);
|
|
assert(codegen_num_lit->resolved_type ||
|
|
after_implicit_cast_resolved_type->id == TypeTableEntryIdInvalid);
|
|
return after_implicit_cast_resolved_type;
|
|
} else {
|
|
return num_lit_type;
|
|
}
|
|
}
|
|
|
|
static TypeStructField *find_struct_type_field(TypeTableEntry *type_entry, Buf *name, int *index) {
|
|
assert(type_entry->id == TypeTableEntryIdStruct);
|
|
for (int i = 0; i < type_entry->data.structure.field_count; i += 1) {
|
|
TypeStructField *field = &type_entry->data.structure.fields[i];
|
|
if (buf_eql_buf(field->name, name)) {
|
|
*index = i;
|
|
return field;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static TypeTableEntry *analyze_struct_val_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
TypeTableEntry *expected_type, AstNode *node)
|
|
{
|
|
assert(node->type == NodeTypeStructValueExpr);
|
|
|
|
AstNodeStructValueExpr *struct_val_expr = &node->data.struct_val_expr;
|
|
|
|
TypeTableEntry *type_entry = resolve_type(g, struct_val_expr->type, import, context, false);
|
|
|
|
if (type_entry->id == TypeTableEntryIdInvalid) {
|
|
return g->builtin_types.entry_invalid;
|
|
} else if (type_entry->id != TypeTableEntryIdStruct) {
|
|
add_node_error(g, node,
|
|
buf_sprintf("type '%s' is not a struct", buf_ptr(&type_entry->name)));
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
|
|
node->data.struct_val_expr.codegen.type_entry = type_entry;
|
|
node->data.struct_val_expr.codegen.source_node = node;
|
|
context->struct_val_expr_alloca_list.append(&node->data.struct_val_expr.codegen);
|
|
|
|
int expr_field_count = struct_val_expr->fields.length;
|
|
int actual_field_count = type_entry->data.structure.field_count;
|
|
|
|
int *field_use_counts = allocate<int>(actual_field_count);
|
|
for (int i = 0; i < expr_field_count; i += 1) {
|
|
AstNode *val_field_node = struct_val_expr->fields.at(i);
|
|
assert(val_field_node->type == NodeTypeStructValueField);
|
|
|
|
int field_index;
|
|
TypeStructField *type_field = find_struct_type_field(type_entry,
|
|
&val_field_node->data.struct_val_field.name, &field_index);
|
|
|
|
if (!type_field) {
|
|
add_node_error(g, val_field_node,
|
|
buf_sprintf("no member named '%s' in '%s'",
|
|
buf_ptr(&val_field_node->data.struct_val_field.name), buf_ptr(&type_entry->name)));
|
|
continue;
|
|
}
|
|
|
|
field_use_counts[field_index] += 1;
|
|
if (field_use_counts[field_index] > 1) {
|
|
add_node_error(g, val_field_node, buf_sprintf("duplicate field"));
|
|
continue;
|
|
}
|
|
|
|
val_field_node->data.struct_val_field.type_struct_field = type_field;
|
|
|
|
analyze_expression(g, import, context, type_field->type_entry,
|
|
val_field_node->data.struct_val_field.expr);
|
|
}
|
|
|
|
for (int i = 0; i < actual_field_count; i += 1) {
|
|
if (field_use_counts[i] == 0) {
|
|
add_node_error(g, node,
|
|
buf_sprintf("missing field: '%s'", buf_ptr(type_entry->data.structure.fields[i].name)));
|
|
}
|
|
}
|
|
|
|
return type_entry;
|
|
}
|
|
|
|
static TypeTableEntry *analyze_while_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
TypeTableEntry *expected_type, AstNode *node)
|
|
{
|
|
assert(node->type == NodeTypeWhileExpr);
|
|
|
|
AstNode *condition_node = node->data.while_expr.condition;
|
|
AstNode *while_body_node = node->data.while_expr.body;
|
|
TypeTableEntry *condition_type = analyze_expression(g, import, context,
|
|
g->builtin_types.entry_bool, condition_node);
|
|
|
|
context->next_child_parent_loop_node = node;
|
|
analyze_expression(g, import, context, g->builtin_types.entry_void, while_body_node);
|
|
|
|
|
|
TypeTableEntry *expr_return_type = g->builtin_types.entry_void;
|
|
|
|
if (condition_type->id == TypeTableEntryIdInvalid) {
|
|
expr_return_type = g->builtin_types.entry_invalid;
|
|
} else {
|
|
// if the condition is a simple constant expression and there are no break statements
|
|
// then the return type is unreachable
|
|
AstNodeNumberLiteral number_literal;
|
|
TypeTableEntry *resolved_type = eval_const_expr(g, context, condition_node, &number_literal);
|
|
if (resolved_type->id != TypeTableEntryIdInvalid) {
|
|
assert(resolved_type->id == TypeTableEntryIdBool);
|
|
bool constant_cond_value = number_literal.data.x_uint;
|
|
if (constant_cond_value) {
|
|
node->data.while_expr.condition_always_true = true;
|
|
if (!node->data.while_expr.contains_break) {
|
|
expr_return_type = g->builtin_types.entry_unreachable;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return expr_return_type;
|
|
}
|
|
|
|
static TypeTableEntry *analyze_break_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
TypeTableEntry *expected_type, AstNode *node)
|
|
{
|
|
assert(node->type == NodeTypeBreak);
|
|
|
|
AstNode *loop_node = context->parent_loop_node;
|
|
if (loop_node) {
|
|
assert(loop_node->type == NodeTypeWhileExpr);
|
|
loop_node->data.while_expr.contains_break = true;
|
|
} else {
|
|
add_node_error(g, node, buf_sprintf("'break' expression outside loop"));
|
|
}
|
|
return g->builtin_types.entry_unreachable;
|
|
}
|
|
|
|
static TypeTableEntry *analyze_continue_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
TypeTableEntry *expected_type, AstNode *node)
|
|
{
|
|
if (!context->parent_loop_node) {
|
|
add_node_error(g, node, buf_sprintf("'continue' expression outside loop"));
|
|
}
|
|
return g->builtin_types.entry_unreachable;
|
|
}
|
|
|
|
static TypeTableEntry *analyze_if_then_else(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
TypeTableEntry *expected_type, AstNode *then_block, AstNode *else_node, AstNode *parent_node)
|
|
{
|
|
TypeTableEntry *then_type = analyze_expression(g, import, context, expected_type, then_block);
|
|
|
|
TypeTableEntry *else_type;
|
|
if (else_node) {
|
|
else_type = analyze_expression(g, import, context, expected_type, else_node);
|
|
} else {
|
|
else_type = g->builtin_types.entry_void;
|
|
else_type = resolve_type_compatibility(g, context, parent_node, expected_type, else_type);
|
|
}
|
|
|
|
|
|
if (expected_type) {
|
|
return (then_type->id == TypeTableEntryIdUnreachable) ? else_type : then_type;
|
|
} else {
|
|
return resolve_peer_type_compatibility(g, context, parent_node,
|
|
then_block, else_node,
|
|
then_type, else_type);
|
|
}
|
|
}
|
|
|
|
static TypeTableEntry *analyze_if_bool_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
TypeTableEntry *expected_type, AstNode *node)
|
|
{
|
|
analyze_expression(g, import, context, g->builtin_types.entry_bool, node->data.if_bool_expr.condition);
|
|
|
|
return analyze_if_then_else(g, import, context, expected_type,
|
|
node->data.if_bool_expr.then_block,
|
|
node->data.if_bool_expr.else_node,
|
|
node);
|
|
}
|
|
|
|
static TypeTableEntry *analyze_if_var_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
TypeTableEntry *expected_type, AstNode *node)
|
|
{
|
|
assert(node->type == NodeTypeIfVarExpr);
|
|
|
|
BlockContext *child_context = new_block_context(node, context);
|
|
node->data.if_var_expr.block_context = child_context;
|
|
|
|
analyze_variable_declaration_raw(g, import, child_context, node, &node->data.if_var_expr.var_decl, true);
|
|
|
|
return analyze_if_then_else(g, import, child_context, expected_type,
|
|
node->data.if_var_expr.then_block, node->data.if_var_expr.else_node, node);
|
|
}
|
|
|
|
static TypeTableEntry *analyze_min_max_value(CodeGen *g, AstNode *node, TypeTableEntry *type_entry,
|
|
const char *err_format)
|
|
{
|
|
if (type_entry->id == TypeTableEntryIdInt ||
|
|
type_entry->id == TypeTableEntryIdFloat ||
|
|
type_entry->id == TypeTableEntryIdBool ||
|
|
type_entry->id == TypeTableEntryIdInvalid)
|
|
{
|
|
return type_entry;
|
|
} else {
|
|
add_node_error(g, node,
|
|
buf_sprintf(err_format, buf_ptr(&type_entry->name)));
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
}
|
|
|
|
static TypeTableEntry *analyze_compiler_fn_type(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
TypeTableEntry *expected_type, AstNode *node)
|
|
{
|
|
assert(node->type == NodeTypeCompilerFnType);
|
|
|
|
Buf *name = &node->data.compiler_fn_type.name;
|
|
TypeTableEntry *type_entry = resolve_type(g, node->data.compiler_fn_type.type, import, context, false);
|
|
|
|
if (buf_eql_str(name, "sizeof")) {
|
|
if (type_entry->id == TypeTableEntryIdInvalid) {
|
|
return type_entry;
|
|
} else if (type_entry->id == TypeTableEntryIdUnreachable) {
|
|
add_node_error(g, node,
|
|
buf_sprintf("no size available for type '%s'", buf_ptr(&type_entry->name)));
|
|
return g->builtin_types.entry_invalid;
|
|
} else {
|
|
uint64_t size_in_bytes = type_entry->size_in_bits / 8;
|
|
|
|
TypeTableEntry *num_lit_type = get_number_literal_type_unsigned(g, size_in_bytes);
|
|
TypeTableEntry *resolved_type = resolve_rhs_number_literal(g, nullptr, expected_type, node, num_lit_type);
|
|
return resolved_type ? resolved_type : num_lit_type;
|
|
}
|
|
} else if (buf_eql_str(name, "min_value")) {
|
|
return analyze_min_max_value(g, node, type_entry, "no min value available for type '%s'");
|
|
} else if (buf_eql_str(name, "max_value")) {
|
|
return analyze_min_max_value(g, node, type_entry, "no max value available for type '%s'");
|
|
} else if (buf_eql_str(name, "value_count")) {
|
|
if (type_entry->id == TypeTableEntryIdInvalid) {
|
|
return type_entry;
|
|
} else if (type_entry->id == TypeTableEntryIdEnum) {
|
|
uint64_t value_count = type_entry->data.enumeration.field_count;
|
|
|
|
TypeTableEntry *num_lit_type = get_number_literal_type_unsigned(g, value_count);
|
|
TypeTableEntry *resolved_type = resolve_rhs_number_literal(g, nullptr, expected_type, node, num_lit_type);
|
|
return resolved_type ? resolved_type : num_lit_type;
|
|
|
|
} else {
|
|
add_node_error(g, node,
|
|
buf_sprintf("no value count available for type '%s'", buf_ptr(&type_entry->name)));
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
} else {
|
|
add_node_error(g, node,
|
|
buf_sprintf("invalid compiler function: '%s'", buf_ptr(name)));
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
}
|
|
|
|
static TypeTableEntry *analyze_builtin_fn_call_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
TypeTableEntry *expected_type, AstNode *node)
|
|
{
|
|
assert(node->type == NodeTypeFnCallExpr);
|
|
|
|
AstNode *fn_ref_expr = node->data.fn_call_expr.fn_ref_expr;
|
|
Buf *name = &fn_ref_expr->data.symbol_expr.symbol;
|
|
|
|
auto entry = g->builtin_fn_table.maybe_get(name);
|
|
|
|
if (entry) {
|
|
BuiltinFnEntry *builtin_fn = entry->value;
|
|
int actual_param_count = node->data.fn_call_expr.params.length;
|
|
|
|
node->data.fn_call_expr.builtin_fn = builtin_fn;
|
|
|
|
if (builtin_fn->param_count != actual_param_count) {
|
|
add_node_error(g, node,
|
|
buf_sprintf("expected %d arguments, got %d",
|
|
builtin_fn->param_count, actual_param_count));
|
|
}
|
|
|
|
switch (builtin_fn->id) {
|
|
case BuiltinFnIdInvalid:
|
|
zig_unreachable();
|
|
case BuiltinFnIdArithmeticWithOverflow:
|
|
for (int i = 0; i < actual_param_count; i += 1) {
|
|
AstNode *child = node->data.fn_call_expr.params.at(i);
|
|
TypeTableEntry *expected_param_type = builtin_fn->param_types[i];
|
|
analyze_expression(g, import, context, expected_param_type, child);
|
|
}
|
|
return builtin_fn->return_type;
|
|
case BuiltinFnIdMemcpy:
|
|
{
|
|
AstNode *dest_node = node->data.fn_call_expr.params.at(0);
|
|
AstNode *src_node = node->data.fn_call_expr.params.at(1);
|
|
AstNode *len_node = node->data.fn_call_expr.params.at(2);
|
|
TypeTableEntry *dest_type = analyze_expression(g, import, context, nullptr, dest_node);
|
|
TypeTableEntry *src_type = analyze_expression(g, import, context, nullptr, src_node);
|
|
analyze_expression(g, import, context, builtin_fn->param_types[2], len_node);
|
|
|
|
if (dest_type->id != TypeTableEntryIdInvalid &&
|
|
dest_type->id != TypeTableEntryIdPointer)
|
|
{
|
|
add_node_error(g, dest_node,
|
|
buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&dest_type->name)));
|
|
}
|
|
|
|
if (src_type->id != TypeTableEntryIdInvalid &&
|
|
src_type->id != TypeTableEntryIdPointer)
|
|
{
|
|
add_node_error(g, src_node,
|
|
buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&src_type->name)));
|
|
}
|
|
|
|
if (dest_type->id == TypeTableEntryIdPointer &&
|
|
src_type->id == TypeTableEntryIdPointer)
|
|
{
|
|
uint64_t dest_align_bits = dest_type->data.pointer.child_type->align_in_bits;
|
|
uint64_t src_align_bits = src_type->data.pointer.child_type->align_in_bits;
|
|
if (dest_align_bits != src_align_bits) {
|
|
add_node_error(g, dest_node, buf_sprintf(
|
|
"misaligned memcpy, '%s' has alignment '%" PRIu64 ", '%s' has alignment %" PRIu64,
|
|
buf_ptr(&dest_type->name), dest_align_bits / 8,
|
|
buf_ptr(&src_type->name), src_align_bits / 8));
|
|
}
|
|
}
|
|
|
|
return builtin_fn->return_type;
|
|
}
|
|
case BuiltinFnIdMemset:
|
|
{
|
|
AstNode *dest_node = node->data.fn_call_expr.params.at(0);
|
|
AstNode *char_node = node->data.fn_call_expr.params.at(1);
|
|
AstNode *len_node = node->data.fn_call_expr.params.at(2);
|
|
TypeTableEntry *dest_type = analyze_expression(g, import, context, nullptr, dest_node);
|
|
analyze_expression(g, import, context, builtin_fn->param_types[1], char_node);
|
|
analyze_expression(g, import, context, builtin_fn->param_types[2], len_node);
|
|
|
|
if (dest_type->id != TypeTableEntryIdInvalid &&
|
|
dest_type->id != TypeTableEntryIdPointer)
|
|
{
|
|
add_node_error(g, dest_node,
|
|
buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&dest_type->name)));
|
|
}
|
|
|
|
return builtin_fn->return_type;
|
|
}
|
|
}
|
|
zig_unreachable();
|
|
} else {
|
|
add_node_error(g, node,
|
|
buf_sprintf("invalid builtin function: '%s'", buf_ptr(name)));
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
}
|
|
|
|
static TypeTableEntry *analyze_fn_call_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
TypeTableEntry *expected_type, AstNode *node)
|
|
{
|
|
AstNode *fn_ref_expr = node->data.fn_call_expr.fn_ref_expr;
|
|
TypeTableEntry *struct_type = nullptr;
|
|
HashMap<Buf *, FnTableEntry *, buf_hash, buf_eql_buf> *fn_table = &import->fn_table;
|
|
AstNode *first_param_expr = nullptr;
|
|
Buf *name;
|
|
|
|
if (fn_ref_expr->type == NodeTypeFieldAccessExpr) {
|
|
first_param_expr = fn_ref_expr->data.field_access_expr.struct_expr;
|
|
struct_type = analyze_expression(g, import, context, nullptr, first_param_expr);
|
|
name = &fn_ref_expr->data.field_access_expr.field_name;
|
|
if (struct_type->id == TypeTableEntryIdStruct) {
|
|
fn_table = &struct_type->data.structure.fn_table;
|
|
} else if (struct_type->id == TypeTableEntryIdPointer &&
|
|
struct_type->data.pointer.child_type->id == TypeTableEntryIdStruct)
|
|
{
|
|
fn_table = &struct_type->data.pointer.child_type->data.structure.fn_table;
|
|
} else if (struct_type->id == TypeTableEntryIdInvalid) {
|
|
return struct_type;
|
|
} else if (struct_type->id == TypeTableEntryIdMetaType &&
|
|
struct_type->data.meta_type.child_type->id == TypeTableEntryIdEnum)
|
|
{
|
|
TypeTableEntry *enum_type = struct_type->data.meta_type.child_type;
|
|
Buf *field_name = &fn_ref_expr->data.field_access_expr.field_name;
|
|
int param_count = node->data.fn_call_expr.params.length;
|
|
if (param_count > 1) {
|
|
add_node_error(g, first_executing_node(node->data.fn_call_expr.params.at(1)),
|
|
buf_sprintf("enum values accept only one parameter"));
|
|
return enum_type;
|
|
} else {
|
|
AstNode *value_node;
|
|
if (param_count == 1) {
|
|
value_node = node->data.fn_call_expr.params.at(0);
|
|
} else {
|
|
value_node = nullptr;
|
|
}
|
|
|
|
return analyze_enum_value_expr(g, import, context, fn_ref_expr, value_node,
|
|
enum_type, field_name);
|
|
}
|
|
} else {
|
|
add_node_error(g, fn_ref_expr->data.field_access_expr.struct_expr,
|
|
buf_sprintf("member reference base type not struct or enum"));
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
} else if (fn_ref_expr->type == NodeTypeSymbol) {
|
|
if (node->data.fn_call_expr.is_builtin) {
|
|
return analyze_builtin_fn_call_expr(g, import, context, expected_type, node);
|
|
}
|
|
name = &fn_ref_expr->data.symbol_expr.symbol;
|
|
} else {
|
|
add_node_error(g, node,
|
|
buf_sprintf("function pointers not yet supported"));
|
|
return g->builtin_types.entry_invalid;
|
|
}
|
|
|
|
auto entry = fn_table->maybe_get(name);
|
|
|
|
if (!entry) {
|
|
add_node_error(g, fn_ref_expr,
|
|
buf_sprintf("undefined function: '%s'", buf_ptr(name)));
|
|
// still analyze the parameters, even though we don't know what to expect
|
|
for (int i = 0; i < node->data.fn_call_expr.params.length; i += 1) {
|
|
AstNode *child = node->data.fn_call_expr.params.at(i);
|
|
analyze_expression(g, import, context, nullptr, child);
|
|
}
|
|
|
|
return g->builtin_types.entry_invalid;
|
|
} else {
|
|
FnTableEntry *fn_table_entry = entry->value;
|
|
assert(fn_table_entry->proto_node->type == NodeTypeFnProto);
|
|
AstNodeFnProto *fn_proto = &fn_table_entry->proto_node->data.fn_proto;
|
|
|
|
// count parameters
|
|
int expected_param_count = fn_proto->params.length;
|
|
int actual_param_count = node->data.fn_call_expr.params.length;
|
|
|
|
if (struct_type) {
|
|
actual_param_count += 1;
|
|
}
|
|
|
|
if (fn_proto->is_var_args) {
|
|
if (actual_param_count < expected_param_count) {
|
|
add_node_error(g, node,
|
|
buf_sprintf("expected at least %d arguments, got %d",
|
|
expected_param_count, actual_param_count));
|
|
}
|
|
} else if (expected_param_count != actual_param_count) {
|
|
add_node_error(g, node,
|
|
buf_sprintf("expected %d arguments, got %d",
|
|
expected_param_count, actual_param_count));
|
|
}
|
|
|
|
// analyze each parameter. in the case of a method, we already analyzed the
|
|
// first parameter in order to figure out which struct we were calling a method on.
|
|
for (int i = 0; i < node->data.fn_call_expr.params.length; i += 1) {
|
|
AstNode *child = node->data.fn_call_expr.params.at(i);
|
|
// determine the expected type for each parameter
|
|
TypeTableEntry *expected_param_type = nullptr;
|
|
int fn_proto_i = i + (struct_type ? 1 : 0);
|
|
if (fn_proto_i < fn_proto->params.length) {
|
|
AstNode *param_decl_node = fn_proto->params.at(fn_proto_i);
|
|
assert(param_decl_node->type == NodeTypeParamDecl);
|
|
AstNode *param_type_node = param_decl_node->data.param_decl.type;
|
|
assert(param_type_node->type == NodeTypeType);
|
|
if (param_type_node->data.type.entry) {
|
|
expected_param_type = param_type_node->data.type.entry;
|
|
}
|
|
}
|
|
analyze_expression(g, import, context, expected_param_type, child);
|
|
}
|
|
|
|
return fn_proto->return_type->data.type.entry;
|
|
}
|
|
}
|
|
|
|
static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, BlockContext *context,
|
|
TypeTableEntry *expected_type, AstNode *node)
|
|
{
|
|
TypeTableEntry *return_type = nullptr;
|
|
switch (node->type) {
|
|
case NodeTypeBlock:
|
|
{
|
|
BlockContext *child_context = new_block_context(node, context);
|
|
node->data.block.block_context = child_context;
|
|
return_type = g->builtin_types.entry_void;
|
|
|
|
for (int i = 0; i < node->data.block.statements.length; i += 1) {
|
|
AstNode *child = node->data.block.statements.at(i);
|
|
if (child->type == NodeTypeLabel) {
|
|
LabelTableEntry *label_entry = child->data.label.label_entry;
|
|
assert(label_entry);
|
|
label_entry->entered_from_fallthrough = (return_type->id != TypeTableEntryIdUnreachable);
|
|
return_type = g->builtin_types.entry_void;
|
|
continue;
|
|
}
|
|
if (return_type->id == TypeTableEntryIdUnreachable) {
|
|
if (child->type == NodeTypeVoid) {
|
|
// {unreachable;void;void} is allowed.
|
|
// ignore void statements once we enter unreachable land.
|
|
continue;
|
|
}
|
|
add_node_error(g, first_executing_node(child), buf_sprintf("unreachable code"));
|
|
break;
|
|
}
|
|
bool is_last = (i == node->data.block.statements.length - 1);
|
|
TypeTableEntry *passed_expected_type = is_last ? expected_type : nullptr;
|
|
return_type = analyze_expression(g, import, child_context, passed_expected_type, child);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case NodeTypeReturnExpr:
|
|
{
|
|
if (context->fn_entry) {
|
|
TypeTableEntry *expected_return_type = get_return_type(context);
|
|
TypeTableEntry *actual_return_type;
|
|
if (node->data.return_expr.expr) {
|
|
actual_return_type = analyze_expression(g, import, context, expected_return_type, node->data.return_expr.expr);
|
|
} else {
|
|
actual_return_type = g->builtin_types.entry_void;
|
|
}
|
|
|
|
if (actual_return_type->id == TypeTableEntryIdUnreachable) {
|
|
// "return exit(0)" should just be "exit(0)".
|
|
add_node_error(g, node, buf_sprintf("returning is unreachable"));
|
|
actual_return_type = g->builtin_types.entry_invalid;
|
|
}
|
|
|
|
resolve_type_compatibility(g, context, node, expected_return_type, actual_return_type);
|
|
} else {
|
|
add_node_error(g, node, buf_sprintf("return expression outside function definition"));
|
|
}
|
|
return_type = g->builtin_types.entry_unreachable;
|
|
break;
|
|
}
|
|
case NodeTypeVariableDeclaration:
|
|
analyze_variable_declaration(g, import, context, expected_type, node);
|
|
return_type = g->builtin_types.entry_void;
|
|
break;
|
|
case NodeTypeGoto:
|
|
{
|
|
FnTableEntry *fn_table_entry = get_context_fn_entry(context);
|
|
auto table_entry = fn_table_entry->label_table.maybe_get(&node->data.goto_expr.name);
|
|
if (table_entry) {
|
|
node->data.goto_expr.label_entry = table_entry->value;
|
|
table_entry->value->used = true;
|
|
} else {
|
|
add_node_error(g, node,
|
|
buf_sprintf("use of undeclared label '%s'", buf_ptr(&node->data.goto_expr.name)));
|
|
}
|
|
return_type = g->builtin_types.entry_unreachable;
|
|
break;
|
|
}
|
|
case NodeTypeBreak:
|
|
return_type = analyze_break_expr(g, import, context, expected_type, node);
|
|
break;
|
|
case NodeTypeContinue:
|
|
return_type = analyze_continue_expr(g, import, context, expected_type, node);
|
|
break;
|
|
case NodeTypeAsmExpr:
|
|
{
|
|
node->data.asm_expr.return_count = 0;
|
|
return_type = g->builtin_types.entry_void;
|
|
for (int i = 0; i < node->data.asm_expr.output_list.length; i += 1) {
|
|
AsmOutput *asm_output = node->data.asm_expr.output_list.at(i);
|
|
if (asm_output->return_type) {
|
|
node->data.asm_expr.return_count += 1;
|
|
return_type = resolve_type(g, asm_output->return_type, import, context, false);
|
|
if (node->data.asm_expr.return_count > 1) {
|
|
add_node_error(g, node,
|
|
buf_sprintf("inline assembly allows up to one output value"));
|
|
break;
|
|
}
|
|
} else {
|
|
analyze_variable_name(g, import, context, node, &asm_output->variable_name);
|
|
}
|
|
}
|
|
for (int i = 0; i < node->data.asm_expr.input_list.length; i += 1) {
|
|
AsmInput *asm_input = node->data.asm_expr.input_list.at(i);
|
|
analyze_expression(g, import, context, nullptr, asm_input->expr);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case NodeTypeBinOpExpr:
|
|
return_type = analyze_bin_op_expr(g, import, context, expected_type, node);
|
|
break;
|
|
|
|
case NodeTypeFnCallExpr:
|
|
return_type = analyze_fn_call_expr(g, import, context, expected_type, node);
|
|
break;
|
|
|
|
case NodeTypeArrayAccessExpr:
|
|
// for reading array access; assignment handled elsewhere
|
|
return_type = analyze_array_access_expr(g, import, context, node);
|
|
break;
|
|
case NodeTypeSliceExpr:
|
|
return_type = analyze_slice_expr(g, import, context, node);
|
|
break;
|
|
case NodeTypeFieldAccessExpr:
|
|
return_type = analyze_field_access_expr(g, import, context, node);
|
|
break;
|
|
case NodeTypeNumberLiteral:
|
|
return_type = analyze_number_literal_expr(g, import, context, expected_type, node);
|
|
break;
|
|
case NodeTypeStringLiteral:
|
|
if (node->data.string_literal.c) {
|
|
return_type = g->builtin_types.entry_c_string_literal;
|
|
} else {
|
|
return_type = get_array_type(g, import, g->builtin_types.entry_u8,
|
|
buf_len(&node->data.string_literal.buf));
|
|
}
|
|
break;
|
|
case NodeTypeCharLiteral:
|
|
return_type = g->builtin_types.entry_u8;
|
|
break;
|
|
case NodeTypeUnreachable:
|
|
return_type = g->builtin_types.entry_unreachable;
|
|
break;
|
|
|
|
case NodeTypeVoid:
|
|
return_type = g->builtin_types.entry_void;
|
|
break;
|
|
|
|
case NodeTypeBoolLiteral:
|
|
return_type = g->builtin_types.entry_bool;
|
|
break;
|
|
|
|
case NodeTypeNullLiteral:
|
|
return_type = analyze_null_literal_expr(g, import, context, expected_type, node);
|
|
break;
|
|
|
|
case NodeTypeSymbol:
|
|
{
|
|
return_type = analyze_symbol_expr(g, import, context, expected_type, node);
|
|
break;
|
|
}
|
|
case NodeTypeCastExpr:
|
|
return_type = analyze_cast_expr(g, import, context, expected_type, node);
|
|
break;
|
|
case NodeTypePrefixOpExpr:
|
|
switch (node->data.prefix_op_expr.prefix_op) {
|
|
case PrefixOpInvalid:
|
|
zig_unreachable();
|
|
case PrefixOpBoolNot:
|
|
analyze_expression(g, import, context, g->builtin_types.entry_bool,
|
|
node->data.prefix_op_expr.primary_expr);
|
|
return_type = g->builtin_types.entry_bool;
|
|
break;
|
|
case PrefixOpBinNot:
|
|
{
|
|
AstNode *operand_node = node->data.prefix_op_expr.primary_expr;
|
|
TypeTableEntry *expr_type = analyze_expression(g, import, context, expected_type,
|
|
operand_node);
|
|
if (expr_type->id == TypeTableEntryIdInvalid) {
|
|
return_type = expr_type;
|
|
} else if (expr_type->id == TypeTableEntryIdInt ||
|
|
(expr_type->id == TypeTableEntryIdNumberLiteral &&
|
|
!is_num_lit_float(expr_type->data.num_lit.kind)))
|
|
{
|
|
return_type = expr_type;
|
|
} else {
|
|
add_node_error(g, operand_node, buf_sprintf("invalid binary not type: '%s'",
|
|
buf_ptr(&expr_type->name)));
|
|
return_type = g->builtin_types.entry_invalid;
|
|
}
|
|
break;
|
|
}
|
|
case PrefixOpNegation:
|
|
{
|
|
AstNode *operand_node = node->data.prefix_op_expr.primary_expr;
|
|
TypeTableEntry *expr_type = analyze_expression(g, import, context, expected_type,
|
|
operand_node);
|
|
if (expr_type->id == TypeTableEntryIdInvalid) {
|
|
return_type = expr_type;
|
|
} else if (expr_type->id == TypeTableEntryIdInt &&
|
|
expr_type->data.integral.is_signed)
|
|
{
|
|
return_type = expr_type;
|
|
} else if (expr_type->id == TypeTableEntryIdFloat) {
|
|
return_type = expr_type;
|
|
} else if (expr_type->id == TypeTableEntryIdNumberLiteral) {
|
|
return_type = expr_type;
|
|
} else {
|
|
add_node_error(g, operand_node, buf_sprintf("invalid negation type: '%s'",
|
|
buf_ptr(&expr_type->name)));
|
|
return_type = g->builtin_types.entry_invalid;
|
|
}
|
|
break;
|
|
}
|
|
case PrefixOpAddressOf:
|
|
case PrefixOpConstAddressOf:
|
|
{
|
|
bool is_const = (node->data.prefix_op_expr.prefix_op == PrefixOpConstAddressOf);
|
|
|
|
TypeTableEntry *child_type = analyze_lvalue(g, import, context,
|
|
node->data.prefix_op_expr.primary_expr, LValPurposeAddressOf, is_const);
|
|
|
|
if (child_type->id == TypeTableEntryIdInvalid) {
|
|
return_type = g->builtin_types.entry_invalid;
|
|
break;
|
|
}
|
|
|
|
return_type = get_pointer_to_type(g, child_type, is_const, false);
|
|
break;
|
|
}
|
|
case PrefixOpDereference:
|
|
{
|
|
TypeTableEntry *type_entry = analyze_expression(g, import, context, nullptr,
|
|
node->data.prefix_op_expr.primary_expr);
|
|
if (type_entry->id == TypeTableEntryIdInvalid) {
|
|
return_type = type_entry;
|
|
} else if (type_entry->id == TypeTableEntryIdPointer) {
|
|
return_type = type_entry->data.pointer.child_type;
|
|
} else {
|
|
add_node_error(g, node->data.prefix_op_expr.primary_expr,
|
|
buf_sprintf("indirection requires pointer operand ('%s' invalid)",
|
|
buf_ptr(&type_entry->name)));
|
|
return_type = g->builtin_types.entry_invalid;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case NodeTypeIfBoolExpr:
|
|
return_type = analyze_if_bool_expr(g, import, context, expected_type, node);
|
|
break;
|
|
case NodeTypeIfVarExpr:
|
|
return_type = analyze_if_var_expr(g, import, context, expected_type, node);
|
|
break;
|
|
case NodeTypeWhileExpr:
|
|
return_type = analyze_while_expr(g, import, context, expected_type, node);
|
|
break;
|
|
case NodeTypeStructValueExpr:
|
|
return_type = analyze_struct_val_expr(g, import, context, expected_type, node);
|
|
break;
|
|
case NodeTypeCompilerFnType:
|
|
return_type = analyze_compiler_fn_type(g, import, context, expected_type, node);
|
|
break;
|
|
case NodeTypeDirective:
|
|
case NodeTypeFnDecl:
|
|
case NodeTypeFnProto:
|
|
case NodeTypeParamDecl:
|
|
case NodeTypeType:
|
|
case NodeTypeRoot:
|
|
case NodeTypeRootExportDecl:
|
|
case NodeTypeExternBlock:
|
|
case NodeTypeFnDef:
|
|
case NodeTypeUse:
|
|
case NodeTypeLabel:
|
|
case NodeTypeStructDecl:
|
|
case NodeTypeStructField:
|
|
case NodeTypeStructValueField:
|
|
case NodeTypeCompilerFnExpr:
|
|
zig_unreachable();
|
|
}
|
|
assert(return_type);
|
|
resolve_type_compatibility(g, context, node, expected_type, return_type);
|
|
|
|
get_resolved_expr(node)->type_entry = return_type;
|
|
get_resolved_expr(node)->block_context = context;
|
|
|
|
return return_type;
|
|
}
|
|
|
|
static void analyze_top_level_fn_def(CodeGen *g, ImportTableEntry *import, AstNode *node) {
|
|
assert(node->type == NodeTypeFnDef);
|
|
|
|
AstNode *fn_proto_node = node->data.fn_def.fn_proto;
|
|
assert(fn_proto_node->type == NodeTypeFnProto);
|
|
|
|
if (fn_proto_node->data.fn_proto.skip) {
|
|
// we detected an error with this function definition which prevents us
|
|
// from further analyzing it.
|
|
return;
|
|
}
|
|
|
|
BlockContext *context = new_block_context(node, import->block_context);
|
|
node->data.fn_def.block_context = context;
|
|
|
|
AstNodeFnProto *fn_proto = &fn_proto_node->data.fn_proto;
|
|
bool is_exported = (fn_proto->visib_mod == VisibModExport);
|
|
for (int i = 0; i < fn_proto->params.length; i += 1) {
|
|
AstNode *param_decl_node = fn_proto->params.at(i);
|
|
assert(param_decl_node->type == NodeTypeParamDecl);
|
|
|
|
// define local variables for parameters
|
|
AstNodeParamDecl *param_decl = ¶m_decl_node->data.param_decl;
|
|
assert(param_decl->type->type == NodeTypeType);
|
|
TypeTableEntry *type = param_decl->type->data.type.entry;
|
|
|
|
if (is_exported && type->id == TypeTableEntryIdStruct) {
|
|
add_node_error(g, param_decl_node,
|
|
buf_sprintf("byvalue struct parameters not yet supported on exported functions"));
|
|
}
|
|
|
|
VariableTableEntry *variable_entry = allocate<VariableTableEntry>(1);
|
|
buf_init_from_buf(&variable_entry->name, ¶m_decl->name);
|
|
variable_entry->type = type;
|
|
variable_entry->is_const = true;
|
|
variable_entry->decl_node = param_decl_node;
|
|
variable_entry->arg_index = i;
|
|
|
|
param_decl_node->data.param_decl.variable = variable_entry;
|
|
|
|
VariableTableEntry *existing_entry = find_local_variable(context, &variable_entry->name);
|
|
if (!existing_entry) {
|
|
// unique definition
|
|
context->variable_table.put(&variable_entry->name, variable_entry);
|
|
} else {
|
|
add_node_error(g, node,
|
|
buf_sprintf("redeclaration of parameter '%s'.", buf_ptr(&existing_entry->name)));
|
|
if (existing_entry->type == variable_entry->type) {
|
|
// types agree, so the type is probably good enough for the rest of analysis
|
|
} else {
|
|
// types disagree. don't trust either one of them.
|
|
existing_entry->type = g->builtin_types.entry_invalid;;
|
|
}
|
|
}
|
|
}
|
|
|
|
TypeTableEntry *expected_type = fn_proto->return_type->data.type.entry;
|
|
TypeTableEntry *block_return_type = analyze_expression(g, import, context, expected_type, node->data.fn_def.body);
|
|
|
|
node->data.fn_def.implicit_return_type = block_return_type;
|
|
|
|
{
|
|
FnTableEntry *fn_table_entry = fn_proto_node->data.fn_proto.fn_table_entry;
|
|
auto it = fn_table_entry->label_table.entry_iterator();
|
|
for (;;) {
|
|
auto *entry = it.next();
|
|
if (!entry)
|
|
break;
|
|
|
|
LabelTableEntry *label_entry = entry->value;
|
|
if (!label_entry->used) {
|
|
add_node_error(g, label_entry->label_node,
|
|
buf_sprintf("label '%s' defined but not used",
|
|
buf_ptr(&label_entry->label_node->data.label.name)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void analyze_top_level_decl(CodeGen *g, ImportTableEntry *import, AstNode *node) {
|
|
switch (node->type) {
|
|
case NodeTypeFnDef:
|
|
analyze_top_level_fn_def(g, import, node);
|
|
break;
|
|
case NodeTypeStructDecl:
|
|
{
|
|
for (int i = 0; i < node->data.struct_decl.fns.length; i += 1) {
|
|
AstNode *fn_def_node = node->data.struct_decl.fns.at(i);
|
|
analyze_top_level_fn_def(g, import, fn_def_node);
|
|
}
|
|
break;
|
|
}
|
|
case NodeTypeRootExportDecl:
|
|
case NodeTypeExternBlock:
|
|
case NodeTypeUse:
|
|
case NodeTypeVariableDeclaration:
|
|
// already took care of these
|
|
break;
|
|
case NodeTypeDirective:
|
|
case NodeTypeParamDecl:
|
|
case NodeTypeFnProto:
|
|
case NodeTypeType:
|
|
case NodeTypeFnDecl:
|
|
case NodeTypeReturnExpr:
|
|
case NodeTypeRoot:
|
|
case NodeTypeBlock:
|
|
case NodeTypeBinOpExpr:
|
|
case NodeTypeFnCallExpr:
|
|
case NodeTypeArrayAccessExpr:
|
|
case NodeTypeSliceExpr:
|
|
case NodeTypeNumberLiteral:
|
|
case NodeTypeStringLiteral:
|
|
case NodeTypeCharLiteral:
|
|
case NodeTypeUnreachable:
|
|
case NodeTypeVoid:
|
|
case NodeTypeBoolLiteral:
|
|
case NodeTypeNullLiteral:
|
|
case NodeTypeSymbol:
|
|
case NodeTypeCastExpr:
|
|
case NodeTypePrefixOpExpr:
|
|
case NodeTypeIfBoolExpr:
|
|
case NodeTypeIfVarExpr:
|
|
case NodeTypeWhileExpr:
|
|
case NodeTypeLabel:
|
|
case NodeTypeGoto:
|
|
case NodeTypeBreak:
|
|
case NodeTypeContinue:
|
|
case NodeTypeAsmExpr:
|
|
case NodeTypeFieldAccessExpr:
|
|
case NodeTypeStructField:
|
|
case NodeTypeStructValueExpr:
|
|
case NodeTypeStructValueField:
|
|
case NodeTypeCompilerFnExpr:
|
|
case NodeTypeCompilerFnType:
|
|
zig_unreachable();
|
|
}
|
|
}
|
|
|
|
static void collect_expr_decl_deps(CodeGen *g, ImportTableEntry *import, AstNode *expr_node,
|
|
TopLevelDecl *decl_node)
|
|
{
|
|
switch (expr_node->type) {
|
|
case NodeTypeNumberLiteral:
|
|
case NodeTypeStringLiteral:
|
|
case NodeTypeCharLiteral:
|
|
case NodeTypeVoid:
|
|
case NodeTypeBoolLiteral:
|
|
case NodeTypeNullLiteral:
|
|
case NodeTypeUnreachable:
|
|
case NodeTypeGoto:
|
|
case NodeTypeBreak:
|
|
case NodeTypeContinue:
|
|
// no dependencies on other top level declarations
|
|
break;
|
|
case NodeTypeSymbol:
|
|
decl_node->deps.put(&expr_node->data.symbol_expr.symbol, expr_node);
|
|
break;
|
|
case NodeTypeBinOpExpr:
|
|
collect_expr_decl_deps(g, import, expr_node->data.bin_op_expr.op1, decl_node);
|
|
collect_expr_decl_deps(g, import, expr_node->data.bin_op_expr.op2, decl_node);
|
|
break;
|
|
case NodeTypeReturnExpr:
|
|
collect_expr_decl_deps(g, import, expr_node->data.return_expr.expr, decl_node);
|
|
break;
|
|
case NodeTypeCastExpr:
|
|
collect_expr_decl_deps(g, import, expr_node->data.cast_expr.expr, decl_node);
|
|
collect_type_decl_deps(g, import, expr_node->data.cast_expr.type, decl_node);
|
|
break;
|
|
case NodeTypePrefixOpExpr:
|
|
collect_expr_decl_deps(g, import, expr_node->data.prefix_op_expr.primary_expr, decl_node);
|
|
break;
|
|
case NodeTypeFnCallExpr:
|
|
collect_expr_decl_deps(g, import, expr_node->data.fn_call_expr.fn_ref_expr, decl_node);
|
|
for (int i = 0; i < expr_node->data.fn_call_expr.params.length; i += 1) {
|
|
AstNode *arg_node = expr_node->data.fn_call_expr.params.at(i);
|
|
collect_expr_decl_deps(g, import, arg_node, decl_node);
|
|
}
|
|
break;
|
|
case NodeTypeArrayAccessExpr:
|
|
collect_expr_decl_deps(g, import, expr_node->data.array_access_expr.array_ref_expr, decl_node);
|
|
collect_expr_decl_deps(g, import, expr_node->data.array_access_expr.subscript, decl_node);
|
|
break;
|
|
case NodeTypeSliceExpr:
|
|
collect_expr_decl_deps(g, import, expr_node->data.slice_expr.array_ref_expr, decl_node);
|
|
collect_expr_decl_deps(g, import, expr_node->data.slice_expr.start, decl_node);
|
|
if (expr_node->data.slice_expr.end) {
|
|
collect_expr_decl_deps(g, import, expr_node->data.slice_expr.end, decl_node);
|
|
}
|
|
break;
|
|
case NodeTypeFieldAccessExpr:
|
|
collect_expr_decl_deps(g, import, expr_node->data.field_access_expr.struct_expr, decl_node);
|
|
break;
|
|
case NodeTypeIfBoolExpr:
|
|
collect_expr_decl_deps(g, import, expr_node->data.if_bool_expr.condition, decl_node);
|
|
collect_expr_decl_deps(g, import, expr_node->data.if_bool_expr.then_block, decl_node);
|
|
if (expr_node->data.if_bool_expr.else_node) {
|
|
collect_expr_decl_deps(g, import, expr_node->data.if_bool_expr.else_node, decl_node);
|
|
}
|
|
break;
|
|
case NodeTypeIfVarExpr:
|
|
if (expr_node->data.if_var_expr.var_decl.type) {
|
|
collect_type_decl_deps(g, import, expr_node->data.if_var_expr.var_decl.type, decl_node);
|
|
}
|
|
if (expr_node->data.if_var_expr.var_decl.expr) {
|
|
collect_expr_decl_deps(g, import, expr_node->data.if_var_expr.var_decl.expr, decl_node);
|
|
}
|
|
collect_expr_decl_deps(g, import, expr_node->data.if_var_expr.then_block, decl_node);
|
|
if (expr_node->data.if_bool_expr.else_node) {
|
|
collect_expr_decl_deps(g, import, expr_node->data.if_var_expr.else_node, decl_node);
|
|
}
|
|
break;
|
|
case NodeTypeWhileExpr:
|
|
collect_expr_decl_deps(g, import, expr_node->data.while_expr.condition, decl_node);
|
|
collect_expr_decl_deps(g, import, expr_node->data.while_expr.body, decl_node);
|
|
break;
|
|
case NodeTypeBlock:
|
|
for (int i = 0; i < expr_node->data.block.statements.length; i += 1) {
|
|
AstNode *stmt = expr_node->data.block.statements.at(i);
|
|
collect_expr_decl_deps(g, import, stmt, decl_node);
|
|
}
|
|
break;
|
|
case NodeTypeAsmExpr:
|
|
for (int i = 0; i < expr_node->data.asm_expr.output_list.length; i += 1) {
|
|
AsmOutput *asm_output = expr_node->data.asm_expr.output_list.at(i);
|
|
if (asm_output->return_type) {
|
|
collect_type_decl_deps(g, import, asm_output->return_type, decl_node);
|
|
} else {
|
|
decl_node->deps.put(&asm_output->variable_name, expr_node);
|
|
}
|
|
}
|
|
for (int i = 0; i < expr_node->data.asm_expr.input_list.length; i += 1) {
|
|
AsmInput *asm_input = expr_node->data.asm_expr.input_list.at(i);
|
|
collect_expr_decl_deps(g, import, asm_input->expr, decl_node);
|
|
}
|
|
break;
|
|
case NodeTypeStructValueExpr:
|
|
collect_type_decl_deps(g, import, expr_node->data.struct_val_expr.type, decl_node);
|
|
for (int i = 0; i < expr_node->data.struct_val_expr.fields.length; i += 1) {
|
|
AstNode *field_node = expr_node->data.struct_val_expr.fields.at(i);
|
|
assert(field_node->type == NodeTypeStructValueField);
|
|
collect_expr_decl_deps(g, import, field_node->data.struct_val_field.expr, decl_node);
|
|
}
|
|
break;
|
|
case NodeTypeCompilerFnExpr:
|
|
collect_expr_decl_deps(g, import, expr_node->data.compiler_fn_expr.expr, decl_node);
|
|
break;
|
|
case NodeTypeCompilerFnType:
|
|
collect_type_decl_deps(g, import, expr_node->data.compiler_fn_type.type, decl_node);
|
|
break;
|
|
case NodeTypeRoot:
|
|
case NodeTypeRootExportDecl:
|
|
case NodeTypeFnProto:
|
|
case NodeTypeFnDef:
|
|
case NodeTypeFnDecl:
|
|
case NodeTypeParamDecl:
|
|
case NodeTypeType:
|
|
case NodeTypeExternBlock:
|
|
case NodeTypeDirective:
|
|
case NodeTypeVariableDeclaration:
|
|
case NodeTypeUse:
|
|
case NodeTypeLabel:
|
|
case NodeTypeStructDecl:
|
|
case NodeTypeStructField:
|
|
case NodeTypeStructValueField:
|
|
zig_unreachable();
|
|
}
|
|
}
|
|
|
|
static void collect_type_decl_deps(CodeGen *g, ImportTableEntry *import, AstNode *type_node, TopLevelDecl *decl_node) {
|
|
assert(type_node->type == NodeTypeType);
|
|
switch (type_node->data.type.type) {
|
|
case AstNodeTypeTypePrimitive:
|
|
{
|
|
Buf *name = &type_node->data.type.primitive_name;
|
|
auto table_entry = g->primitive_type_table.maybe_get(name);
|
|
if (!table_entry) {
|
|
table_entry = import->block_context->type_table.maybe_get(name);
|
|
}
|
|
if (!table_entry) {
|
|
decl_node->deps.put(name, type_node);
|
|
}
|
|
break;
|
|
}
|
|
case AstNodeTypeTypePointer:
|
|
collect_type_decl_deps(g, import, type_node->data.type.child_type, decl_node);
|
|
break;
|
|
case AstNodeTypeTypeArray:
|
|
collect_type_decl_deps(g, import, type_node->data.type.child_type, decl_node);
|
|
if (type_node->data.type.array_size) {
|
|
collect_expr_decl_deps(g, import, type_node->data.type.array_size, decl_node);
|
|
}
|
|
break;
|
|
case AstNodeTypeTypeMaybe:
|
|
collect_type_decl_deps(g, import, type_node->data.type.child_type, decl_node);
|
|
break;
|
|
case AstNodeTypeTypeCompilerExpr:
|
|
collect_expr_decl_deps(g, import, type_node->data.type.compiler_expr, decl_node);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static TypeTableEntryId container_to_type(ContainerKind kind) {
|
|
switch (kind) {
|
|
case ContainerKindStruct:
|
|
return TypeTableEntryIdStruct;
|
|
case ContainerKindEnum:
|
|
return TypeTableEntryIdEnum;
|
|
}
|
|
zig_unreachable();
|
|
}
|
|
|
|
static void detect_top_level_decl_deps(CodeGen *g, ImportTableEntry *import, AstNode *node) {
|
|
switch (node->type) {
|
|
case NodeTypeStructDecl:
|
|
{
|
|
Buf *name = &node->data.struct_decl.name;
|
|
auto table_entry = g->primitive_type_table.maybe_get(name);
|
|
if (!table_entry) {
|
|
table_entry = import->block_context->type_table.maybe_get(name);
|
|
}
|
|
if (table_entry) {
|
|
node->data.struct_decl.type_entry = table_entry->value;
|
|
add_node_error(g, node,
|
|
buf_sprintf("redefinition of '%s'", buf_ptr(name)));
|
|
} else {
|
|
TypeTableEntryId type_id = container_to_type(node->data.struct_decl.kind);
|
|
TypeTableEntry *entry = new_type_table_entry(type_id);
|
|
switch (node->data.struct_decl.kind) {
|
|
case ContainerKindStruct:
|
|
entry->data.structure.decl_node = node;
|
|
break;
|
|
case ContainerKindEnum:
|
|
entry->data.enumeration.decl_node = node;
|
|
break;
|
|
}
|
|
|
|
entry->type_ref = LLVMStructCreateNamed(LLVMGetGlobalContext(), buf_ptr(name));
|
|
entry->di_type = LLVMZigCreateReplaceableCompositeType(g->dbuilder,
|
|
LLVMZigTag_DW_structure_type(), buf_ptr(name),
|
|
LLVMZigFileToScope(import->di_file), import->di_file, node->line + 1);
|
|
|
|
buf_init_from_buf(&entry->name, name);
|
|
// put off adding the debug type until we do the full struct body
|
|
// this type is incomplete until we do another pass
|
|
import->block_context->type_table.put(&entry->name, entry);
|
|
node->data.struct_decl.type_entry = entry;
|
|
|
|
bool is_pub = (node->data.struct_decl.visib_mod != VisibModPrivate);
|
|
if (is_pub) {
|
|
for (int i = 0; i < import->importers.length; i += 1) {
|
|
ImporterInfo importer = import->importers.at(i);
|
|
auto table_entry = importer.import->block_context->type_table.maybe_get(&entry->name);
|
|
if (table_entry) {
|
|
add_node_error(g, importer.source_node,
|
|
buf_sprintf("import of type '%s' overrides existing definition",
|
|
buf_ptr(&entry->name)));
|
|
} else {
|
|
importer.import->block_context->type_table.put(&entry->name, entry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// determine which other top level declarations this struct depends on.
|
|
TopLevelDecl *decl_node = &node->data.struct_decl.top_level_decl;
|
|
decl_node->deps.init(1);
|
|
for (int i = 0; i < node->data.struct_decl.fields.length; i += 1) {
|
|
AstNode *field_node = node->data.struct_decl.fields.at(i);
|
|
AstNode *type_node = field_node->data.struct_field.type;
|
|
collect_type_decl_deps(g, import, type_node, decl_node);
|
|
}
|
|
decl_node->name = name;
|
|
decl_node->import = import;
|
|
if (decl_node->deps.size() > 0) {
|
|
g->unresolved_top_level_decls.put(name, node);
|
|
} else {
|
|
resolve_top_level_decl(g, import, node);
|
|
}
|
|
|
|
// handle the member function definitions independently
|
|
for (int i = 0; i < node->data.struct_decl.fns.length; i += 1) {
|
|
AstNode *fn_def_node = node->data.struct_decl.fns.at(i);
|
|
AstNode *fn_proto_node = fn_def_node->data.fn_def.fn_proto;
|
|
fn_proto_node->data.fn_proto.struct_node = node;
|
|
detect_top_level_decl_deps(g, import, fn_def_node);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case NodeTypeExternBlock:
|
|
for (int fn_decl_i = 0; fn_decl_i < node->data.extern_block.fn_decls.length; fn_decl_i += 1) {
|
|
AstNode *fn_decl = node->data.extern_block.fn_decls.at(fn_decl_i);
|
|
assert(fn_decl->type == NodeTypeFnDecl);
|
|
AstNode *fn_proto = fn_decl->data.fn_decl.fn_proto;
|
|
fn_proto->data.fn_proto.extern_node = node;
|
|
detect_top_level_decl_deps(g, import, fn_proto);
|
|
}
|
|
resolve_top_level_decl(g, import, node);
|
|
break;
|
|
case NodeTypeFnDef:
|
|
node->data.fn_def.fn_proto->data.fn_proto.fn_def_node = node;
|
|
detect_top_level_decl_deps(g, import, node->data.fn_def.fn_proto);
|
|
break;
|
|
case NodeTypeVariableDeclaration:
|
|
{
|
|
// determine which other top level declarations this variable declaration depends on.
|
|
TopLevelDecl *decl_node = &node->data.variable_declaration.top_level_decl;
|
|
decl_node->deps.init(1);
|
|
if (node->data.variable_declaration.type) {
|
|
collect_type_decl_deps(g, import, node->data.variable_declaration.type, decl_node);
|
|
}
|
|
if (node->data.variable_declaration.expr) {
|
|
collect_expr_decl_deps(g, import, node->data.variable_declaration.expr, decl_node);
|
|
}
|
|
Buf *name = &node->data.variable_declaration.symbol;
|
|
decl_node->name = name;
|
|
decl_node->import = import;
|
|
if (decl_node->deps.size() > 0) {
|
|
g->unresolved_top_level_decls.put(name, node);
|
|
} else {
|
|
resolve_top_level_decl(g, import, node);
|
|
}
|
|
break;
|
|
}
|
|
case NodeTypeFnProto:
|
|
{
|
|
// determine which other top level declarations this function prototype depends on.
|
|
TopLevelDecl *decl_node = &node->data.fn_proto.top_level_decl;
|
|
decl_node->deps.init(1);
|
|
for (int i = 0; i < node->data.fn_proto.params.length; i += 1) {
|
|
AstNode *param_node = node->data.fn_proto.params.at(i);
|
|
assert(param_node->type == NodeTypeParamDecl);
|
|
collect_type_decl_deps(g, import, param_node->data.param_decl.type, decl_node);
|
|
}
|
|
|
|
Buf *name = &node->data.fn_proto.name;
|
|
decl_node->name = name;
|
|
decl_node->import = import;
|
|
if (decl_node->deps.size() > 0) {
|
|
g->unresolved_top_level_decls.put(name, node);
|
|
} else {
|
|
resolve_top_level_decl(g, import, node);
|
|
}
|
|
break;
|
|
}
|
|
case NodeTypeRootExportDecl:
|
|
resolve_top_level_decl(g, import, node);
|
|
break;
|
|
case NodeTypeUse:
|
|
// already taken care of
|
|
break;
|
|
case NodeTypeDirective:
|
|
case NodeTypeParamDecl:
|
|
case NodeTypeType:
|
|
case NodeTypeFnDecl:
|
|
case NodeTypeReturnExpr:
|
|
case NodeTypeRoot:
|
|
case NodeTypeBlock:
|
|
case NodeTypeBinOpExpr:
|
|
case NodeTypeFnCallExpr:
|
|
case NodeTypeArrayAccessExpr:
|
|
case NodeTypeSliceExpr:
|
|
case NodeTypeNumberLiteral:
|
|
case NodeTypeStringLiteral:
|
|
case NodeTypeCharLiteral:
|
|
case NodeTypeUnreachable:
|
|
case NodeTypeVoid:
|
|
case NodeTypeBoolLiteral:
|
|
case NodeTypeNullLiteral:
|
|
case NodeTypeSymbol:
|
|
case NodeTypeCastExpr:
|
|
case NodeTypePrefixOpExpr:
|
|
case NodeTypeIfBoolExpr:
|
|
case NodeTypeIfVarExpr:
|
|
case NodeTypeWhileExpr:
|
|
case NodeTypeLabel:
|
|
case NodeTypeGoto:
|
|
case NodeTypeBreak:
|
|
case NodeTypeContinue:
|
|
case NodeTypeAsmExpr:
|
|
case NodeTypeFieldAccessExpr:
|
|
case NodeTypeStructField:
|
|
case NodeTypeStructValueExpr:
|
|
case NodeTypeStructValueField:
|
|
case NodeTypeCompilerFnExpr:
|
|
case NodeTypeCompilerFnType:
|
|
zig_unreachable();
|
|
}
|
|
}
|
|
|
|
static void recursive_resolve_decl(CodeGen *g, ImportTableEntry *import, AstNode *node) {
|
|
auto it = get_resolved_top_level_decl(node)->deps.entry_iterator();
|
|
for (;;) {
|
|
auto *entry = it.next();
|
|
if (!entry)
|
|
break;
|
|
|
|
auto unresolved_entry = g->unresolved_top_level_decls.maybe_get(entry->key);
|
|
if (!unresolved_entry) {
|
|
continue;
|
|
}
|
|
|
|
AstNode *child_node = unresolved_entry->value;
|
|
|
|
if (get_resolved_top_level_decl(child_node)->in_current_deps) {
|
|
// dependency loop. we'll let the fact that it's not in the respective
|
|
// table cause an error in resolve_top_level_decl.
|
|
continue;
|
|
}
|
|
|
|
// set temporary flag
|
|
TopLevelDecl *top_level_decl = get_resolved_top_level_decl(child_node);
|
|
top_level_decl->in_current_deps = true;
|
|
|
|
recursive_resolve_decl(g, top_level_decl->import, child_node);
|
|
|
|
// unset temporary flag
|
|
top_level_decl->in_current_deps = false;
|
|
}
|
|
|
|
resolve_top_level_decl(g, import, node);
|
|
g->unresolved_top_level_decls.remove(get_resolved_top_level_decl(node)->name);
|
|
}
|
|
|
|
static void resolve_top_level_declarations_root(CodeGen *g, ImportTableEntry *import, AstNode *node) {
|
|
assert(node->type == NodeTypeRoot);
|
|
|
|
while (g->unresolved_top_level_decls.size() > 0) {
|
|
// for the sake of determinism, find the element with the lowest
|
|
// insert index and resolve that one.
|
|
AstNode *decl_node = nullptr;
|
|
auto it = g->unresolved_top_level_decls.entry_iterator();
|
|
for (;;) {
|
|
auto *entry = it.next();
|
|
if (!entry)
|
|
break;
|
|
|
|
AstNode *this_node = entry->value;
|
|
if (!decl_node || this_node->create_index < decl_node->create_index) {
|
|
decl_node = this_node;
|
|
}
|
|
|
|
}
|
|
// set temporary flag
|
|
TopLevelDecl *top_level_decl = get_resolved_top_level_decl(decl_node);
|
|
top_level_decl->in_current_deps = true;
|
|
|
|
recursive_resolve_decl(g, top_level_decl->import, decl_node);
|
|
|
|
// unset temporary flag
|
|
top_level_decl->in_current_deps = false;
|
|
}
|
|
}
|
|
|
|
static void analyze_top_level_decls_root(CodeGen *g, ImportTableEntry *import, AstNode *node) {
|
|
assert(node->type == NodeTypeRoot);
|
|
|
|
for (int i = 0; i < node->data.root.top_level_decls.length; i += 1) {
|
|
AstNode *child = node->data.root.top_level_decls.at(i);
|
|
analyze_top_level_decl(g, import, child);
|
|
}
|
|
}
|
|
|
|
void semantic_analyze(CodeGen *g) {
|
|
{
|
|
auto it = g->import_table.entry_iterator();
|
|
for (;;) {
|
|
auto *entry = it.next();
|
|
if (!entry)
|
|
break;
|
|
|
|
ImportTableEntry *import = entry->value;
|
|
|
|
for (int i = 0; i < import->root->data.root.top_level_decls.length; i += 1) {
|
|
AstNode *child = import->root->data.root.top_level_decls.at(i);
|
|
if (child->type == NodeTypeUse) {
|
|
for (int i = 0; i < child->data.use.directives->length; i += 1) {
|
|
AstNode *directive_node = child->data.use.directives->at(i);
|
|
Buf *name = &directive_node->data.directive.name;
|
|
add_node_error(g, directive_node,
|
|
buf_sprintf("invalid directive: '%s'", buf_ptr(name)));
|
|
}
|
|
|
|
ImportTableEntry *target_import = child->data.use.import;
|
|
assert(target_import);
|
|
|
|
target_import->importers.append({import, child});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
{
|
|
auto it = g->import_table.entry_iterator();
|
|
for (;;) {
|
|
auto *entry = it.next();
|
|
if (!entry)
|
|
break;
|
|
|
|
ImportTableEntry *import = entry->value;
|
|
|
|
for (int i = 0; i < import->root->data.root.top_level_decls.length; i += 1) {
|
|
AstNode *child = import->root->data.root.top_level_decls.at(i);
|
|
detect_top_level_decl_deps(g, import, child);
|
|
}
|
|
}
|
|
}
|
|
{
|
|
auto it = g->import_table.entry_iterator();
|
|
for (;;) {
|
|
auto *entry = it.next();
|
|
if (!entry)
|
|
break;
|
|
|
|
ImportTableEntry *import = entry->value;
|
|
resolve_top_level_declarations_root(g, import, import->root);
|
|
}
|
|
}
|
|
{
|
|
auto it = g->import_table.entry_iterator();
|
|
for (;;) {
|
|
auto *entry = it.next();
|
|
if (!entry)
|
|
break;
|
|
|
|
ImportTableEntry *import = entry->value;
|
|
analyze_top_level_decls_root(g, import, import->root);
|
|
}
|
|
}
|
|
|
|
if (!g->root_out_name) {
|
|
add_node_error(g, g->root_import->root,
|
|
buf_sprintf("missing export declaration and output name not provided"));
|
|
} else if (g->out_type == OutTypeUnknown) {
|
|
add_node_error(g, g->root_import->root,
|
|
buf_sprintf("missing export declaration and export type not provided"));
|
|
}
|
|
}
|
|
|
|
Expr *get_resolved_expr(AstNode *node) {
|
|
switch (node->type) {
|
|
case NodeTypeReturnExpr:
|
|
return &node->data.return_expr.resolved_expr;
|
|
case NodeTypeBinOpExpr:
|
|
return &node->data.bin_op_expr.resolved_expr;
|
|
case NodeTypeCastExpr:
|
|
return &node->data.cast_expr.resolved_expr;
|
|
case NodeTypePrefixOpExpr:
|
|
return &node->data.prefix_op_expr.resolved_expr;
|
|
case NodeTypeFnCallExpr:
|
|
return &node->data.fn_call_expr.resolved_expr;
|
|
case NodeTypeArrayAccessExpr:
|
|
return &node->data.array_access_expr.resolved_expr;
|
|
case NodeTypeSliceExpr:
|
|
return &node->data.slice_expr.resolved_expr;
|
|
case NodeTypeFieldAccessExpr:
|
|
return &node->data.field_access_expr.resolved_expr;
|
|
case NodeTypeIfBoolExpr:
|
|
return &node->data.if_bool_expr.resolved_expr;
|
|
case NodeTypeIfVarExpr:
|
|
return &node->data.if_var_expr.resolved_expr;
|
|
case NodeTypeWhileExpr:
|
|
return &node->data.while_expr.resolved_expr;
|
|
case NodeTypeAsmExpr:
|
|
return &node->data.asm_expr.resolved_expr;
|
|
case NodeTypeStructValueExpr:
|
|
return &node->data.struct_val_expr.resolved_expr;
|
|
case NodeTypeNumberLiteral:
|
|
return &node->data.number_literal.resolved_expr;
|
|
case NodeTypeStringLiteral:
|
|
return &node->data.string_literal.resolved_expr;
|
|
case NodeTypeBlock:
|
|
return &node->data.block.resolved_expr;
|
|
case NodeTypeVoid:
|
|
return &node->data.void_expr.resolved_expr;
|
|
case NodeTypeUnreachable:
|
|
return &node->data.unreachable_expr.resolved_expr;
|
|
case NodeTypeSymbol:
|
|
return &node->data.symbol_expr.resolved_expr;
|
|
case NodeTypeVariableDeclaration:
|
|
return &node->data.variable_declaration.resolved_expr;
|
|
case NodeTypeCharLiteral:
|
|
return &node->data.char_literal.resolved_expr;
|
|
case NodeTypeBoolLiteral:
|
|
return &node->data.bool_literal.resolved_expr;
|
|
case NodeTypeNullLiteral:
|
|
return &node->data.null_literal.resolved_expr;
|
|
case NodeTypeGoto:
|
|
return &node->data.goto_expr.resolved_expr;
|
|
case NodeTypeBreak:
|
|
return &node->data.break_expr.resolved_expr;
|
|
case NodeTypeContinue:
|
|
return &node->data.continue_expr.resolved_expr;
|
|
case NodeTypeCompilerFnExpr:
|
|
return &node->data.compiler_fn_expr.resolved_expr;
|
|
case NodeTypeCompilerFnType:
|
|
return &node->data.compiler_fn_type.resolved_expr;
|
|
case NodeTypeLabel:
|
|
return &node->data.label.resolved_expr;
|
|
case NodeTypeRoot:
|
|
case NodeTypeRootExportDecl:
|
|
case NodeTypeFnProto:
|
|
case NodeTypeFnDef:
|
|
case NodeTypeFnDecl:
|
|
case NodeTypeParamDecl:
|
|
case NodeTypeType:
|
|
case NodeTypeExternBlock:
|
|
case NodeTypeDirective:
|
|
case NodeTypeUse:
|
|
case NodeTypeStructDecl:
|
|
case NodeTypeStructField:
|
|
case NodeTypeStructValueField:
|
|
zig_unreachable();
|
|
}
|
|
}
|
|
|
|
NumLitCodeGen *get_resolved_num_lit(AstNode *node) {
|
|
switch (node->type) {
|
|
case NodeTypeNumberLiteral:
|
|
return &node->data.number_literal.codegen;
|
|
case NodeTypeCompilerFnType:
|
|
return &node->data.compiler_fn_type.resolved_num_lit;
|
|
case NodeTypeReturnExpr:
|
|
case NodeTypeBinOpExpr:
|
|
case NodeTypeCastExpr:
|
|
case NodeTypePrefixOpExpr:
|
|
case NodeTypeFnCallExpr:
|
|
case NodeTypeArrayAccessExpr:
|
|
case NodeTypeSliceExpr:
|
|
case NodeTypeFieldAccessExpr:
|
|
case NodeTypeIfBoolExpr:
|
|
case NodeTypeIfVarExpr:
|
|
case NodeTypeWhileExpr:
|
|
case NodeTypeAsmExpr:
|
|
case NodeTypeStructValueExpr:
|
|
case NodeTypeRoot:
|
|
case NodeTypeRootExportDecl:
|
|
case NodeTypeFnProto:
|
|
case NodeTypeFnDef:
|
|
case NodeTypeFnDecl:
|
|
case NodeTypeParamDecl:
|
|
case NodeTypeType:
|
|
case NodeTypeBlock:
|
|
case NodeTypeExternBlock:
|
|
case NodeTypeDirective:
|
|
case NodeTypeVariableDeclaration:
|
|
case NodeTypeStringLiteral:
|
|
case NodeTypeCharLiteral:
|
|
case NodeTypeUnreachable:
|
|
case NodeTypeSymbol:
|
|
case NodeTypeUse:
|
|
case NodeTypeVoid:
|
|
case NodeTypeBoolLiteral:
|
|
case NodeTypeNullLiteral:
|
|
case NodeTypeLabel:
|
|
case NodeTypeGoto:
|
|
case NodeTypeBreak:
|
|
case NodeTypeContinue:
|
|
case NodeTypeStructDecl:
|
|
case NodeTypeStructField:
|
|
case NodeTypeStructValueField:
|
|
case NodeTypeCompilerFnExpr:
|
|
zig_unreachable();
|
|
}
|
|
}
|
|
|
|
TopLevelDecl *get_resolved_top_level_decl(AstNode *node) {
|
|
switch (node->type) {
|
|
case NodeTypeVariableDeclaration:
|
|
return &node->data.variable_declaration.top_level_decl;
|
|
case NodeTypeFnProto:
|
|
return &node->data.fn_proto.top_level_decl;
|
|
case NodeTypeStructDecl:
|
|
return &node->data.struct_decl.top_level_decl;
|
|
case NodeTypeNumberLiteral:
|
|
case NodeTypeReturnExpr:
|
|
case NodeTypeBinOpExpr:
|
|
case NodeTypeCastExpr:
|
|
case NodeTypePrefixOpExpr:
|
|
case NodeTypeFnCallExpr:
|
|
case NodeTypeArrayAccessExpr:
|
|
case NodeTypeSliceExpr:
|
|
case NodeTypeFieldAccessExpr:
|
|
case NodeTypeIfBoolExpr:
|
|
case NodeTypeIfVarExpr:
|
|
case NodeTypeWhileExpr:
|
|
case NodeTypeAsmExpr:
|
|
case NodeTypeStructValueExpr:
|
|
case NodeTypeRoot:
|
|
case NodeTypeRootExportDecl:
|
|
case NodeTypeFnDef:
|
|
case NodeTypeFnDecl:
|
|
case NodeTypeParamDecl:
|
|
case NodeTypeType:
|
|
case NodeTypeBlock:
|
|
case NodeTypeExternBlock:
|
|
case NodeTypeDirective:
|
|
case NodeTypeStringLiteral:
|
|
case NodeTypeCharLiteral:
|
|
case NodeTypeUnreachable:
|
|
case NodeTypeSymbol:
|
|
case NodeTypeUse:
|
|
case NodeTypeVoid:
|
|
case NodeTypeBoolLiteral:
|
|
case NodeTypeNullLiteral:
|
|
case NodeTypeLabel:
|
|
case NodeTypeGoto:
|
|
case NodeTypeBreak:
|
|
case NodeTypeContinue:
|
|
case NodeTypeStructField:
|
|
case NodeTypeStructValueField:
|
|
case NodeTypeCompilerFnExpr:
|
|
case NodeTypeCompilerFnType:
|
|
zig_unreachable();
|
|
}
|
|
}
|