// Copyright (c) 2019-2024 Christoffer Lerno. All rights reserved. // Use of this source code is governed by the GNU LGPLv3.0 license // a copy of which can be found in the LICENSE file. #include "sema_internal.h" #define EXPORTED_USER_DEFINED_TYPES (ATTR_ENUM | ATTR_UNION | ATTR_STRUCT | ATTR_FAULT) #define CALLABLE_TYPE (ATTR_FUNC | ATTR_INTERFACE_METHOD | ATTR_MACRO) #define USER_DEFINED_TYPES EXPORTED_USER_DEFINED_TYPES | ATTR_BITSTRUCT | ATTR_DISTINCT static inline bool sema_analyse_func_macro(SemaContext *context, Decl *decl, AttributeDomain domain, bool *erase_decl); static inline bool sema_analyse_func(SemaContext *context, Decl *decl, bool *erase_decl); static inline bool sema_analyse_macro(SemaContext *context, Decl *decl, bool *erase_decl); static inline bool sema_analyse_signature(SemaContext *context, Signature *sig, TypeInfoId type_parent, bool is_export); static inline bool sema_analyse_main_function(SemaContext *context, Decl *decl); static inline bool sema_check_param_uniqueness_and_type(SemaContext *context, Decl **decls, Decl *current, unsigned current_index, unsigned count); static inline bool sema_analyse_method(SemaContext *context, Decl *decl); static inline bool sema_is_valid_method_param(SemaContext *context, Decl *param, Type *parent_type, bool is_dynamic); static inline bool sema_analyse_macro_method(SemaContext *context, Decl *decl); static inline bool unit_add_base_extension_method(SemaContext *context, CompilationUnit *unit, Type *parent_type, Decl *method); static inline bool unit_add_method(SemaContext *context, Type *parent_type, Decl *method); static bool sema_analyse_operator_common(SemaContext *context, Decl *method, TypeInfo **rtype_ptr, Decl ***params_ptr, uint32_t parameters); static inline Decl *operator_in_module(SemaContext *c, Module *module, OperatorOverload operator_overload); static inline bool sema_analyse_operator_element_at(SemaContext *context, Decl *method); static inline bool sema_analyse_operator_element_set(SemaContext *context, Decl *method); static inline bool sema_analyse_operator_len(Decl *method, SemaContext *context); static bool sema_check_operator_method_validity(SemaContext *context, Decl *method); static inline const char *method_name_by_decl(Decl *method_like); static bool sema_analyse_struct_union(SemaContext *context, Decl *decl, bool *erase_decl); static bool sema_analyse_bitstruct(SemaContext *context, Decl *decl, bool *erase_decl); static bool sema_analyse_union_members(SemaContext *context, Decl *decl); static bool sema_analyse_struct_members(SemaContext *context, Decl *decl); static inline bool sema_analyse_struct_member(SemaContext *context, Decl *parent, Decl *decl, bool *erase_decl); static inline bool sema_analyse_bitstruct_member(SemaContext *context, Decl *parent, Decl *member, unsigned index, bool allow_overlap, bool *erase_decl); static inline bool sema_analyse_doc_header(SemaContext *context, AstId doc, Decl **params, Decl **extra_params, bool *pure_ref); static const char *attribute_domain_to_string(AttributeDomain domain); static bool sema_analyse_attribute(SemaContext *context, Decl *decl, Attr *attr, AttributeDomain domain, bool *erase_decl); static bool sema_analyse_attributes_inner(SemaContext *context, Decl *decl, Attr **attrs, AttributeDomain domain, Decl *top, bool *erase_decl); static bool sema_analyse_attributes(SemaContext *context, Decl *decl, Attr **attrs, AttributeDomain domain, bool *erase_decl); static bool sema_analyse_attributes_for_var(SemaContext *context, Decl *decl, bool *erase_decl); static bool sema_check_section(SemaContext *context, Attr *attr); static inline bool sema_analyse_attribute_decl(SemaContext *context, SemaContext *c, Decl *decl, bool *erase_decl); static inline bool sema_analyse_typedef(SemaContext *context, Decl *decl, bool *erase_decl); static bool sema_analyse_decl_type(SemaContext *context, Type *type, SourceSpan span); static inline bool sema_analyse_define(SemaContext *c, Decl *decl, bool *erase_decl); static inline bool sema_analyse_distinct(SemaContext *context, Decl *decl, bool *erase_decl); static CompilationUnit *unit_copy(Module *module, CompilationUnit *unit); static bool sema_analyse_parameterized_define(SemaContext *c, Decl *decl); static Module *module_instantiate_generic(SemaContext *context, Module *module, Path *path, Expr **params, SourceSpan from); static inline bool sema_analyse_enum_param(SemaContext *context, Decl *param); static inline bool sema_analyse_enum(SemaContext *context, Decl *decl, bool *erase_decl); static inline bool sema_analyse_error(SemaContext *context, Decl *decl, bool *erase_decl); static bool sema_check_section(SemaContext *context, Attr *attr) { Expr *expr = attr->exprs[0]; const char *section_string = expr->const_expr.bytes.ptr; // No restrictions except for MACH-O if (platform_target.object_format != OBJ_FORMAT_MACHO) { return true; } scratch_buffer_clear(); StringSlice slice = slice_from_string(section_string); StringSlice segment = slice_next_token(&slice, ','); StringSlice section = slice_next_token(&slice, ','); StringSlice attrs = slice_next_token(&slice, ','); StringSlice stub_size_str = slice_next_token(&slice, ','); (void)attrs; (void)stub_size_str; if (slice.len) RETURN_SEMA_ERROR(expr, "Too many parts to the Mach-o section description."); slice_trim(&segment); if (!segment.len) RETURN_SEMA_ERROR(expr, "The segment is missing, did you type it correctly?"); slice_trim(§ion); if (!section.len) RETURN_SEMA_ERROR(expr, "Mach-o requires 'segment,section' as the format, did you type it correctly?"); if (section.len > 16) RETURN_SEMA_ERROR(expr, "Mach-o requires the section to be at the most 16 characters, can you shorten it?"); REMINDER("Improve section type checking for Mach-o like Clang has."); return true; } /** * Check parameter name uniqueness and that the type is not void. */ static inline bool sema_check_param_uniqueness_and_type(SemaContext *context, Decl **decls, Decl *current, unsigned current_index, unsigned count) { // We may have `void` arguments. They are not allowed in C3. Theoretically they could // be permitted, but it would complicate semantics in general. if (current->type && type_flatten(current->type) == type_void) { if (count == 1 && !current->name && current->var.kind == VARDECL_PARAM) { RETURN_SEMA_ERROR(current, "C-style 'foo(void)' style argument declarations are not valid, please remove 'void'."); } // Some languages would allow this, because it is a way to do overloading RETURN_SEMA_ERROR(current, "Parameters may not be of type 'void'."); } const char *name = current->name; // There is no need to do a check if it is anonymous. if (!name) return true; // Check for a duplicate name, this algorithm is O(n^2), // but is fine as parameters are typically few, and doing something like // a hash map would be more expensive to set up. for (int i = 0; i < current_index; i++) { if (decls[i] && name == decls[i]->name) { SEMA_ERROR(current, "Duplicate parameter name '%s'.", name); SEMA_NOTE(decls[i], "Previous use of the name was here."); decl_poison(current); return false; } } return true; } /** * Look at all the interface declarations, optionally type check the interfaces completely if needed. */ static inline bool sema_resolve_implemented_interfaces(SemaContext *context, Decl *decl, bool deep) { TypeInfo **interfaces = decl->interfaces; unsigned count = vec_size(interfaces); // No interfaces? Then we're done. This is the most common case. if (!count) return true; for (unsigned i = 0; i < count; i++) { TypeInfo *inf_info = interfaces[i]; // Resolve the name if (!sema_resolve_type_info(context, inf_info, RESOLVE_TYPE_DEFAULT)) return false; Type *inf_type = inf_info->type->canonical; if (inf_type->type_kind != TYPE_INTERFACE) { RETURN_SEMA_ERROR(inf_info, "Expected an interface name, but %s is not an interface.", type_quoted_error_string(inf_type)); } // We don't permit duplicates as this would affect the implementation ordering. for (unsigned j = 0; j < i; j++) { if (interfaces[j]->type->canonical == inf_type) { RETURN_SEMA_ERROR(inf_info, "The interface '%s' was included more than once, " "this is not allowed, so please remove the duplicates.", inf_type->name); } } // If this is a deep check, then we also resolve the interface itself (this would mean resolving function types) if (deep && !sema_resolve_type_decl(context, inf_type)) return false; } return true; } /** * Analyse a struct or union field. */ static inline bool sema_analyse_struct_member(SemaContext *context, Decl *parent, Decl *decl, bool *erase_decl) { // Resolution might already have been completed in some cases due to $checks if (decl->resolve_status == RESOLVE_DONE) { if (!decl_ok(decl)) return false; if (decl->name) sema_decl_stack_push(decl); return true; } // Detect circular dependency. if (decl->resolve_status == RESOLVE_RUNNING) RETURN_SEMA_ERROR(decl, "Circular dependency resolving member."); // Mark the unit, it should not have been assigned at this point. assert(!decl->unit || decl->unit->module->is_generic || decl->unit == parent->unit); decl->unit = parent->unit; // Pick the domain for attribute analysis. AttributeDomain domain = ATTR_MEMBER; switch (decl->decl_kind) { case DECL_BITSTRUCT: domain = ATTR_BITSTRUCT; break; case DECL_UNION: domain = ATTR_UNION; break; case DECL_STRUCT: domain = ATTR_STRUCT; break; case DECL_VAR: break; default: UNREACHABLE } // Check attributes. if (!sema_analyse_attributes(context, decl, decl->attributes, domain, erase_decl)) return decl_poison(decl); // If we should erase this declaration due to an @if, exit here. if (*erase_decl) return true; // If it has a name, place it in the decl stack to ensure it is unique. if (decl->name) { Decl *other = sema_decl_stack_resolve_symbol(decl->name); if (other) { SEMA_ERROR(decl, "Duplicate member name '%s'.", other->name); SEMA_NOTE(other, "Previous declaration was here."); return false; } // Push the name onto the stack. sema_decl_stack_push(decl); } bool is_export = parent->is_export; // Analysis depends on the underlying type. switch (decl->decl_kind) { case DECL_VAR: { assert(decl->var.kind == VARDECL_MEMBER); decl->resolve_status = RESOLVE_RUNNING; // Inferred types are not strictly allowed, but we use the int[*] for the flexible array member. assert(type_infoptrzero(decl->var.type_info)); TypeInfo *type_info = type_infoptr(decl->var.type_info); if (!sema_resolve_type_info(context, type_info, RESOLVE_TYPE_ALLOW_FLEXIBLE)) return decl_poison(decl); Type *type = type_info->type; switch (type_storage_type(type)) { case STORAGE_NORMAL: break; case STORAGE_VOID: case STORAGE_COMPILE_TIME: RETURN_SEMA_ERROR(type_info, "Members cannot be of type %s.", type_quoted_error_string(type)); case STORAGE_WILDCARD: case STORAGE_UNKNOWN: RETURN_SEMA_ERROR(type_info, "%s has unknown size and cannot be used as a member.", type_quoted_error_string(type)); } decl->type = type; decl->resolve_status = RESOLVE_DONE; return true; } case DECL_STRUCT: case DECL_UNION: case DECL_BITSTRUCT: decl->is_export = is_export; if (!sema_analyse_decl(context, decl)) return false; return true; default: UNREACHABLE } } /** * Analyse union members, calculating alignment. */ static bool sema_analyse_union_members(SemaContext *context, Decl *decl) { AlignSize max_size = 0; MemberIndex max_alignment_element = 0; AlignSize max_alignment = 0; bool has_named_parameter = false; Decl **members = decl->strukt.members; unsigned member_count = vec_size(members); assert(member_count > 0); // Check all members for (unsigned i = 0; i < member_count; i++) { AGAIN:; Decl *member = members[i]; // The member may already have been resolved if it is poisoned, then exit. if (!decl_ok(member)) return false; bool erase_decl = false; // Check the member if (!sema_analyse_struct_member(context, decl, member, &erase_decl)) { // Failed return decl_poison(member); } // If we need to erase it then do so. if (erase_decl) { vec_erase_ptr_at(members, i); member_count--; // Go back and take the next one. if (i < member_count) goto AGAIN; break; } if (member->type->type_kind == TYPE_INFERRED_ARRAY) { decl_poison(member); RETURN_SEMA_ERROR(member, "Flexible array members not allowed in unions."); } AlignSize member_alignment; if (!sema_set_abi_alignment(context, member->type, &member_alignment)) return false; ByteSize member_size = type_size(member->type); assert(member_size <= MAX_TYPE_SIZE); // Update max alignment if (member_alignment > max_alignment) { max_alignment = member_alignment; max_alignment_element = (MemberIndex)i; } // Update max size if (member_size > max_size) { //max_size_element = i; max_size = (AlignSize)member_size; // If this is bigger than the previous with max // alignment, pick this as the maximum size field. if (max_alignment_element != (MemberIndex)i && max_alignment == member_alignment) { max_alignment_element = (MemberIndex)i; } } // Offset is always 0 member->offset = 0; } assert(decl_ok(decl)); // 1. If packed, then the alignment is zero, unless previously given if (decl->is_packed && !decl->alignment) decl->alignment = 1; // 2. otherwise pick the highest of the natural alignment and the given alignment. if (!decl->is_packed && decl->alignment < max_alignment) decl->alignment = max_alignment; // We're only packed if the max alignment is > 1 decl->is_packed = decl->is_packed && max_alignment > 1; // "Representative" type is the one with the maximum alignment. decl->strukt.union_rep = max_alignment_element; // All members share the same alignment FOREACH(Decl *, member, members) { member->alignment = decl->alignment; } assert(max_size); // The actual size might be larger than the max size due to alignment. AlignSize size = aligned_offset(max_size, decl->alignment); ByteSize rep_size = type_size(members[max_alignment_element]->type); // If the actual size is bigger than the real size, add // padding – typically used with LLVM lowering. if (size > rep_size) { decl->strukt.padding = (AlignSize)(size - rep_size); } decl->strukt.size = size; return true; } /* * Analyse the members of a struct, assumed to be 1 or more (should already be checked before calling the function) */ static bool sema_analyse_struct_members(SemaContext *context, Decl *decl) { // Default alignment is 1 even if it is empty. AlignSize natural_alignment = 1; bool is_unaligned = false; AlignSize size = 0; AlignSize offset = 0; bool is_packed = decl->is_packed; Decl **struct_members = decl->strukt.members; unsigned member_count = vec_size(struct_members); assert(member_count > 0 && "This analysis should only be called on member_count > 0"); for (unsigned i = 0; i < member_count; i++) { AGAIN:; Decl *member = struct_members[i]; // We might have already analysed and poisoned this decl, if so, exit. if (!decl_ok(member)) return decl_poison(decl); bool erase_decl = false; // Check the member if (!sema_analyse_struct_member(context, decl, member, &erase_decl)) { return decl_poison(decl); } // If we should erase it, do so. if (erase_decl) { vec_erase_ptr_at(struct_members, i); member_count--; if (i < member_count) goto AGAIN; break; } Type *member_type = type_flatten(member->type); // If this is a struct and it has a variable array ending, then it must also be the last struct. // So this is ok: // struct Foo { int x; struct { int x; int[*] y; } } // But not this: // struct Bar { struct { int x; int[*] y; } int x; } if (member_type->type_kind == TYPE_STRUCT && member_type->decl->has_variable_array) { if (i != member_count - 1) { decl_poison(member); RETURN_SEMA_ERROR(member, "A struct member with a flexible array must be the last element."); } // Mark it as a variable array. decl->has_variable_array = true; } else if (member_type->type_kind == TYPE_INFERRED_ARRAY) { // Check chat it is the last element. if (i != member_count - 1) { decl_poison(member); RETURN_SEMA_ERROR(member, "The flexible array member must be the last element."); } // And that it isn't the only element. if (i == 0) { decl_poison(member); RETURN_SEMA_ERROR(member, "The flexible array member cannot be the only element."); } // Now replace the type, because we want a TYPE_FLEXIBLE_ARRAY rather than the assumed TYPE_INFERRED_ARRAY member->type = type_get_flexible_array(member->type->array.base); // And mark as variable array decl->has_variable_array = true; } assert(decl_ok(decl) && "The declaration should be fine at this point."); // Grab the ABI alignment as its natural alignment AlignSize member_natural_alignment; if (!sema_set_abi_alignment(context, member->type, &member_natural_alignment)) return decl_poison(decl); // If packed, then the alignment is 1 AlignSize member_alignment = is_packed ? 1 : member_natural_alignment; // If a member has an assigned alignment, then we use that one, even if it is packed. if (member->alignment) { member_alignment = member->alignment; // Update total alignment if we have a member that has bigger alignment. if (member_alignment > decl->alignment) decl->alignment = member_alignment; } // If the member alignment is higher than the currently detected alignment, // then we update the natural alignment if (member_natural_alignment > natural_alignment) { natural_alignment = member_natural_alignment; } // In the case of a struct, we will align this to the next offset, // using the alignment of the member. unsigned align_offset = aligned_offset(offset, member_alignment); unsigned natural_align_offset = aligned_offset(offset, member_natural_alignment); // If the natural align is different from the aligned offset we have two cases: if (natural_align_offset != align_offset) { // If the natural alignment is greater, in this case the struct is unaligned. if (member_natural_alignment > member_alignment) { assert(natural_align_offset > align_offset); is_unaligned = true; } else { // Otherwise we have a greater offset, and in this case // we add padding for the difference. assert(natural_align_offset < align_offset); member->padding = align_offset - offset; } } member->alignment = member_alignment; offset = align_offset; member->offset = offset; offset += type_size(member->type); } // Set the alignment: // 1. If packed, use the alignment given, otherwise set to 1. if (decl->is_packed && !decl->alignment) decl->alignment = 1; // 2. otherwise pick the highest of the natural alignment and the given alignment. if (!decl->is_packed && decl->alignment < natural_alignment) decl->alignment = natural_alignment; // We must now possibly add the end padding. // First we calculate the actual size size = aligned_offset(offset, decl->alignment); // We might get a size that is greater than the natural alignment // in this case we need an additional padding if (size > aligned_offset(offset, natural_alignment)) { decl->strukt.padding = (AlignSize)(size - offset); } // If the size is smaller the naturally aligned struct, then it is also unaligned if (size < aligned_offset(offset, natural_alignment)) { is_unaligned = true; } if (is_unaligned && size > offset) { assert(!decl->strukt.padding); decl->strukt.padding = (AlignSize)(size - offset); } decl->is_packed = is_unaligned; decl->strukt.size = size; return true; } static bool sema_analyse_struct_union(SemaContext *context, Decl *decl, bool *erase_decl) { // Begin by analysing attributes bool is_union = decl->decl_kind == DECL_UNION; AttributeDomain domain = is_union ? ATTR_UNION : ATTR_STRUCT; if (!sema_analyse_attributes(context, decl, decl->attributes, domain, erase_decl)) return decl_poison(decl); // If an @if attribute erases it, end here if (*erase_decl) return true; // Resolve any implemented interfaces shallowly (i.e. functions are not resolved) if (!sema_resolve_implemented_interfaces(context, decl, false)) return decl_poison(decl); DEBUG_LOG("Beginning analysis of %s.", decl->name ? decl->name : ".anon"); Decl **members = decl->strukt.members; // We require at least one member in C3. This simplifies semantics in corner cases. if (!vec_size(members)) { RETURN_SEMA_ERROR(decl, "Zero sized %s are not permitted.", is_union ? "unions" : "structs"); } // If we have a name, we need to create a new decl stack Decl** state = decl->name ? sema_decl_stack_store() : NULL; bool success = is_union ? sema_analyse_union_members(context, decl) : sema_analyse_struct_members(context, decl); // Restore if needed. if (decl->name) sema_decl_stack_restore(state); DEBUG_LOG("Struct/union size %d, alignment %d.", (int)decl->strukt.size, (int)decl->alignment); DEBUG_LOG("Analysis complete."); // Failed, so exit. if (!success) return decl_poison(decl); assert(decl_ok(decl)); return true; } static inline bool sema_analyse_bitstruct_member(SemaContext *context, Decl *parent, Decl *member, unsigned index, bool allow_overlap, bool *erase_decl) { if (member->resolve_status == RESOLVE_DONE) { if (!decl_ok(member)) return false; if (member->name) sema_decl_stack_push(member); return true; } TypeInfo *type_info = type_infoptr(member->var.type_info); if (member->resolve_status == RESOLVE_RUNNING) { RETURN_SEMA_ERROR(member, "Circular dependency resolving member."); } bool ease_decl = false; if (!sema_analyse_attributes(context, member, member->attributes, ATTR_BITSTRUCT_MEMBER, erase_decl)) return decl_poison(member); if (*erase_decl) return true; if (member->name) { Decl *other = sema_decl_stack_resolve_symbol(member->name); if (other) { SEMA_ERROR(member, "Duplicate member name '%s'.", other->name); SEMA_NOTE(other, "Previous declaration was here."); return false; } if (member->name) sema_decl_stack_push(member); } bool is_consecutive = parent->bitstruct.consecutive; // Resolve the type. if (!sema_resolve_type_info(context, type_info, RESOLVE_TYPE_DEFAULT)) return false; member->type = type_info->type; // Flatten the distinct and enum types. Type *member_type = type_flatten_for_bitstruct(member->type); // Only accept (flattened) integer and bool types if (!type_is_integer(member_type) && member_type != type_bool) { SEMA_ERROR(type_info, "%s is not supported in a bitstruct, only enums, integer and boolean values may be used.", type_quoted_error_string(member->type)); return false; } // Grab the underlying bit type size. BitSize bits = type_size(parent->bitstruct.base_type->type) * (BitSize)8; if (bits > MAX_BITSTRUCT) { SEMA_ERROR(parent->bitstruct.base_type, "Bitstruct size may not exceed %d bits.", MAX_BITSTRUCT); return false; } Int max_bits = (Int) { .type = TYPE_I64, .i = { .low = bits } }; // Resolve the bit range, starting with the beginning unsigned start_bit, end_bit; if (is_consecutive) { assert(!member->var.bit_is_expr && "Should always me inferred"); if (member_type != type_bool) { SEMA_ERROR(type_info, "For bitstructs without bit ranges, the types must all be 'bool'."); return false; } start_bit = end_bit = member->var.start_bit; if (start_bit >= bits) { SEMA_ERROR(member, "This element would overflow the bitstruct size (%d bits).", bits); return false; } goto AFTER_BIT_CHECK; } if (member->var.bit_is_expr) { Expr *start = member->var.start; if (!sema_analyse_expr(context, start)) return false; // Check for negative, non integer or non const values. if (!expr_is_const(start) || !type_is_integer(start->type) || int_is_neg(start->const_expr.ixx)) { SEMA_ERROR(start, "This must be a constant non-negative integer value."); return false; } // Check that we didn't exceed max bits. if (int_comp(start->const_expr.ixx, max_bits, BINARYOP_GE)) { SEMA_ERROR(start, "Expected at the most a bit index of %d\n", bits - 1); return false; } end_bit = start_bit = (unsigned)start->const_expr.ixx.i.low; // Handle the end Expr *end = member->var.end; if (end) { // Analyse the end if (!sema_analyse_expr(context, start)) return false; if (!expr_is_const(end) || !type_is_integer(end->type) || int_is_neg(end->const_expr.ixx)) { SEMA_ERROR(end, "This must be a constant non-negative integer value."); return false; } if (int_comp(end->const_expr.ixx, max_bits, BINARYOP_GE)) { SEMA_ERROR(end, "Expected at the most a bit index of %d.", bits - 1); return false; } end_bit = (unsigned)end->const_expr.ixx.i.low; } else { // No end bit, this is only ok if the type is bool, otherwise a range is needed. // This prevents confusion with C style bits. if (member_type->type_kind != TYPE_BOOL) { SEMA_ERROR(member, "Only booleans may use non-range indices, try using %d..%d instead.", start_bit, start_bit); return false; } } // Check that we actually have a positive range. if (start_bit > end_bit) { SEMA_ERROR(start, "The start bit must be smaller than the end bit index."); return false; } } else { start_bit = member->var.start_bit; end_bit = member->var.end_bit; } // Check how many bits we need. TypeSize bitsize_type = member_type == type_bool ? 1 : type_size(member_type) * 8; // And how many we have. TypeSize bits_available = end_bit + 1 - start_bit; // Assigning more than needed is not allowed. if (bitsize_type < bits_available) { SEMA_ERROR(member, "The bit width of %s (%d) is less than the assigned bits (%d), try reducing the range.", type_quoted_error_string(member->type), (int)bitsize_type, (int)bits_available); return false; } // Store the bits. member->var.start_bit = start_bit; member->var.end_bit = end_bit; member->var.bit_is_expr = false; AFTER_BIT_CHECK: // Check for overlap if (!allow_overlap) { Decl **members = parent->bitstruct.members; for (unsigned i = 0; i < index; i++) { Decl *other_member = members[i]; // Check for overlap. if ((start_bit >= other_member->var.start_bit || end_bit >= other_member->var.start_bit) && start_bit <= other_member->var.end_bit) { SEMA_ERROR(member, "Overlapping members, please use '@overlap' if this is intended."); SEMA_NOTE(other_member, "The other member was declared here."); return false; } } } member->resolve_status = RESOLVE_DONE; return true; } /* * Analyse an interface declaration, because it is only called from analyse_decl, it is safe * to just return false (and not poison explicitly), if it should be called from elsewhere, * then this needs to be handled. */ static bool sema_analyse_interface(SemaContext *context, Decl *decl, bool *erase_decl) { // Begin with analysing attributes. if (!sema_analyse_attributes(context, decl, decl->attributes, ATTR_INTERFACE, erase_decl)) return false; // If erased using @if, we exit. if (*erase_decl) return true; // Resolve the inherited interfaces deeply if (!sema_resolve_implemented_interfaces(context, decl, true)) return false; // Walk through the methods. Decl **functions = decl->interface_methods; unsigned count = vec_size(functions); // Note that zero functions are allowed, this is useful for creating combinations of interfaces. for (unsigned i = 0; i < count; i++) { RETRY:; Decl *method = functions[i]; // The method might have been resolved earlier, if so we either exit or go to the next. // This might happen for example if it was resolved using $checks if (method->resolve_status == RESOLVE_DONE) { if (!decl_ok(method)) return false; continue; } if (method->decl_kind != DECL_FUNC) RETURN_SEMA_ERROR(method, "Only functions are allowed here."); if (method->func_decl.type_parent) { RETURN_SEMA_ERROR(type_infoptr(method->func_decl.type_parent), "Interfaces should not be declared as methods."); } method->func_decl.attr_interface_method = true; bool erase = false; // Insert the first parameter, which is the implicit `void*` Decl *first = decl_new_var(kw_self, decl->span, NULL, VARDECL_PARAM); first->type = type_voidptr; first->var.kind = VARDECL_PARAM; first->unit = context->unit; first->resolve_status = RESOLVE_DONE; first->alignment = type_abi_alignment(type_voidptr); vec_insert_first(method->func_decl.signature.params, first); method->unit = context->unit; // Now we analyse the function as a regular function. if (!sema_analyse_func(context, method, &erase)) { // This is necessary in order to allow this check to run again. decl_poison(method); vec_erase_ptr_at(method->func_decl.signature.params, 0); return false; } // We might need to erase the function. if (erase) { vec_erase_ptr_at(functions, i); count--; if (i >= count) break; goto RETRY; } const char *name = method->name; // Do a simple check to ensure the same function isn't defined twice. // note that this doesn't check if it's overlapping an inherited method, this is deliberate. for (unsigned j = 0; j < i; j++) { if (functions[j]->name == name) { SEMA_ERROR(method, "Duplicate definition of method '%s'.", name); SEMA_NOTE(functions[j], "The previous definition was here."); decl_poison(method); return false; } } } return true; } static bool sema_deep_resolve_function_ptr(SemaContext *context, TypeInfo *type_to_resolve) { assert(type_to_resolve->type); Type *type = type_to_resolve->type; RETRY: switch (type->type_kind) { case TYPE_POISONED: case TYPE_VOID: case TYPE_BOOL: case ALL_INTS: case ALL_FLOATS: case TYPE_ANY: case TYPE_INTERFACE: case TYPE_ANYFAULT: case TYPE_TYPEID: case TYPE_ENUM: case TYPE_STRUCT: case TYPE_UNION: case TYPE_BITSTRUCT: case TYPE_FAULTTYPE: case TYPE_DISTINCT: case TYPE_VECTOR: case TYPE_INFERRED_VECTOR: case TYPE_UNTYPED_LIST: case TYPE_WILDCARD: case TYPE_TYPEINFO: case TYPE_MEMBER: return true; case TYPE_TYPEDEF: type = type->canonical; goto RETRY; case TYPE_POINTER: case TYPE_FUNC_PTR: type = type->pointer; goto RETRY; case TYPE_FUNC_RAW: return sema_analyse_decl(context, type->decl); case TYPE_OPTIONAL: type = type->optional; goto RETRY; case TYPE_ARRAY: case TYPE_SLICE: case TYPE_INFERRED_ARRAY: case TYPE_FLEXIBLE_ARRAY: type = type->array.base; goto RETRY; } UNREACHABLE } static bool sema_analyse_bitstruct(SemaContext *context, Decl *decl, bool *erase_decl) { if (!sema_analyse_attributes(context, decl, decl->attributes, ATTR_BITSTRUCT, erase_decl)) return decl_poison(decl); if (!sema_resolve_implemented_interfaces(context, decl, false)) return decl_poison(decl); if (*erase_decl) return true; DEBUG_LOG("Beginning analysis of %s.", decl->name ? decl->name : ".anon"); if (!sema_resolve_type_info(context, decl->bitstruct.base_type, RESOLVE_TYPE_DEFAULT)) return false; Type *type = decl->bitstruct.base_type->type->canonical; Type *base_type = type->type_kind == TYPE_ARRAY ? type->array.base : type; if (!type_is_integer(base_type)) { SEMA_ERROR(decl->bitstruct.base_type, "The type of the bitstruct cannot be %s but must be an integer or an array of integers.", type_quoted_error_string(decl->bitstruct.base_type->type)); return false; } Decl **members = decl->bitstruct.members; unsigned member_count = vec_size(members); Decl **state = decl->name ? sema_decl_stack_store() : NULL; for (unsigned i = 0; i < member_count; i++) { AGAIN:; Decl *member = members[i]; if (!decl_ok(member)) goto ERROR; bool erase_decl_member = false; if (!sema_analyse_bitstruct_member(context, decl, member, i, decl->bitstruct.overlap, &erase_decl_member)) goto ERROR; if (erase_decl_member) { vec_erase_ptr_at(members, i); member_count--; if (i < member_count) goto AGAIN; break; } } if (state) sema_decl_stack_restore(state); return true; ERROR: if (state) sema_decl_stack_restore(state); return decl_poison(decl); } static inline bool sema_analyse_signature(SemaContext *context, Signature *sig, TypeInfoId type_parent, bool is_export) { Variadic variadic_type = sig->variadic; Decl **params = sig->params; unsigned param_count = vec_size(params); unsigned vararg_index = sig->vararg_index; bool is_macro = sig->is_macro; bool is_macro_at_name = sig->is_at_macro || sig->is_safemacro; // Check return type assert(sig->rtype || sig->is_macro); Type *rtype = NULL; if (sig->rtype) { TypeInfo *rtype_info = type_infoptr(sig->rtype); if (!sema_resolve_type_info(context, type_infoptr(sig->rtype), is_macro ? RESOLVE_TYPE_ALLOW_INFER : RESOLVE_TYPE_DEFAULT)) return false; rtype = rtype_info->type; if (sig->attrs.nodiscard) { if (type_is_void(rtype)) { RETURN_SEMA_ERROR(rtype_info, "@nodiscard cannot be used on %s returning 'void'.", is_macro ? "macros" : "functions"); } } if (sig->attrs.maydiscard) { if (!type_is_optional(rtype)) { SEMA_ERROR(rtype_info, "@maydiscard can only be used on %s returning optional values.", is_macro ? "macros" : "functions"); return false; } } if (!sema_deep_resolve_function_ptr(context, rtype_info)) return false; } // We don't support more than MAX_PARAMS number of params. This makes everything sane. if (param_count > MAX_PARAMS) { if (variadic_type != VARIADIC_NONE) { SEMA_ERROR(params[MAX_PARAMS], "The number of params exceeded the max of %d.", MAX_PARAMS); return false; } SEMA_ERROR(params[MAX_PARAMS], "The number of params exceeded the max of %d. To accept more arguments, consider using varargs.", MAX_PARAMS); return false; } TypeInfo *method_parent = type_infoptrzero(type_parent); if (method_parent) { if (!sema_resolve_type_info(context, method_parent, is_macro ? RESOLVE_TYPE_MACRO_METHOD : RESOLVE_TYPE_FUNC_METHOD)) return false; } // Fill in the type if the first parameter is lacking a type. if (method_parent && params && params[0] && !params[0]->var.type_info) { Decl *param = params[0]; Type *inferred_type = NULL; switch (param->var.kind) { case VARDECL_PARAM_REF: inferred_type = type_get_ptr(method_parent->type); if (!is_macro) param->var.kind = VARDECL_PARAM; break; case VARDECL_PARAM: inferred_type = method_parent->type; break; default: goto CHECK_PARAMS; } param->var.type_info = type_info_id_new_base(inferred_type, param->span); } CHECK_PARAMS: // Check parameters for (unsigned i = 0; i < param_count; i++) { Decl *param = params[i]; if (!param) { assert(variadic_type == VARIADIC_RAW); assert(i == vararg_index); continue; } if (vararg_index < i) { if (!is_macro && variadic_type == VARIADIC_RAW) { SEMA_ERROR(param, "C-style varargs cannot be followed by regular parameters."); return decl_poison(param); } // If we already reached a vararg (as a previous argument) and we have a // parameter without a name then that is an error. if (!param->name) { SEMA_ERROR(param, "A parameter name was expected, as parameters after varargs must always be named."); return decl_poison(param); } } if (i == 0 && param->resolve_status == RESOLVE_DONE) { assert(param->type == type_voidptr && "Expected the first parameter of an interface method."); continue; } assert(param->resolve_status == RESOLVE_NOT_DONE && "The param shouldn't have been resolved yet."); param->resolve_status = RESOLVE_RUNNING; bool erase = false; if (!sema_analyse_attributes(context, param, param->attributes, ATTR_PARAM, &erase)) { return decl_poison(param); } assert(!erase); param->unit = context->unit; assert(param->decl_kind == DECL_VAR); VarDeclKind var_kind = param->var.kind; TypeInfo *type_info = type_infoptrzero(param->var.type_info); if (type_info) { if (!sema_resolve_type_info(context, type_info, is_macro ? RESOLVE_TYPE_ALLOW_INFER : RESOLVE_TYPE_DEFAULT)) return decl_poison(param); param->type = type_info->type; } switch (var_kind) { case VARDECL_PARAM_REF: if (type_info && !type_is_pointer(param->type)) { RETURN_SEMA_ERROR(type_info, "A pointer type was expected for a ref argument, did you mean %s?", type_quoted_error_string(type_get_ptr(param->type))); return decl_poison(param); } FALLTHROUGH; case VARDECL_PARAM_EXPR: if (!is_macro) { SEMA_ERROR(param, "Only regular parameters are allowed for functions."); return decl_poison(param); } if (!is_macro_at_name && (!type_parent || i != 0 || var_kind != VARDECL_PARAM_REF)) { SEMA_ERROR(param, "Ref and expression parameters are not allowed in function-like macros. Prefix the macro name with '@'."); return decl_poison(param); } FALLTHROUGH; case VARDECL_PARAM_CT: if (!is_macro) { SEMA_ERROR(param, "Only regular parameters are allowed for functions."); return decl_poison(param); } FALLTHROUGH; case VARDECL_PARAM: if (!param->type && !is_macro) { RETURN_SEMA_ERROR(param, "Only typed parameters are allowed for functions."); } bool erase_decl = false; if (!sema_analyse_attributes_for_var(context, param, &erase_decl)) return false; assert(!erase_decl); break; case VARDECL_PARAM_CT_TYPE: if (type_info) { SEMA_ERROR(type_info, "A compile time type parameter cannot have a type itself."); return decl_poison(param); } if (!is_macro) { SEMA_ERROR(param, "Only regular parameters are allowed for functions."); return decl_poison(param); } break; case VARDECL_CONST: case VARDECL_GLOBAL: case VARDECL_LOCAL: case VARDECL_MEMBER: case VARDECL_BITMEMBER: case VARDECL_LOCAL_CT: case VARDECL_LOCAL_CT_TYPE: case VARDECL_UNWRAPPED: case VARDECL_REWRAPPED: case VARDECL_ERASE: UNREACHABLE } if (param->var.vararg) { if (var_kind != VARDECL_PARAM) { SEMA_ERROR(param, "Only regular parameters may be vararg."); return decl_poison(param); } if (!type_info) { SEMA_ERROR(param, "Only typed parameters may be vararg."); return decl_poison(param); } // Update whether this was a vararg, and update "default" for the signature. if (param->var.vararg) { if (vararg_index != i) { SEMA_ERROR(param, "A %s may not have more than one vararg.", is_macro ? "macro" : "function"); return decl_poison(param); } } type_info->type = type_get_slice(type_info->type); } if (type_info) { if (!sema_deep_resolve_function_ptr(context, type_info)) return false; param->type = type_info->type; if (!sema_set_abi_alignment(context, param->type, ¶m->alignment)) return false; } if (param->var.init_expr) { Expr *expr = param->var.init_expr; if (expr_is_const(expr)) { if (!sema_analyse_expr_rhs(context, param->type, expr, true, NULL)) return decl_poison(param); } } if (!sema_check_param_uniqueness_and_type(context, params, param, i, param_count)) return decl_poison(param); param->resolve_status = RESOLVE_DONE; } return true; } bool sema_analyse_function_signature(SemaContext *context, Decl *func_decl, CallABI abi, Signature *signature) { // Get param count and variadic type Decl **params = signature->params; if (!sema_analyse_signature(context, signature, func_decl->func_decl.type_parent, func_decl->is_export)) return false; Variadic variadic_type = signature->variadic; // Remove the last empty value. if (variadic_type == VARIADIC_RAW) { assert(params && !params[signature->vararg_index] && "The last parameter must have been a raw variadic."); assert(signature->vararg_index == vec_size(params) - 1); vec_pop(params); } Type **types = NULL; bool all_ok = true; unsigned param_count = vec_size(params); for (unsigned i = 0; i < param_count; i++) { assert(IS_RESOLVED(params[i])); assert(params[i]->type->canonical); vec_add(types, params[i]->type); } if (!all_ok) return false; Type *raw_type = sema_resolve_type_get_func(signature, abi); assert(func_decl->type->type_kind == TYPE_FUNC_RAW); assert(raw_type->function.prototype); func_decl->type->function.prototype = raw_type->function.prototype; return true; } static inline bool sema_analyse_fntype(SemaContext *context, Decl *decl, bool *erase_decl) { if (!sema_analyse_attributes(context, decl, decl->attributes, ATTR_DEF, erase_decl)) return decl_poison(decl); if (*erase_decl) return true; Signature *sig = &decl->fntype_decl; return sema_analyse_function_signature(context, decl, sig->abi, sig); } static inline bool sema_analyse_typedef(SemaContext *context, Decl *decl, bool *erase_decl) { if (!sema_analyse_attributes(context, decl, decl->attributes, ATTR_DEF, erase_decl)) return decl_poison(decl); if (*erase_decl) return true; bool is_export = decl->is_export; if (decl->typedef_decl.is_func) { Decl *fn_decl = decl->typedef_decl.decl; fn_decl->is_export = is_export; fn_decl->unit = decl->unit; fn_decl->type = type_new_func(fn_decl, &fn_decl->fntype_decl); decl->type->canonical = type_get_func_ptr(fn_decl->type); return true; } TypeInfo *info = decl->typedef_decl.type_info; if (!sema_resolve_type_info(context, info, RESOLVE_TYPE_DEFAULT)) return false; decl->type->canonical = info->type->canonical; // Do we need anything else? return true; } static inline bool sema_analyse_distinct(SemaContext *context, Decl *decl, bool *erase) { // Check the attributes on the distinct type. if (!sema_analyse_attributes(context, decl, decl->attributes, ATTR_DISTINCT, erase)) return false; // Erase it? if (*erase) return true; // Check the interfaces. if (!sema_resolve_implemented_interfaces(context, decl, false)) return false; // Infer the underlying type normally. TypeInfo *info = decl->distinct; if (!sema_resolve_type_info(context, info, RESOLVE_TYPE_DEFAULT)) return false; // Optional isn't allowed of course. if (type_is_optional(info->type)) RETURN_SEMA_ERROR(decl, "You cannot create a distinct type from an optional."); // Distinct types drop the canonical part. info->type = info->type->canonical; return true; } static inline bool sema_analyse_enum_param(SemaContext *context, Decl *param) { assert(param->decl_kind == DECL_VAR && param->var.kind == VARDECL_PARAM && param->var.type_info); if (vec_size(param->attributes)) { SEMA_ERROR(param->attributes[0], "There are no valid attributes for associated values."); return false; } TypeInfo *type_info = type_infoptrzero(param->var.type_info); if (!sema_resolve_type_info(context, type_info, RESOLVE_TYPE_DEFAULT)) return false; assert(!param->var.vararg); param->type = type_info->type; assert(param->name); if (param->name == kw_nameof) { SEMA_ERROR(param, "'nameof' is not a valid parameter name for enums."); return false; } Decl *other = sema_decl_stack_resolve_symbol(param->name); if (other) { SEMA_ERROR(param, "Duplicate parameter name '%s'.", param->name); return false; } sema_decl_stack_push(param); assert(!param->var.init_expr); return sema_set_abi_alignment(context, param->type, ¶m->alignment); } static inline bool sema_analyse_enum(SemaContext *context, Decl *decl, bool *erase_decl) { if (!sema_analyse_attributes(context, decl, decl->attributes, ATTR_ENUM, erase_decl)) return decl_poison(decl); if (*erase_decl) return true; if (!sema_resolve_implemented_interfaces(context, decl, false)) return decl_poison(decl); // Resolve the type of the enum. if (!sema_resolve_type_info(context, decl->enums.type_info, RESOLVE_TYPE_DEFAULT)) return false; Type *type = decl->enums.type_info->type; assert(!type_is_optional(type) && "Already stopped when parsing."); Type *flat_underlying_type = type_flatten(type); // Require an integer type if (!type_is_integer(flat_underlying_type)) { SEMA_ERROR(decl->enums.type_info, "The enum type must be an integer type not '%s'.", type_to_error_string(type)); return false; } DEBUG_LOG("* Enum type resolved to %s.", type->name); Decl **associated_values = decl->enums.parameters; unsigned associated_value_count = vec_size(associated_values); Decl** state = sema_decl_stack_store(); for (unsigned i = 0; i < associated_value_count; i++) { Decl *value = associated_values[i]; switch (value->resolve_status) { case RESOLVE_DONE: continue; case RESOLVE_RUNNING: SEMA_ERROR(value, "Recursive definition found."); goto ERR; case RESOLVE_NOT_DONE: value->resolve_status = RESOLVE_RUNNING; break; } if (!sema_analyse_enum_param(context, value)) goto ERR; value->resolve_status = RESOLVE_DONE; } sema_decl_stack_restore(state); bool success = true; unsigned enums = vec_size(decl->enums.values); Int128 value = { 0, 0 }; Decl **enum_values = decl->enums.values; for (unsigned i = 0; i < enums; i++) { Decl *enum_value = enum_values[i]; bool erase_val = false; if (!sema_analyse_attributes(context, decl, enum_value->attributes, ATTR_ENUM, &erase_val)) return decl_poison(decl); if (erase_val) { if (enums == 1) { SEMA_ERROR(decl, "No enum values left in enum after @if resolution, there must be at least one."); return decl_poison(decl); } vec_erase_ptr_at(enum_values, i); enums--; i--; continue; } enum_value->type = decl->type; DEBUG_LOG("* Checking enum constant %s.", enum_value->name); enum_value->enum_constant.ordinal = i; DEBUG_LOG("* Ordinal: %d", i); assert(enum_value->resolve_status == RESOLVE_NOT_DONE); assert(enum_value->decl_kind == DECL_ENUM_CONSTANT); // Start evaluating the constant enum_value->resolve_status = RESOLVE_RUNNING; // Create a "fake" expression. // This will be evaluated later to catch the case Int val = (Int){ value, flat_underlying_type->type_kind }; if (!int_fits(val, flat_underlying_type->type_kind)) { SEMA_ERROR(enum_value, "The enum value would implicitly be %s which does not fit in %s.", i128_to_string(value, 10, type_is_signed(flat_underlying_type)), type_quoted_error_string(type)); return false; } enum_value->enum_constant.ordinal = value.low; // Update the value value.low++; Expr **args = enum_value->enum_constant.args; unsigned arg_count = vec_size(args); if (arg_count > associated_value_count) { if (!associated_value_count) { RETURN_SEMA_ERROR(args[0], "No associated values are defined for this enum."); } RETURN_SEMA_ERROR(args[associated_value_count], "Only %d associated value(s) may be defined for this enum."); } if (arg_count < associated_value_count) { RETURN_SEMA_ERROR(enum_value, "Expected %d associated value(s) for this enum.", associated_value_count); return false; } for (unsigned j = 0; j < arg_count; j++) { Expr *arg = args[j]; if (!sema_analyse_expr_rhs(context, associated_values[j]->type, arg, false, NULL)) return false; if (!expr_is_constant_eval(arg, CONSTANT_EVAL_GLOBAL_INIT)) { SEMA_ERROR(arg, "Expected a constant expression as parameter."); return false; } } enum_value->resolve_status = RESOLVE_DONE; } return success; ERR: sema_decl_stack_restore(state); return false; } static inline bool sema_analyse_error(SemaContext *context, Decl *decl, bool *erase_decl) { if (!sema_analyse_attributes(context, decl, decl->attributes, ATTR_FAULT, erase_decl)) return decl_poison(decl); if (*erase_decl) return true; if (!sema_resolve_implemented_interfaces(context, decl, false)) return decl_poison(decl); bool success = true; unsigned enums = vec_size(decl->enums.values); for (unsigned i = 0; i < enums; i++) { Decl *enum_value = decl->enums.values[i]; enum_value->type = decl->type; DEBUG_LOG("* Checking error value %s.", enum_value->name); enum_value->enum_constant.ordinal = i; DEBUG_LOG("* Ordinal: %d", i); assert(enum_value->resolve_status == RESOLVE_NOT_DONE); assert(enum_value->decl_kind == DECL_FAULTVALUE); // Start evaluating the constant enum_value->resolve_status = RESOLVE_DONE; } return success; } static inline const char *method_name_by_decl(Decl *method_like) { switch (method_like->decl_kind) { case DECL_MACRO: return "macro method"; case DECL_FUNC: return "method"; default: UNREACHABLE } } static bool sema_analyse_operator_common(SemaContext *context, Decl *method, TypeInfo **rtype_ptr, Decl ***params_ptr, uint32_t parameters) { Signature *signature = &method->func_decl.signature; Decl **params = *params_ptr = signature->params; uint32_t param_count = vec_size(params); if (param_count > parameters) { RETURN_SEMA_ERROR(params[parameters], "Too many parameters, '%s' expects only %u.", method->name, (unsigned)parameters); } if (param_count < parameters) { RETURN_SEMA_ERROR(method, "Not enough parameters, '%s' requires %u.", method->name, (unsigned)parameters); } if (!signature->rtype) RETURN_SEMA_ERROR(method, "The return value must be explicitly typed for '%s'.", method->name); FOREACH(Decl *, param, params) { if (!param->var.type_info) { RETURN_SEMA_ERROR(param, "All parameters must be explicitly typed for '%s'.", method->name); } } *rtype_ptr = type_infoptr(signature->rtype); return true; } static inline Decl *operator_in_module(SemaContext *c, Module *module, OperatorOverload operator_overload) { if (module->is_generic) return NULL; Decl **extensions = module->private_method_extensions; FOREACH(Decl *, extension, extensions) { if (extension->operator == operator_overload) { unit_register_external_symbol(c->compilation_unit, extension); return extension; } } FOREACH(Module *, sub_module, module->sub_modules) { return operator_in_module(c, sub_module, operator_overload); } return NULL; } Decl *sema_find_operator(SemaContext *context, Type *type, OperatorOverload operator_overload) { type = type->canonical; if (!type_may_have_sub_elements(type)) return NULL; Decl *def = type->decl; FOREACH(Decl *, func, def->methods) { if (func->operator == operator_overload) { unit_register_external_symbol(context->compilation_unit, func); return func; } } Decl *extension = operator_in_module(context, context->compilation_unit->module, operator_overload); if (extension) return extension; FOREACH(Decl *, import, context->unit->imports) { extension = operator_in_module(context, import->import.module, operator_overload); if (extension) return extension; } return NULL; } static inline bool sema_analyse_operator_element_at(SemaContext *context, Decl *method) { TypeInfo *rtype; Decl **params; if (!sema_analyse_operator_common(context, method, &rtype, ¶ms, 2)) return false; switch (type_storage_type(rtype->type)) { case STORAGE_NORMAL: break; case STORAGE_VOID: case STORAGE_WILDCARD: RETURN_SEMA_ERROR(rtype, "The return type cannot be 'void'."); case STORAGE_COMPILE_TIME: RETURN_SEMA_ERROR(rtype, "The return type is %s which is a compile time type, which isn't allowed.", type_quoted_error_string(rtype->type)); case STORAGE_UNKNOWN: RETURN_SEMA_ERROR(rtype, "%s has unknown size and cannot be used as a return type.", type_quoted_error_string(rtype->type)); } if (method->operator == OVERLOAD_ELEMENT_REF && !type_is_pointer(rtype->type)) { RETURN_SEMA_ERROR(rtype, "The return type must be a pointer, but it is returning %s, did you mean to overload [] instead?", type_quoted_error_string(rtype->type)); } return true; } static inline bool sema_analyse_operator_element_set(SemaContext *context, Decl *method) { TypeInfo *rtype; Decl **params; return sema_analyse_operator_common(context, method, &rtype, ¶ms, 3); } static inline bool sema_analyse_operator_len(Decl *method, SemaContext *context) { TypeInfo *rtype; Decl **params; if (!sema_analyse_operator_common(context, method, &rtype, ¶ms, 1)) return false; if (!type_is_integer(rtype->type)) { RETURN_SEMA_ERROR(rtype, "The return type must be an integer type."); } return true; } static bool sema_check_operator_method_validity(SemaContext *context, Decl *method) { switch (method->operator) { case OVERLOAD_ELEMENT_SET: return sema_analyse_operator_element_set(context, method); case OVERLOAD_ELEMENT_AT: case OVERLOAD_ELEMENT_REF: return sema_analyse_operator_element_at(context, method); case OVERLOAD_LEN: return sema_analyse_operator_len(method, context); } UNREACHABLE } bool sema_decl_if_cond(SemaContext *context, Decl *decl) { Attr *attr = attr_find_kind(decl->attributes, ATTRIBUTE_IF); decl->is_if = true; assert(attr); if (vec_size(attr->exprs) != 1) { RETURN_SEMA_ERROR(attr, "Expected an argument to '@if'."); } Expr *expr = attr->exprs[0]; context->call_env.in_if_resolution = attr->span; bool success = sema_analyse_ct_expr(context, expr); context->call_env.in_if_resolution.a = 0; if (!success) return false; if (expr->type->canonical != type_bool) { RETURN_SEMA_ERROR(expr, "Expected a boolean value not %s.", type_quoted_error_string(expr->type)); } if (expr->const_expr.b) return true; decl->decl_kind = DECL_ERASED; context->call_env.in_if_resolution.a = 0; return false; } INLINE Attr* method_find_overload_attribute(Decl *method) { FOREACH(Attr *, attr, method->attributes) { if (attr->attr_kind == ATTRIBUTE_OPERATOR) return attr; } UNREACHABLE } static inline bool unit_add_base_extension_method(SemaContext *context, CompilationUnit *unit, Type *parent_type, Decl *method) { // We don't support operator overloading on base types, because // there seems little use for it frankly. if (method->operator) { RETURN_SEMA_ERROR(method_find_overload_attribute(method), "Only user-defined types support operator oveloading."); } // Add it to the right list of extensions. switch (method->visibility) { case VISIBLE_PUBLIC: vec_add(global_context.method_extensions, method); break; case VISIBLE_PRIVATE: vec_add(unit->module->private_method_extensions, method); break; case VISIBLE_LOCAL: vec_add(unit->local_method_extensions, method); break; } DEBUG_LOG("Method-like '%s.%s' analysed.", parent_type->name, method->name); return true; } static inline void sema_get_overload_arguments(Decl *method, Type **value_ref, Type **index_ref) { switch (method->operator) { case OVERLOAD_LEN: UNREACHABLE case OVERLOAD_ELEMENT_AT: *value_ref = type_no_optional(typeget(method->func_decl.signature.rtype)->canonical); *index_ref = method->func_decl.signature.params[1]->type->canonical; return; case OVERLOAD_ELEMENT_REF: *value_ref = type_no_optional(typeget(method->func_decl.signature.rtype)->canonical->pointer); *index_ref = method->func_decl.signature.params[1]->type->canonical; return; case OVERLOAD_ELEMENT_SET: *value_ref = method->func_decl.signature.params[2]->type->canonical; *index_ref = method->func_decl.signature.params[1]->type->canonical; return; default: UNREACHABLE } } /** * Do checks on an operator method: * * 1. Are the arguments valid for the operator? * 2. Check if the operator was already defined. * 3. See if another operator was defined and if the results match up. */ INLINE bool sema_analyse_operator_method(SemaContext *context, Type *parent_type, Decl *method) { // Check it's valid for the operator type. if (!sema_check_operator_method_validity(context, method)) return false; // See if the operator has already been defined. OperatorOverload operator = method->operator; Decl *other = sema_find_operator(context, parent_type, operator); if (other) { Attr *attr = method_find_overload_attribute(method); SEMA_ERROR(attr->exprs[0], "This operator is already defined for '%s'.", parent_type->name); SEMA_NOTE(other, "The previous definition was here."); return false; } // Check that actual types match up Type *value; Type *index_type; switch (operator) { case OVERLOAD_LEN: // No dependency return true; case OVERLOAD_ELEMENT_AT: // [] compares &[] other = sema_find_operator(context, parent_type, OVERLOAD_ELEMENT_REF); if (other && decl_ok(other)) { sema_get_overload_arguments(other, &value, &index_type); break; } // And []= other = sema_find_operator(context, parent_type, OVERLOAD_ELEMENT_SET); if (other && decl_ok(other)) { sema_get_overload_arguments(other, &value, &index_type); break; } return true; case OVERLOAD_ELEMENT_REF: // &[] compares [] other = sema_find_operator(context, parent_type, OVERLOAD_ELEMENT_AT); if (other && decl_ok(other)) { sema_get_overload_arguments(other, &value, &index_type); break; } // And []= other = sema_find_operator(context, parent_type, OVERLOAD_ELEMENT_SET); if (other && decl_ok(other)) { sema_get_overload_arguments(other, &value, &index_type); break; } return true; case OVERLOAD_ELEMENT_SET: // []= compares &[] other = sema_find_operator(context, parent_type, OVERLOAD_ELEMENT_REF); if (other && decl_ok(other)) { sema_get_overload_arguments(other, &value, &index_type); break; } // And [] other = sema_find_operator(context, parent_type, OVERLOAD_ELEMENT_AT); if (other && decl_ok(other)) { sema_get_overload_arguments(other, &value, &index_type); break; } return true; default: UNREACHABLE } // We have an operator to compare with. Type *this_value; Type *this_index_type; sema_get_overload_arguments(method, &this_value, &this_index_type); // So compare the value if (this_value != value) { SEMA_ERROR(method, "There is a mismatch of the 'value' type compared to that of another operator: expected %s but got %s.", type_quoted_error_string(value), type_quoted_error_string(this_value)); SEMA_NOTE(other, "The other definition is here."); return false; } // And the index. if (this_index_type != index_type) { SEMA_ERROR(method, "There is a mismatch of the 'index' type compared to that of another operator: expected %s but got %s.", type_quoted_error_string(index_type), type_quoted_error_string(this_index_type)); SEMA_NOTE(other, "The other definition is here."); return false; } // Everything worked! Done. return true; } /** * Attempt to add a method to the type: * * 1. See if it is already defined as an extension method => if so show error * 2. If it is a non-user defined => delegate to unit_add_base_extension_method * 3. If it is check if it is defined on the type. * 4. If it is an operator method call sema_analyse_operator_method to check it. * 5. Register its external name. * 6. Add the module according to visibility. */ static inline bool unit_add_method(SemaContext *context, Type *parent_type, Decl *method) { CompilationUnit *unit = context->unit; assert(parent_type->canonical == parent_type); const char *name = method->name; // Did we already define it externally? Decl *other = sema_find_extension_method_in_list(unit->local_method_extensions, parent_type, name); if (!other) sema_find_extension_method_in_list(unit->module->private_method_extensions, parent_type, name); if (!other) sema_find_extension_method_in_list(global_context.method_extensions, parent_type, name); if (other) { SEMA_ERROR(method, "This %s is already defined.", method_name_by_decl(method)); SEMA_NOTE(other, "The previous definition was here."); return false; } // Is it a base extension? if (!type_is_user_defined(parent_type)) return unit_add_base_extension_method(context, unit, parent_type, method); // Resolve it as a user-defined type extension. Decl *parent = parent_type->decl; Decl *ambiguous = NULL; Decl *private = NULL; // If we found it, issue an error. other = sema_resolve_method(unit, parent, name, &ambiguous, &private); if (other) { SEMA_ERROR(method, "This %s is already defined for '%s'.", method_name_by_decl(method), parent_type->name); SEMA_NOTE(other, "The previous definition was here."); return false; } // Is it an operator? if (method->operator) { if (!sema_analyse_operator_method(context, parent_type, method)) return false; } DEBUG_LOG("Method-like '%s.%s' analysed.", parent->name, method->name); // Add it to the correct place: type methods, private extensions, local method extensions switch (method->visibility) { case VISIBLE_PUBLIC: vec_add(parent->methods, method); break; case VISIBLE_PRIVATE: if (decl_module(parent) == unit->module && parent->visibility >= VISIBLE_PRIVATE) { vec_add(parent->methods, method); break; } vec_add(unit->module->private_method_extensions, method); break; case VISIBLE_LOCAL: if (parent->unit == unit && parent->visibility >= VISIBLE_LOCAL) { vec_add(parent->methods, method); break; } vec_add(unit->local_method_extensions, method); break; default: UNREACHABLE } return true; } /** * Find an interface name by searching through its own interfaces as well as any * parents to the interface. */ static Decl *sema_interface_method_by_name(Decl *interface, const char *name) { FOREACH(Decl *, method, interface->interface_methods) { if (method->name == name) return method; } FOREACH(TypeInfo *, parent_type, interface->interfaces) { Decl *res = sema_interface_method_by_name(parent_type->type->decl, name); if (res) return res; } return NULL; } /** * Given a method, find the interface it implements. * * 1. Check that it can implement an interface (structs, unions, distinct, faults and enums can) * 2. Find the method in the interfaces implemented. If more than one interface has the method then they must match * 3. If no match is found we return. * 4. Otherwise we fully resolve the matching interface. * 5. And return the interface method. * * If (2) or (4) fails, then this is an error returning a poisoned decl. Not finding a method is fine, * this returns NULL. */ static inline Decl *sema_find_interface_for_method(SemaContext *context, CanonicalType *parent_type, Decl *method) { // Can the parent even implement a interface? switch (parent_type->type_kind) { case TYPE_STRUCT: case TYPE_UNION: case TYPE_DISTINCT: case TYPE_FAULTTYPE: case TYPE_ENUM: break; case TYPE_TYPEDEF: UNREACHABLE default: return NULL; } const char *name = method->name; Decl *first_match = NULL; Decl *first_interface = NULL; // Walk through all implemented interfaces. FOREACH(TypeInfo *, proto, parent_type->decl->interfaces) { Decl *interface = proto->type->decl; Decl *match = sema_interface_method_by_name(interface, name); if (!match) continue; // Is there a already a match? if (first_match) { if (first_match->type->function.prototype->raw_type == match->type->function.prototype->raw_type) continue; SEMA_ERROR(method, "Both '%s' and '%s' interfaces have a method matching '%s' but their signatures are different, " "which prevents it from being implemented.", first_interface->name, interface->name, name); return poisoned_decl; } // Update the match. first_match = match; first_interface = interface; } // No match => return NULL. if (!first_match) return NULL; // Analyse the interface. if (!sema_analyse_decl(context, first_interface)) return poisoned_decl; // Return the match. return first_match; } /** * Check that an interface matches the implementing method. * * 1. Check return types. * 2. Check parameter count. * 3. Check each parameter for matching types * * @return true if it matches, false otherwise. */ static inline bool sema_compare_method_with_interface(SemaContext *context, Decl *decl, Decl *implemented_method) { Signature interface_sig = implemented_method->func_decl.signature; Signature this_sig = decl->func_decl.signature; Type *any_rtype = typeget(interface_sig.rtype); Type *this_rtype = typeget(this_sig.rtype); // Do the return types match? if (any_rtype->canonical != this_rtype->canonical) { SEMA_ERROR(type_infoptr(this_sig.rtype), "The prototype method has a return type %s, but this function returns %s, they need to match.", type_quoted_error_string(any_rtype), type_quoted_error_string(this_rtype)); SEMA_NOTE(type_infoptr(interface_sig.rtype), "The interface definition is here."); return false; } Decl **any_params = interface_sig.params; Decl **this_params = this_sig.params; unsigned any_param_count = vec_size(any_params); unsigned this_param_count = vec_size(this_params); // Do the param counts match? if (any_param_count != this_param_count) { if (any_param_count > this_param_count) { SEMA_ERROR(decl, "This function is missing parameters, %d parameters were expected.", any_param_count); SEMA_NOTE(any_params[this_param_count], "Compare with the interface definition."); return false; } else { SEMA_ERROR(this_params[any_param_count], "This function has too many parameters (%d).", this_param_count); SEMA_NOTE(decl, "Compare with the interface, which has only %d parameter%s.", any_param_count, any_param_count == 1 ? "" : "s"); } return false; } // Check each param. FOREACH_IDX(i, Decl *, param, this_params) { if (i == 0) continue; if (param->type->canonical != any_params[i]->type->canonical) { SEMA_ERROR(vartype(param), "The prototype argument has type %s, but in this function it has type %s. Please make them match.", type_quoted_error_string(any_params[i]->type), type_quoted_error_string(param->type)); SEMA_NOTE(vartype(any_params[i]), "The interface definition is here."); return false; } } return true; } /** * Analyse a method. * * 1. Check that it has no init/finalizer attributes. * 2. Check that it has no test/benchmark attributes. * 3. Resolve the parent type. * 4. Resolve the declaration of the parent (as needed). * 5. Check that it has at least one parameter. * 6. Check that this parameter is correct. * 7. If it is dynamic, the type may not be an interface or any * 8. If it is dynamic, make sure that it implements an interface correctly if available * 9. Try adding the method */ static inline bool sema_analyse_method(SemaContext *context, Decl *decl) { // Check for @init, @finalizer, @test and @benchmark if (decl->func_decl.attr_init | decl->func_decl.attr_finalizer) { SEMA_ERROR(decl, "Methods may not have '@init' or '@finalizer' attributes."); return decl_poison(decl); } if (decl->func_decl.attr_test || decl->func_decl.attr_benchmark) { SEMA_ERROR(decl, "Methods may not be annotated %s.", decl->func_decl.attr_test ? "@test" : "@benchmark"); return decl_poison(decl); } // Resolve the parent type. TypeInfo *parent_type = type_infoptr(decl->func_decl.type_parent); if (!sema_resolve_type_info(context, parent_type, RESOLVE_TYPE_FUNC_METHOD)) return false; Type *par_type = parent_type->type->canonical; // Resolve declaration of parent as needed. if (!sema_resolve_type_decl(context, par_type)) return false; Decl **params = decl->func_decl.signature.params; bool is_dynamic = decl->func_decl.attr_dynamic; // Ensure it has at least one parameter. if (!vec_size(params)) RETURN_SEMA_ERROR(decl, "A method must start with an argument of the type " "it is a method of, e.g. 'fn void %s.%s(%s* self)'.", type_to_error_string(par_type), decl->name, type_to_error_string(par_type)); // Ensure that the first parameter is valid. if (!sema_is_valid_method_param(context, params[0], par_type, is_dynamic)) return false; // Make dynamic checks. if (is_dynamic) { if (par_type->type_kind == TYPE_INTERFACE) { RETURN_SEMA_ERROR(decl, "Interfaces may not implement '@dynamic' methods, only regular methods."); } if (par_type == type_any) { RETURN_SEMA_ERROR(decl, "'any' may not implement '@dynamic' methods, only regular methods."); } // Retrieve the implemented method. Decl *implemented_method = sema_find_interface_for_method(context, par_type, decl); if (!decl_ok(implemented_method)) return false; // If it's implementing a method, check it. if (implemented_method) { if (!sema_compare_method_with_interface(context, decl, implemented_method)) return false; decl->func_decl.interface_method = declid(implemented_method); } else { decl->func_decl.interface_method = 0; } } return unit_add_method(context, par_type, decl); } static const char *attribute_domain_to_string(AttributeDomain domain) { switch (domain) { case ATTR_MACRO: return "macro"; case ATTR_LOCAL: return "local variable"; case ATTR_BITSTRUCT: return "bitstruct"; case ATTR_INTERFACE: return "interface"; case ATTR_MEMBER: return "member"; case ATTR_BITSTRUCT_MEMBER: return "bitstruct member"; case ATTR_FUNC: return "function"; case ATTR_PARAM: return "parameter"; case ATTR_ENUM_VALUE: return "enum value"; case ATTR_GLOBAL: return "global variable"; case ATTR_ENUM: return "enum"; case ATTR_STRUCT: return "struct"; case ATTR_UNION: return "union"; case ATTR_CONST: return "constant"; case ATTR_FAULT: return "fault"; case ATTR_DEF: return "def"; case ATTR_CALL: return "call"; case ATTR_DISTINCT: return "distinct"; case ATTR_INTERFACE_METHOD: return "interface method"; } UNREACHABLE } // Helper method INLINE bool update_abi(Decl *decl, CallABI abi) { decl->func_decl.signature.abi = abi; return true; } /** * Given a @callconv string, update the call convention. * * Test for: * 1. cdecl * 2. veccall * 3. stdcall * * If the platform is unsupported on the platform, it is ignored. */ static bool update_call_abi_from_string(SemaContext *context, Decl *decl, Expr *expr) { const char *str = expr->const_expr.bytes.ptr; CallABI abi; // C decl is easy if (str_eq(str, "cdecl")) return update_abi(decl, CALL_C); // Check veccall if (str_eq(str, "veccall")) { switch (platform_target.arch) { case ARCH_TYPE_X86_64: return update_abi(decl, CALL_X64_VECTOR); case ARCH_TYPE_ARM: case ARCH_TYPE_ARMB: case ARCH_TYPE_AARCH64: case ARCH_TYPE_AARCH64_32: case ARCH_TYPE_AARCH64_BE: return update_abi(decl, CALL_AAPCS_VFP); case ARCH_TYPE_X86: // Unsupported FALLTHROUGH; default: return true; } } // Finally check stdcall. if (strcmp(str, "stdcall") == 0) { if (platform_target.arch == ARCH_TYPE_ARM || platform_target.arch == ARCH_TYPE_ARMB) { return update_abi(decl, CALL_AAPCS); } return true; } RETURN_SEMA_ERROR(expr, "Unknown call convention, only 'cdecl', 'stdcall' and 'veccall' are supported"); } #define EXPORTED_USER_DEFINED_TYPES (ATTR_ENUM | ATTR_UNION | ATTR_STRUCT | ATTR_FAULT) #define CALLABLE_TYPE (ATTR_FUNC | ATTR_INTERFACE_METHOD | ATTR_MACRO) #define USER_DEFINED_TYPES EXPORTED_USER_DEFINED_TYPES | ATTR_BITSTRUCT | ATTR_DISTINCT /** * Analyse almost all attributes. */ static bool sema_analyse_attribute(SemaContext *context, Decl *decl, Attr *attr, AttributeDomain domain, bool *erase_decl) { AttributeType type = attr->attr_kind; assert(type >= 0 && type < NUMBER_OF_ATTRIBUTES); // NOLINTBEGIN(*.EnumCastOutOfRange) static AttributeDomain attribute_domain[NUMBER_OF_ATTRIBUTES] = { [ATTRIBUTE_ALIGN] = ATTR_FUNC | ATTR_CONST | ATTR_LOCAL | ATTR_GLOBAL | ATTR_BITSTRUCT | ATTR_STRUCT | ATTR_UNION | ATTR_MEMBER, // NOLINT [ATTRIBUTE_BENCHMARK] = ATTR_FUNC, [ATTRIBUTE_BIGENDIAN] = ATTR_BITSTRUCT, [ATTRIBUTE_BUILTIN] = ATTR_MACRO | ATTR_FUNC | ATTR_GLOBAL | ATTR_CONST, [ATTRIBUTE_CALLCONV] = ATTR_FUNC | ATTR_INTERFACE_METHOD, [ATTRIBUTE_DEPRECATED] = USER_DEFINED_TYPES | CALLABLE_TYPE | ATTR_CONST | ATTR_GLOBAL | ATTR_MEMBER | ATTR_BITSTRUCT_MEMBER | ATTR_INTERFACE, [ATTRIBUTE_DYNAMIC] = ATTR_FUNC, [ATTRIBUTE_EXPORT] = ATTR_FUNC | ATTR_GLOBAL | ATTR_CONST | USER_DEFINED_TYPES | ATTR_DEF, [ATTRIBUTE_EXTERN] = ATTR_FUNC | ATTR_GLOBAL | ATTR_CONST | USER_DEFINED_TYPES, [ATTRIBUTE_FINALIZER] = ATTR_FUNC, [ATTRIBUTE_IF] = (AttributeDomain)~(ATTR_CALL | ATTR_LOCAL | ATTR_PARAM), [ATTRIBUTE_INIT] = ATTR_FUNC, [ATTRIBUTE_INLINE] = ATTR_FUNC | ATTR_CALL, [ATTRIBUTE_LINK] = ATTR_FUNC | ATTR_MACRO | ATTR_CONST | ATTR_GLOBAL, [ATTRIBUTE_LITTLEENDIAN] = ATTR_BITSTRUCT, [ATTRIBUTE_LOCAL] = ATTR_FUNC | ATTR_MACRO | ATTR_GLOBAL | ATTR_CONST | USER_DEFINED_TYPES | ATTR_DEF | ATTR_INTERFACE, [ATTRIBUTE_MAYDISCARD] = CALLABLE_TYPE, [ATTRIBUTE_NAKED] = ATTR_FUNC, [ATTRIBUTE_NODISCARD] = CALLABLE_TYPE, [ATTRIBUTE_NOINIT] = ATTR_GLOBAL | ATTR_LOCAL, [ATTRIBUTE_NOINLINE] = ATTR_FUNC | ATTR_CALL, [ATTRIBUTE_NORETURN] = CALLABLE_TYPE, [ATTRIBUTE_NOSTRIP] = ATTR_FUNC | ATTR_GLOBAL | ATTR_CONST | EXPORTED_USER_DEFINED_TYPES, [ATTRIBUTE_OBFUSCATE] = ATTR_ENUM | ATTR_FAULT, [ATTRIBUTE_OPERATOR] = ATTR_MACRO | ATTR_FUNC, [ATTRIBUTE_OPTIONAL] = ATTR_INTERFACE_METHOD, [ATTRIBUTE_OVERLAP] = ATTR_BITSTRUCT, [ATTRIBUTE_PACKED] = ATTR_STRUCT | ATTR_UNION, [ATTRIBUTE_PRIVATE] = ATTR_FUNC | ATTR_MACRO | ATTR_GLOBAL | ATTR_CONST | USER_DEFINED_TYPES | ATTR_DEF | ATTR_INTERFACE, [ATTRIBUTE_PUBLIC] = ATTR_FUNC | ATTR_MACRO | ATTR_GLOBAL | ATTR_CONST | USER_DEFINED_TYPES | ATTR_DEF | ATTR_INTERFACE, [ATTRIBUTE_PURE] = ATTR_CALL, [ATTRIBUTE_REFLECT] = ATTR_FUNC | ATTR_GLOBAL | ATTR_CONST | USER_DEFINED_TYPES, [ATTRIBUTE_SAFEMACRO] = ATTR_MACRO, [ATTRIBUTE_SECTION] = ATTR_FUNC | ATTR_CONST | ATTR_GLOBAL, [ATTRIBUTE_TEST] = ATTR_FUNC, [ATTRIBUTE_UNUSED] = (AttributeDomain)~(ATTR_CALL), [ATTRIBUTE_USED] = (AttributeDomain)~(ATTR_CALL), [ATTRIBUTE_WASM] = ATTR_FUNC, [ATTRIBUTE_WEAK] = ATTR_FUNC | ATTR_CONST | ATTR_GLOBAL, [ATTRIBUTE_WINMAIN] = ATTR_FUNC, }; // NOLINTEND(*.EnumCastOutOfRange) // First check if it doesn't match the domain. if ((attribute_domain[type] & domain) != domain) { RETURN_SEMA_ERROR(attr, "'%s' is not a valid %s attribute.", attr->name, attribute_domain_to_string(domain)); } // No attribute has more than one argument right now. unsigned args = vec_size(attr->exprs); if (args > 1 && type != ATTRIBUTE_LINK) { SEMA_ERROR(attr->exprs[1], "Too many arguments for the attribute."); return false; } Expr *expr = args ? attr->exprs[0] : NULL; switch (type) { case ATTRIBUTE_PRIVATE: case ATTRIBUTE_PUBLIC: case ATTRIBUTE_LOCAL: case ATTRIBUTE_BUILTIN: // These are pseudo-attributes and are processed separately. UNREACHABLE; case ATTRIBUTE_DEPRECATED: // We expect an optional string. if (expr) { if (!sema_analyse_expr(context, expr)) return false; if (!expr_is_const_string(expr)) { RETURN_SEMA_ERROR(expr, "Expected a constant string value as argument."); } } decl->is_deprecated = true; return true; case ATTRIBUTE_OPTIONAL: decl->func_decl.attr_optional = true; return true; case ATTRIBUTE_WINMAIN: if (decl->name != kw_main) { SEMA_ERROR(attr, "'@winmain' can only be used on the 'main' function."); return false; } decl->func_decl.attr_winmain = true; break; case ATTRIBUTE_CALLCONV: if (!expr) RETURN_SEMA_ERROR(decl, "Expected a string argument."); if (expr && !sema_analyse_expr(context, expr)) return false; if (!expr_is_const_string(expr)) RETURN_SEMA_ERROR(expr, "Expected a constant string value as argument."); if (!update_call_abi_from_string(context, decl, expr)) return false; return true; case ATTRIBUTE_BENCHMARK: decl->func_decl.attr_benchmark = true; break; case ATTRIBUTE_TEST: decl->func_decl.attr_test = true; break; case ATTRIBUTE_OPERATOR: { assert(decl->decl_kind == DECL_FUNC || decl->decl_kind == DECL_MACRO); if (!expr) goto FAILED_OP_TYPE; switch (expr->expr_kind) { case EXPR_IDENTIFIER: if (expr->identifier_expr.path) goto FAILED_OP_TYPE; if (expr->identifier_expr.ident != kw_len) goto FAILED_OP_TYPE; decl->operator = OVERLOAD_LEN; break; case EXPR_OPERATOR_CHARS: decl->operator = expr->overload_expr; break; default: goto FAILED_OP_TYPE; } if (!decl->func_decl.type_parent) { SEMA_ERROR(expr, "@operator(...) can only be used with methods."); return false; } return true; FAILED_OP_TYPE: RETURN_SEMA_ERROR(attr, "'operator' requires an operator type argument: '[]', '[]=', '&[]' or 'len'."); } case ATTRIBUTE_ALIGN: if (!expr) { RETURN_SEMA_ERROR(attr, "'align' requires an power-of-2 argument, e.g. align(8)."); } if (!sema_analyse_expr(context, expr)) return false; if (!expr_is_const_int(expr)) { RETURN_SEMA_ERROR(expr, "Expected a constant integer value as argument."); } { if (int_ucomp(expr->const_expr.ixx, MAX_ALIGNMENT, BINARYOP_GT)) { RETURN_SEMA_ERROR(expr, "Alignment must be less or equal to %ull.", MAX_ALIGNMENT); } if (int_ucomp(expr->const_expr.ixx, 0, BINARYOP_LE)) { RETURN_SEMA_ERROR(expr, "Alignment must be greater than zero."); } uint64_t align = int_to_u64(expr->const_expr.ixx); if (!is_power_of_two(align)) { RETURN_SEMA_ERROR(expr, "Alignment must be a power of two."); } decl->alignment = (AlignSize)align; return true; } case ATTRIBUTE_EXPORT: if (context->unit->module->is_generic) { RETURN_SEMA_ERROR(attr, "'@export' is not allowed in generic modules."); } if (expr) { if (!sema_analyse_expr(context, expr)) return false; if (!expr_is_const_string(expr)) { RETURN_SEMA_ERROR(expr, "Expected a constant string value as argument."); } if (decl->has_extname) { RETURN_SEMA_ERROR(expr, "An external name is already defined, please use '@extern` without an argument."); } decl->has_extname = true; decl->extname = expr->const_expr.bytes.ptr; } decl->is_export = true; return true; case ATTRIBUTE_NOSTRIP: decl->no_strip = true; return true; case ATTRIBUTE_IF: if (!expr) RETURN_SEMA_ERROR(attr, "'@if' requires a boolean argument."); if (!sema_analyse_expr(context, expr)) return false; if (expr->type->canonical != type_bool || !expr_is_const(expr)) { RETURN_SEMA_ERROR(expr, "Expected a boolean compile time constant value."); } if (!expr->const_expr.b) *erase_decl = true; return true; case ATTRIBUTE_FINALIZER: decl->func_decl.attr_finalizer = true; // Ugly goto PARSE; case ATTRIBUTE_LINK: if (args < 1) RETURN_SEMA_ERROR(attr, "'@link' requires at least one argument."); Expr *cond = args > 1 ? attr->exprs[0] : NULL; if (cond && !sema_analyse_expr(context, cond)) return false; int start = 0; decl->has_link = true; if (cond && expr_is_const_bool(cond)) { start = 1; decl->has_link = cond->const_expr.b; } for (unsigned i = start; i < args; i++) { Expr *string = attr->exprs[i]; if (!sema_analyse_expr(context, string)) return false; if (!expr_is_const_string(string)) RETURN_SEMA_ERROR(string, "Expected a constant string here, usage is: '@link(cond1, link1, link2, ...)'."); } // Erase if not applicable. if (start == 1) attr->exprs[0] = NULL; if (!decl->has_link) attr->exprs = NULL; return true; case ATTRIBUTE_INIT: decl->func_decl.attr_init = true; PARSE:; if (expr) { if (!sema_analyse_expr(context, expr)) return false; if (!expr_is_const_int(expr)) { RETURN_SEMA_ERROR(attr, "Expected an integer value."); } uint64_t prio = decl->func_decl.priority = expr->const_expr.ixx.i.low; if (expr_const_will_overflow(&expr->const_expr, TYPE_U16) || prio > MAX_PRIORITY || prio < 1) { RETURN_SEMA_ERROR(attr, "The priority must be a value between 1 and %d", MAX_PRIORITY); } } if (!decl->func_decl.priority) decl->func_decl.priority = MAX_PRIORITY; return true; case ATTRIBUTE_SECTION: case ATTRIBUTE_EXTERN: if (context->unit->module->is_generic) { RETURN_SEMA_ERROR(attr, "'%s' attributes are not allowed in generic modules.", attr->name); } if (!expr) { RETURN_SEMA_ERROR(attr, "'%s' requires a string argument, e.g. %s(\"foo\").", attr->name, attr->name); } if (!sema_analyse_expr(context, expr)) return false; if (!expr_is_const_string(expr)) { RETURN_SEMA_ERROR(expr, "Expected a constant string value as argument."); } switch (type) { case ATTRIBUTE_SECTION: if (!sema_check_section(context, attr)) return false; decl->section_id = global_context_register_section(expr->const_expr.bytes.ptr); break; case ATTRIBUTE_EXTERN: decl->has_extname = true; decl->extname = expr->const_expr.bytes.ptr; break; default: UNREACHABLE; } return true; case ATTRIBUTE_NOINLINE: decl->func_decl.attr_noinline = true; decl->func_decl.attr_inline = false; break; case ATTRIBUTE_NOINIT: decl->var.no_init = true; break; case ATTRIBUTE_NODISCARD: decl->func_decl.signature.attrs.nodiscard = true; break; case ATTRIBUTE_MAYDISCARD: decl->func_decl.signature.attrs.maydiscard = true; break; case ATTRIBUTE_INLINE: decl->func_decl.attr_inline = true; decl->func_decl.attr_noinline = false; break; case ATTRIBUTE_NORETURN: decl->func_decl.signature.attrs.noreturn = true; break; case ATTRIBUTE_WEAK: decl->is_weak = true; break; case ATTRIBUTE_WASM: decl->is_export = true; break; case ATTRIBUTE_NAKED: assert(domain == ATTR_FUNC); decl->func_decl.attr_naked = true; break; case ATTRIBUTE_OVERLAP: decl->bitstruct.overlap = true; break; case ATTRIBUTE_DYNAMIC: decl->func_decl.attr_dynamic = true; break; case ATTRIBUTE_BIGENDIAN: if (decl->bitstruct.little_endian) { SEMA_ERROR(attr, "Attribute cannot be combined with @littleendian"); return decl_poison(decl); } decl->bitstruct.big_endian = true; break; case ATTRIBUTE_LITTLEENDIAN: if (decl->bitstruct.big_endian) { SEMA_ERROR(attr, "Attribute cannot be combined with @bigendian"); return decl_poison(decl); } decl->bitstruct.little_endian = true; break; case ATTRIBUTE_PACKED: decl->is_packed = true; break; case ATTRIBUTE_UNUSED: decl->is_maybe_unused = true; break; case ATTRIBUTE_USED: decl->is_must_use = true; break; case ATTRIBUTE_PURE: // Only used for calls. UNREACHABLE case ATTRIBUTE_SAFEMACRO: decl->func_decl.signature.is_safemacro = true; break; case ATTRIBUTE_REFLECT: decl->will_reflect = true; break; case ATTRIBUTE_OBFUSCATE: decl->obfuscate = true; break; case ATTRIBUTE_NONE: UNREACHABLE } if (expr) RETURN_SEMA_ERROR(expr, "'%s' should not have any arguments.", attr->name); return true; } static inline bool sema_analyse_custom_attribute(SemaContext *context, Decl *decl, Attr *attr, AttributeDomain domain, Decl *top, bool *erase_decl) { // Custom attributes. // First find it. Decl *attr_decl = sema_resolve_symbol(context, attr->name, attr->path, attr->span); if (!attr_decl) return false; // Detect direct cycles @Foo = @Bar @Bar = @Foo if (attr_decl == top) { SEMA_ERROR(top, "Recursive declaration of attribute '%s'.", top->name); return decl_poison(attr_decl); } // Handle the case where the current function is the declaration itself. if (context->call_env.kind == CALL_ENV_ATTR && context->call_env.attr_declaration == attr_decl) { RETURN_SEMA_ERROR(attr_decl, "Recursive declaration of attribute '%s' – it contains itself.", attr_decl->name); } // Grab all the parameters to the attribute and copy them. Decl **params = attr_decl->attr_decl.params; unsigned param_count = vec_size(params); params = copy_decl_list_single(params); // Get the arguments Expr **args = attr->exprs; // Check that the parameters match. No varargs are allowed on attributes, // there seems to be little use for it. if (param_count != vec_size(args)) { SEMA_ERROR(attr, "Expected %d parameter(s).", param_count); SEMA_NOTE(attr_decl, "The declaration was here."); return false; } // Ok, we have the list of inner attributes. Attr **attributes = attr_decl->attr_decl.attrs; attributes = copy_attributes_single(attributes); // Now we need to evaluate these attributes in the attribute definition // context. SemaContext eval_context; sema_context_init(&eval_context, attr_decl->unit); eval_context.macro_call_depth = context->macro_call_depth + 1; eval_context.call_env = (CallEnv) { .kind = CALL_ENV_ATTR, .attr_declaration = decl }; // We copy the compilation unit. eval_context.compilation_unit = context->unit; // First we need to analyse each expression in the current scope for (int j = 0; j < param_count; j++) { if (!sema_analyse_ct_expr(context, args[j])) goto ERR; params[j]->var.init_expr = args[j]; params[j]->var.kind = VARDECL_CONST; // Then add them to the evaluation context. // (Yes this is messy) sema_add_local(&eval_context, params[j]); } // Now we've added everything to the evaluation context, so we can (recursively) // apply it to the contained attributes, which in turn may be derived attributes. if (!sema_analyse_attributes_inner(&eval_context, decl, attributes, domain, top ? top : attr_decl, erase_decl)) goto ERR; // Then destroy the eval context. sema_context_destroy(&eval_context); // Stop evaluating on erase. return true; ERR: sema_context_destroy(&eval_context); return false; } // TODO consider doing this evaluation early, it should be possible. static bool sema_analyse_attributes_inner(SemaContext *context, Decl *decl, Attr **attrs, AttributeDomain domain, Decl *top, bool *erase_decl) { // Detect cycles of the type @Foo = @BarCyclic, @BarCyclic = @BarCyclic if (context->macro_call_depth > 1024) { if (!top) top = decl; RETURN_SEMA_ERROR(top, "Recursive declaration of attribute '%s'.", top->name); } // Walk through all of the attributes. FOREACH(Attr *, attr, attrs) { if (attr->is_custom) { if (!sema_analyse_custom_attribute(context, decl, attr, domain, top, erase_decl)) return false; } else { // The simple case, we have a built in attribute: if (!sema_analyse_attribute(context, decl, attr, domain, erase_decl)) return false; } if (*erase_decl) return true; } return true; } static bool sema_analyse_attributes(SemaContext *context, Decl *decl, Attr **attrs, AttributeDomain domain, bool *erase_decl) { return sema_analyse_attributes_inner(context, decl, attrs, domain, NULL, erase_decl); } static inline bool sema_analyse_doc_header(SemaContext *context, AstId doc, Decl **params, Decl **extra_params, bool *pure_ref) { while (doc) { Ast *directive = astptr(doc); doc = directive->next; ContractKind directive_kind = directive->contract_stmt.kind; if (directive_kind == CONTRACT_PURE) { if (*pure_ref) { SEMA_ERROR(directive, "Multiple '@pure' declarations, please remove one."); return false; } *pure_ref = true; continue; } if (directive_kind != CONTRACT_PARAM) continue; const char *param_name = directive->contract_stmt.param.name; Decl *extra_param = NULL; Decl *param = NULL; FOREACH(Decl *, other_param, params) { param = other_param; if (param && param->name == param_name) goto NEXT; } FOREACH(Decl *, extra, extra_params) { param = extra; if (param && param->name == param_name) goto NEXT; } RETURN_SEMA_ERROR(&directive->contract_stmt.param, "There is no parameter '%s', did you misspell it?", param_name); NEXT:; Type *type = param->type; if (type) type = type_flatten(type); bool may_be_pointer = !type || type_is_pointer(type) || type_is_any_raw(type); if (directive->contract_stmt.param.by_ref) { if (!may_be_pointer) { RETURN_SEMA_ERROR(directive, "'&' can only be added to pointer type parameters."); } param->var.not_null = true; } switch (directive->contract_stmt.param.modifier) { case PARAM_ANY: goto ADDED; case PARAM_IN: param->var.in_param = true; break; case PARAM_OUT: param->var.out_param = true; break; case PARAM_INOUT: break; } if (!may_be_pointer && type->type_kind != TYPE_SLICE) { RETURN_SEMA_ERROR(directive, "'in', 'out' and 'inout' may only be added to pointers and slices."); } ADDED:; } return true; } static inline MainType sema_find_main_type(SemaContext *context, Signature *sig, bool is_winmain) { Decl **params = sig->params; unsigned param_count = vec_size(params); bool is_win32 = platform_target.os == OS_TYPE_WIN32; Type *arg_type, *arg_type2; switch (param_count) { case 0: return MAIN_TYPE_NO_ARGS; case 1: arg_type = type_flatten(params[0]->type); if (arg_type == type_get_slice(type_string)) return MAIN_TYPE_ARGS; SEMA_ERROR(params[0], "Expected a parameter of type 'String[]'."); return MAIN_TYPE_ERROR; case 2: arg_type = type_flatten(params[0]->type); arg_type2 = type_flatten(params[1]->type); if (arg_type != type_cint) { SEMA_ERROR(params[0], "Expected a parameter of type %s for a C-style main.", type_quoted_error_string(type_cint)); return MAIN_TYPE_ERROR; } if (arg_type2 != type_get_ptr(type_get_ptr(type_char))) { SEMA_ERROR(params[1], "Expected a parameter of type 'char**' for a C-style main."); return MAIN_TYPE_ERROR; } if (is_winmain) { SEMA_ERROR(params[0], "For '@winmain' functions, C-style 'main' with argc + argv isn't valid. It compiles if you remove the '@winmain' attribute."); return MAIN_TYPE_ERROR; } return MAIN_TYPE_RAW; case 3: if (!is_win32 || !is_winmain) break; arg_type = type_flatten(params[0]->type); arg_type2 = type_flatten(params[1]->type); if (arg_type != type_voidptr) { SEMA_ERROR(params[0], "Expected a parameter of type 'void*' (HINSTANCE)"); return MAIN_TYPE_ERROR; } if (arg_type2 != type_get_slice(type_string)) { SEMA_ERROR(params[1], "Expected a parameter of type 'String[]'."); return MAIN_TYPE_ERROR; } if (type_flatten(params[2]->type) != type_cint) { SEMA_ERROR(params[2], "Expected a parameter of type %s for the 'showCmd' parameter.", type_quoted_error_string(type_cint)); return MAIN_TYPE_ERROR; } if (!is_win32) { SEMA_ERROR(params[0], "'main(HINSTANCE, String[], int) is only valid for Windows."); return MAIN_TYPE_ERROR; } return MAIN_TYPE_WIN; default: break; } SEMA_ERROR(params[0], (is_win32 & is_winmain) ? "Expected zero, 1 or 3 parameters for main." : "Expected zero or 1 parameters for main."); return MAIN_TYPE_ERROR; } static inline Decl *sema_create_synthetic_main(SemaContext *context, Decl *decl, MainType main, bool int_return, bool err_return, bool is_winmain, bool is_wmain) { Decl *function = decl_new(DECL_FUNC, NULL, decl->span); function->is_export = true; function->has_extname = true; function->extname = kw_mainstub; function->name = kw_mainstub; function->unit = decl->unit; // Pick wWinMain, main or wmain Decl *param1; Decl *param2; Decl *param3 = NULL; if (is_winmain) { function->extname = kw_winmain; param1 = decl_new_generated_var(type_voidptr, VARDECL_PARAM, decl->span); param2 = decl_new_generated_var(type_get_ptr(type_ushort), VARDECL_PARAM, decl->span); param3 = decl_new_generated_var(type_cint, VARDECL_PARAM, decl->span); } else if (is_wmain) { function->extname = kw_wmain; param1 = decl_new_generated_var(type_cint, VARDECL_PARAM, decl->span); param2 = decl_new_generated_var(type_get_ptr(type_get_ptr(type_ushort)), VARDECL_PARAM, decl->span); } else { function->extname = kw_main; param1 = decl_new_generated_var(type_cint, VARDECL_PARAM, decl->span); param2 = decl_new_generated_var(type_get_ptr(type_get_ptr(type_char)), VARDECL_PARAM, decl->span); } function->has_extname = true; function->func_decl.signature.rtype = type_infoid(type_info_new_base(type_cint, decl->span)); function->func_decl.signature.vararg_index = is_winmain ? 3 : 2; Decl **main_params = NULL; vec_add(main_params, param1); vec_add(main_params, param2); if (param3) vec_add(main_params, param3); function->func_decl.signature.params = main_params; Ast *body = new_ast(AST_COMPOUND_STMT, decl->span); AstId *next = &body->compound_stmt.first_stmt; Ast *ret_stmt = new_ast(AST_RETURN_STMT, decl->span); int type = int_return ? 1 : (err_return ? 2 : 0); const char *main_invoker; switch (main) { case MAIN_TYPE_ARGS: if (is_winmain) { switch (type) { case 0 : main_invoker = "@win_to_void_main_args"; goto NEXT; case 1 : main_invoker = "@win_to_int_main_args"; goto NEXT; case 2 : main_invoker = "@win_to_err_main_args"; goto NEXT; default: UNREACHABLE } } else if (is_wmain) { switch (type) { case 0 : main_invoker = "@wmain_to_void_main_args"; goto NEXT; case 1 : main_invoker = "@wmain_to_int_main_args"; goto NEXT; case 2 : main_invoker = "@wmain_to_err_main_args"; goto NEXT; default: UNREACHABLE } } else { switch (type) { case 0 : main_invoker = "@main_to_void_main_args"; goto NEXT; case 1 : main_invoker = "@main_to_int_main_args"; goto NEXT; case 2 : main_invoker = "@main_to_err_main_args"; goto NEXT; default: UNREACHABLE } } case MAIN_TYPE_NO_ARGS: assert(!is_wmain); if (is_winmain) { switch (type) { case 0 : main_invoker = "@win_to_void_main_noargs"; goto NEXT; case 1 : main_invoker = "@win_to_int_main_noargs"; goto NEXT; case 2 : main_invoker = "@win_to_err_main_noargs"; goto NEXT; default: UNREACHABLE } } switch (type) { case 0 : main_invoker = "@main_to_void_main"; goto NEXT; case 1 : main_invoker = "@main_to_int_main"; goto NEXT; case 2 : main_invoker = "@main_to_err_main"; goto NEXT; default: UNREACHABLE } case MAIN_TYPE_WIN: assert(is_winmain); switch (type) { case 0 : main_invoker = "@win_to_void_main"; goto NEXT; case 1 : main_invoker = "@win_to_int_main"; goto NEXT; case 2 : main_invoker = "@win_to_err_main"; goto NEXT; default: UNREACHABLE } default: UNREACHABLE; } NEXT:; const char *kw_main_invoker = symtab_preset(main_invoker, TOKEN_AT_IDENT); Decl *d = sema_find_symbol(context, kw_main_invoker); if (!d) { SEMA_ERROR(decl, "Missing main forwarding function '%s'.", kw_main_invoker); return poisoned_decl; } Expr *invoker = expr_new(EXPR_IDENTIFIER, decl->span); expr_resolve_ident(invoker, d); Expr *call = expr_new(EXPR_CALL, decl->span); Expr *fn_ref = expr_variable(decl); vec_add(call->call_expr.arguments, fn_ref); vec_add(call->call_expr.arguments, expr_variable(param1)); vec_add(call->call_expr.arguments, expr_variable(param2)); if (param3) vec_add(call->call_expr.arguments, expr_variable(param3)); call->call_expr.function = exprid(invoker); param1->resolve_status = RESOLVE_NOT_DONE; param2->resolve_status = RESOLVE_NOT_DONE; if (param3) param3->resolve_status = RESOLVE_NOT_DONE; ast_append(&next, ret_stmt); ret_stmt->return_stmt.expr = call; function->func_decl.body = astid(body); function->is_synthetic = true; return function; } static inline bool sema_analyse_main_function(SemaContext *context, Decl *decl) { assert(decl != context->unit->main_function); bool is_winmain = decl->func_decl.attr_winmain; bool is_win32 = platform_target.os == OS_TYPE_WIN32; if (decl->visibility != VISIBLE_PUBLIC) { SEMA_ERROR(decl, "A main function must be public."); return false; } Signature *signature = &decl->func_decl.signature; TypeInfo *rtype_info = type_infoptr(signature->rtype); Type *rtype = rtype_info->type; bool is_int_return = true; bool is_err_return = false; if (!is_err_return && type_is_optional(rtype)) { if (rtype->optional->type_kind != TYPE_VOID) { SEMA_ERROR(rtype_info, "The return type of 'main' cannot be an optional, unless it is 'void!'."); return false; } is_int_return = false; is_err_return = true; } if (type_is_void(rtype)) is_int_return = false; if (is_int_return && type_flatten(rtype) != type_cint) { RETURN_SEMA_ERROR(rtype_info, "Expected a return type of 'void' or %s.", type_quoted_error_string(type_cint)); } // At this point the style is either MAIN_INT_VOID, MAIN_VOID_VOID or MAIN_ERR_VOID MainType type = sema_find_main_type(context, signature, is_winmain); if (type == MAIN_TYPE_ERROR) return false; if (active_target.type == TARGET_TYPE_TEST || active_target.type == TARGET_TYPE_BENCHMARK) return true; Decl *function; if (active_target.no_entry) { function = decl; goto REGISTER_MAIN; } if (type == MAIN_TYPE_RAW && !is_int_return) { RETURN_SEMA_ERROR(rtype_info, "Int return is required for a C style main."); } // Suppress winmain on non-win32 if (platform_target.os != OS_TYPE_WIN32) is_winmain = false; if ((type == MAIN_TYPE_RAW || type == MAIN_TYPE_NO_ARGS) && is_int_return && !is_winmain) { // Int return is pass-through at the moment. decl->is_export = true; decl->has_extname = true; decl->extname = kw_main; function = decl; goto REGISTER_MAIN; } bool use_wmain = is_win32 && !is_winmain && type != MAIN_TYPE_NO_ARGS; active_target.win.use_win_subsystem = is_winmain && is_win32; function = sema_create_synthetic_main(context, decl, type, is_int_return, is_err_return, is_winmain, use_wmain); if (!decl_ok(function)) return false; REGISTER_MAIN: context->unit->main_function = function; if (global_context.main) { SEMA_ERROR(function, "Duplicate main functions found."); SEMA_NOTE(global_context.main, "The first one was found here."); return false; } else { global_context.main = function; } return true; } static inline bool sema_analyse_func_macro(SemaContext *context, Decl *decl, AttributeDomain domain, bool *erase_decl) { assert((domain & CALLABLE_TYPE) == domain); if (!sema_analyse_attributes(context, decl, decl->attributes, domain, erase_decl)) return decl_poison(decl); return true; } static inline bool sema_analyse_func(SemaContext *context, Decl *decl, bool *erase_decl) { DEBUG_LOG(">>> Analyse function [%s] in %s", decl_safe_name(decl), context->unit->file->full_path); bool is_interface_method = decl->func_decl.attr_interface_method; if (!sema_analyse_func_macro(context, decl, is_interface_method ? ATTR_INTERFACE_METHOD : ATTR_FUNC, erase_decl)) return false; if (*erase_decl) return true; bool is_test = decl->func_decl.attr_test; bool is_benchmark = decl->func_decl.attr_benchmark; bool is_init_finalizer = decl->func_decl.attr_init || decl->func_decl.attr_finalizer; Signature *sig = &decl->func_decl.signature; if (is_init_finalizer && (is_test || is_benchmark)) { RETURN_SEMA_ERROR(decl, "Test and benchmark functions may not also be marked '@init' or '@finalizer'."); } if (is_test || is_benchmark || is_init_finalizer) { assert(!is_interface_method); if (vec_size(sig->params)) { RETURN_SEMA_ERROR(sig->params[0], "%s functions may not take any parameters.", is_init_finalizer ? "'@init' and '@finalizer'" : "'@test' and '@benchmark'"); } TypeInfo *rtype_info = type_infoptr(sig->rtype); if (!sema_resolve_type_info(context, rtype_info, RESOLVE_TYPE_DEFAULT)) return false; Type *rtype = rtype_info->type; if (is_init_finalizer) { if (rtype->canonical != type_void) { RETURN_SEMA_ERROR(rtype_info, "'@init' and '@finalizer' functions may only return 'void'."); } } else { if (type_no_optional(rtype) != type_void) { RETURN_SEMA_ERROR(rtype_info, "'@test' and '@benchmark' functions may only return 'void' or 'void!'."); } if (type_is_void(rtype)) { rtype_info->type = type_get_optional(rtype); } } } decl->type = type_new_func(decl, sig); if (!sema_analyse_function_signature(context, decl, sig->abi, sig)) return decl_poison(decl); TypeInfo *rtype_info = type_infoptr(sig->rtype); assert(rtype_info); Type *rtype = rtype_info->type->canonical; if (sig->attrs.nodiscard) { if (type_is_void(rtype)) { RETURN_SEMA_ERROR(rtype_info, "@nodiscard cannot be used on functions returning 'void'."); } } if (sig->attrs.maydiscard && !type_is_optional(rtype)) { RETURN_SEMA_ERROR(rtype_info, "@maydiscard can only be used on functions returning optional values."); } if (decl->func_decl.type_parent) { if (!sema_analyse_method(context, decl)) return false; } else if (!is_interface_method) { if (decl->func_decl.attr_dynamic) RETURN_SEMA_ERROR(decl, "Only methods may implement interfaces."); if (decl->name == kw_main) { if (is_test || is_benchmark) { RETURN_SEMA_ERROR(decl, "The main function may not be annotated %s.", is_test ? "@test" : "@benchmark"); } if (!sema_analyse_main_function(context, decl)) return false; } } // Do we have fn void any.foo(void*) { ... }? if (!decl->func_decl.body && !decl->is_extern && !decl->unit->is_interface_file && !is_interface_method) { RETURN_SEMA_ERROR(decl, "Expected a function body, if you want to declare an extern function use " "'extern' or place it in an .c3i file."); } bool pure = false; if (!sema_analyse_doc_header(context, decl->func_decl.docs, decl->func_decl.signature.params, NULL, &pure)) return false; decl->func_decl.signature.attrs.is_pure = pure; if (!sema_set_alloca_alignment(context, decl->type, &decl->alignment)) return false; DEBUG_LOG("<<< Function analysis of [%s] successful.", decl_safe_name(decl)); return true; } static inline bool sema_is_valid_method_param(SemaContext *context, Decl *param, Type *parent_type, bool is_dynamic) { assert(parent_type->canonical == parent_type && "Expected already the canonical version."); Type *param_type = param->type; if (!param_type) goto ERROR; param_type = param_type->canonical; if (is_dynamic) { if (param_type->type_kind != TYPE_POINTER || param_type->pointer != parent_type) { RETURN_SEMA_ERROR(type_infoptr(param->var.type_info), "The first parameter must always be a pointer for '@dynamic' methods, " "so please change its type to %s if possible.", type_quoted_error_string(type_get_ptr(parent_type))); } return true; } // 1. Same type ok! if (param_type == parent_type) return true; switch (param_type->type_kind) { case TYPE_POINTER: if (param_type->pointer == parent_type) return true; break; default: break; } ERROR: RETURN_SEMA_ERROR(type_infoptr(param->var.type_info), "The first parameter of a method must be of type %s or %s.", type_quoted_error_string(parent_type), type_quoted_error_string(type_get_ptr(parent_type))); } static bool sema_analyse_macro_method(SemaContext *context, Decl *decl) { // Resolve the type of the method. TypeInfo *parent_type_info = type_infoptr(decl->func_decl.type_parent); if (!sema_resolve_type_info(context, parent_type_info, RESOLVE_TYPE_MACRO_METHOD)) return false; // Can the type have methods? Type *parent_type = parent_type_info->type; if (!type_may_have_method(parent_type)) { RETURN_SEMA_ERROR(parent_type_info, "Methods can not be associated with '%s'", type_to_error_string(parent_type)); } // We need at least one argument (the parent type) if (!vec_size(decl->func_decl.signature.params)) { RETURN_SEMA_ERROR(decl, "Expected at least one parameter - of type '%s'.", type_to_error_string(parent_type)); } // Check the first argument. Decl *first_param = decl->func_decl.signature.params[0]; if (!first_param) { SEMA_ERROR(decl, "The first parameter to this method must be of type '%s'.", type_to_error_string(parent_type)); return false; } if (!sema_is_valid_method_param(context, first_param, parent_type->canonical, false)) return false; if (first_param->var.kind != VARDECL_PARAM_REF && first_param->var.kind != VARDECL_PARAM) { RETURN_SEMA_ERROR(first_param, "The first parameter must be a regular or ref (&) type."); } return unit_add_method(context, parent_type->canonical, decl); } INLINE bool sema_analyse_macro_body(SemaContext *context, Decl **body_parameters) { unsigned body_param_count = vec_size(body_parameters); for (unsigned i = 0; i < body_param_count; i++) { assert(body_parameters); Decl *param = body_parameters[i]; assert(param); param->resolve_status = RESOLVE_RUNNING; assert(param->decl_kind == DECL_VAR); TypeInfo *type_info = type_infoptrzero(param->var.type_info); VarDeclKind kind = param->var.kind; switch (kind) { case VARDECL_PARAM: case VARDECL_PARAM_EXPR: case VARDECL_PARAM_CT: case VARDECL_PARAM_REF: case VARDECL_PARAM_CT_TYPE: if (!type_info) break; if (!sema_resolve_type_info(context, type_info, RESOLVE_TYPE_DEFAULT)) return false; if (kind != VARDECL_PARAM_REF || type_is_pointer(type_info->type)) break; RETURN_SEMA_ERROR(type_info, "A pointer type was expected for a ref argument, did you mean %s?", type_quoted_error_string(type_get_ptr(type_info->type))); case VARDECL_CONST: case VARDECL_GLOBAL: case VARDECL_LOCAL: case VARDECL_MEMBER: case VARDECL_BITMEMBER: case VARDECL_LOCAL_CT: case VARDECL_LOCAL_CT_TYPE: case VARDECL_UNWRAPPED: case VARDECL_REWRAPPED: case VARDECL_ERASE: UNREACHABLE } if (!sema_check_param_uniqueness_and_type(context, body_parameters, param, i, body_param_count)) return false; param->resolve_status = RESOLVE_DONE; } return true; } static inline bool sema_analyse_macro(SemaContext *context, Decl *decl, bool *erase_decl) { decl->func_decl.unit = context->unit; if (!sema_analyse_func_macro(context, decl, ATTR_MACRO, erase_decl)) return false; if (*erase_decl) return true; if (!sema_analyse_signature(context, &decl->func_decl.signature, decl->func_decl.type_parent, false)) return decl_poison(decl); if (!decl->func_decl.signature.is_at_macro && decl->func_decl.body_param && !decl->func_decl.signature.is_safemacro) { SEMA_ERROR(decl, "Names of macros with a trailing body must start with '@'."); return decl_poison(decl); } DeclId body_param = decl->func_decl.body_param; Decl **body_parameters = body_param ? declptr(body_param)->body_params : NULL; if (!sema_analyse_macro_body(context, body_parameters)) return decl_poison(decl); bool pure = false; if (!sema_analyse_doc_header(context, decl->func_decl.docs, decl->func_decl.signature.params, body_parameters, &pure)) return decl_poison(decl); if (decl->func_decl.type_parent) { if (!sema_analyse_macro_method(context, decl)) return decl_poison(decl); } decl->type = type_void; return true; } static bool sema_analyse_attributes_for_var(SemaContext *context, Decl *decl, bool *erase_decl) { AttributeDomain domain; switch (decl->var.kind) { case VARDECL_CONST: domain = ATTR_CONST; break; case VARDECL_GLOBAL: domain = ATTR_GLOBAL; break; case VARDECL_PARAM: case VARDECL_PARAM_REF: case VARDECL_PARAM_CT_TYPE: case VARDECL_PARAM_CT: domain = ATTR_PARAM; break; default: domain = ATTR_LOCAL; break; } if (!sema_analyse_attributes(context, decl, decl->attributes, domain, erase_decl)) return decl_poison(decl); return true; } static bool sema_analyse_decl_type(SemaContext *context, Type *type, SourceSpan span) { switch (type_storage_type(type)) { case STORAGE_NORMAL: return true; case STORAGE_VOID: case STORAGE_WILDCARD: if (type_is_optional(type)) { sema_error_at(context, span, "The use of 'void!' as a variable type is not permitted, use %s instead.", type_quoted_error_string(type_anyfault)); } else { sema_error_at(context, span, "The use of 'void' as a variable type is not permitted."); } return false; case STORAGE_COMPILE_TIME: sema_error_at(context, span, "The variable cannot have an compile time %s type.", type_quoted_error_string(type)); return false; case STORAGE_UNKNOWN: sema_error_at(context, span, "%s has unknown size, and so it cannot be a variable type.", type_quoted_error_string(type)); return false; } UNREACHABLE } /** * Analyse $foo and $Foo variables. */ bool sema_analyse_var_decl_ct(SemaContext *context, Decl *decl) { Expr *init; assert(decl->decl_kind == DECL_VAR && "Should only be called on variables."); // Grab the optional type_info. TypeInfo *type_info = vartype(decl); switch (decl->var.kind) { case VARDECL_LOCAL_CT_TYPE: // Locally declared compile time type. if (type_info) { SEMA_ERROR(type_info, "Compile time type variables may not have a type."); goto FAIL; } // Check initialization. if ((init = decl->var.init_expr)) { // Try to fold any constant into an lvalue. if (!sema_analyse_expr_lvalue_fold_const(context, init)) goto FAIL; // If this isn't a type, it's an error. if (init->expr_kind != EXPR_TYPEINFO) { SEMA_ERROR(decl->var.init_expr, "Expected a type assigned to %s.", decl->name); goto FAIL; } } // Otherwise we're done. break; case VARDECL_LOCAL_CT: // Resolve the type if it's present. if (type_info) { if (!sema_resolve_type_info(context, type_info, RESOLVE_TYPE_DEFAULT)) goto FAIL; // Set the type of the declaration. decl->type = type_info->type->canonical; init = decl->var.init_expr; // If there is no init, set it to zero. if (!init) { decl->var.init_expr = init = expr_new(EXPR_POISONED, decl->span); expr_rewrite_to_const_zero(init, decl->type); } // Analyse the expression. if (!sema_analyse_expr_rhs(context, decl->type, init, false, NULL)) goto FAIL; // Check that it is constant. if (!expr_is_constant_eval(init, CONSTANT_EVAL_CONSTANT_VALUE)) { SEMA_ERROR(init, "Expected a constant expression assigned to %s.", decl->name); goto FAIL; } break; } // If we don't have a type, resolve the expression. if ((init = decl->var.init_expr)) { if (!sema_analyse_expr(context, init)) goto FAIL; // Check it is constant. if (!expr_is_constant_eval(init, CONSTANT_EVAL_CONSTANT_VALUE)) { SEMA_ERROR(init, "Expected a constant expression assigned to %s.", decl->name); goto FAIL; } // Update the type. decl->type = init->type; break; } // Here we set a void type if both init and type is missing. decl->type = type_void; break; default: UNREACHABLE } return sema_add_local(context, decl); FAIL: sema_add_local(context, decl); return decl_poison(decl); } /** * Analyse a regular global or local declaration, e.g. int x = 123 */ bool sema_analyse_var_decl(SemaContext *context, Decl *decl, bool local) { assert(decl->decl_kind == DECL_VAR && "Unexpected declaration type"); VarDeclKind kind = decl->var.kind; bool is_global = !local; switch (kind) { case VARDECL_LOCAL_CT: case VARDECL_LOCAL_CT_TYPE: return sema_analyse_var_decl_ct(context, decl); case VARDECL_GLOBAL: is_global = true; break; case VARDECL_CONST: default: break; } TypeInfo *type_info = vartype(decl); // We expect a constant to actually be parsed correctly so that it has a value, so // this should always be true. assert(type_info || decl->var.init_expr); if (is_global) { } else { if (context->call_env.kind == CALL_ENV_GLOBAL_INIT) { if (context->current_macro) { SEMA_ERROR(decl, "Macros with declarations may not be used outside of functions."); return decl_poison(decl); } SEMA_ERROR(decl, "Variable declarations may not be used outside of functions."); return decl_poison(decl); } // Add a local to the current context, will throw error on shadowing. if (!sema_add_local(context, decl)) return decl_poison(decl); } bool erase_decl = false; if (!sema_analyse_attributes_for_var(context, decl, &erase_decl)) return decl_poison(decl); bool is_static = decl->var.is_static; bool global_level_var = is_static || decl->var.kind == VARDECL_CONST || is_global; if (decl->is_extern && decl->var.init_expr) { assert(is_global); SEMA_ERROR(decl->var.init_expr, "Extern globals may not have initializers."); return decl_poison(decl); } if (erase_decl) { decl->decl_kind = DECL_ERASED; decl->resolve_status = RESOLVE_DONE; return true; } // 1. Local or global constants: const int FOO = 123. if (!type_info) { Expr *init_expr = decl->var.init_expr; // 1a. We require an init expression. if (!init_expr) { assert(kind == VARDECL_CONST); SEMA_ERROR(decl, "Constants need to have an initial value."); return decl_poison(decl); } if (kind == VARDECL_LOCAL && !context->current_macro) { SEMA_ERROR(decl, "Defining a variable using 'var %s = ...' is only allowed inside a macro.", decl->name); return decl_poison(decl); } assert(!decl->var.no_init); if (!type_info) { if (!sema_analyse_expr(context, init_expr)) return decl_poison(decl); if (global_level_var && !expr_is_constant_eval(init_expr, CONSTANT_EVAL_GLOBAL_INIT)) { SEMA_ERROR(init_expr, "This expression cannot be evaluated at compile time."); return decl_poison(decl); } decl->type = init_expr->type; switch (type_storage_type(init_expr->type)) { case STORAGE_NORMAL: break; case STORAGE_WILDCARD: SEMA_ERROR(init_expr, "No type can be inferred from the optional result."); return decl_poison(decl); case STORAGE_VOID: SEMA_ERROR(init_expr, "You cannot initialize a value to 'void'."); return decl_poison(decl); case STORAGE_COMPILE_TIME: if (init_expr->type == type_untypedlist) { SEMA_ERROR(init_expr, "The type of an untyped list cannot be inferred, you can try adding an explicit type to solve this."); return decl_poison(decl); } if (decl->var.kind == VARDECL_CONST) { SEMA_ERROR(init_expr, "You cannot initialize a constant to %s, but you can assign the expression to a compile time variable.", type_invalid_storage_type_name(init_expr->type)); return decl_poison(decl); } SEMA_ERROR(init_expr, "You can't store a compile time type in a variable."); return decl_poison(decl); case STORAGE_UNKNOWN: SEMA_ERROR(init_expr, "You cannot initialize a value to %s as it has unknown size.", type_quoted_error_string(init_expr->type)); return decl_poison(decl); } if (!decl->alignment) { if (!sema_set_alloca_alignment(context, decl->type, &decl->alignment)) return false; } if (!sema_analyse_decl_type(context, decl->type, init_expr->span)) return decl_poison(decl); // Skip further evaluation. goto EXIT_OK; } } if (!sema_resolve_type_info(context, type_info, decl->var.init_expr ? RESOLVE_TYPE_ALLOW_INFER : RESOLVE_TYPE_DEFAULT)) return decl_poison(decl); Type *type = decl->type = type_info->type; if (!sema_analyse_decl_type(context, decl->type, type_info->span)) return decl_poison(decl); type = type_no_optional(type); if (type_is_user_defined(type) && type->decl) { sema_display_deprecated_warning_on_use(context, type->decl, type_info->span); } if (is_static && context->call_env.pure) { SEMA_ERROR(decl, "'@pure' functions may not have static variables."); return decl_poison(decl); } bool infer_len = type_len_is_inferred(decl->type); if (!decl->var.init_expr && infer_len) { SEMA_ERROR(type_info, "The length cannot be inferred without an initializer."); return decl_poison(decl); } if (decl->var.init_expr) { Expr *init = decl->var.init_expr; if (!infer_len) { // Pre resolve to avoid problem with recursive definitions. decl->resolve_status = RESOLVE_DONE; if (!decl->alignment) { if (!sema_set_alloca_alignment(context, decl->type, &decl->alignment)) return false; } } CallEnvKind env_kind = context->call_env.kind; if (is_static) context->call_env.kind = CALL_ENV_GLOBAL_INIT; if (!sema_expr_analyse_assign_right_side(context, NULL, decl->type, init, false)) { context->call_env.kind = env_kind; return decl_poison(decl); } context->call_env.kind = env_kind; if (infer_len) { decl->type = type_add_optional(init->type, IS_OPTIONAL(decl)); } Expr *init_expr = decl->var.init_expr; // 2. Check const-ness if (global_level_var && !expr_is_constant_eval(init_expr, CONSTANT_EVAL_GLOBAL_INIT)) { SEMA_ERROR(init_expr, "The expression must be a constant value."); return decl_poison(decl); } if (expr_is_const(init_expr)) { init_expr->const_expr.is_hex = false; } } EXIT_OK:; // Patch the external name for local consts and static variables. if ((decl->var.kind == VARDECL_CONST || is_static) && !decl->extname && context->call_env.kind == CALL_ENV_FUNCTION) { scratch_buffer_clear(); scratch_buffer_append(context->call_env.current_function->name); scratch_buffer_append_char('.'); scratch_buffer_append(decl->name); decl->extname = scratch_buffer_copy(); } if (!decl->alignment) { if (!sema_set_alloca_alignment(context, decl->type, &decl->alignment)) return false; } return true; } static CompilationUnit *unit_copy(Module *module, CompilationUnit *unit) { CompilationUnit *copy = unit_create(unit->file); copy->imports = copy_decl_list_single(unit->imports); copy->global_decls = copy_decl_list_single_for_unit(unit->global_decls); copy->global_cond_decls = copy_decl_list_single_for_unit(unit->global_cond_decls); copy->module = module; assert(!unit->functions && !unit->macro_methods && !unit->methods && !unit->enums && !unit->ct_includes && !unit->types); return copy; } static Module *module_instantiate_generic(SemaContext *context, Module *module, Path *path, Expr **params, SourceSpan from) { unsigned decls = 0; Decl* params_decls[MAX_PARAMS]; unsigned count = vec_size(module->parameters); for (unsigned i = 0; i < count; i++) { const char *param_name = module->parameters[i]; bool is_value = str_is_valid_constant(param_name); Expr *param = params[i]; if (param->expr_kind != EXPR_TYPEINFO) { if (!is_value) { SEMA_ERROR(param, "Expected a type, not a value."); return NULL; } Decl *decl = decl_new_var(param_name, param->span, NULL, VARDECL_CONST); decl->var.init_expr = param; decl->type = param->type; decl->resolve_status = RESOLVE_NOT_DONE; params_decls[decls++] = decl; continue; } if (is_value) { SEMA_ERROR(param, "Expected a value, not a type."); return NULL; } TypeInfo *type_info = param->type_expr; if (!sema_resolve_type_info(context, type_info, RESOLVE_TYPE_DEFAULT)) return false; Decl *decl = decl_new_with_type(param_name, params[i]->span, DECL_TYPEDEF); decl->resolve_status = RESOLVE_DONE; assert(type_info->resolve_status == RESOLVE_DONE); decl->typedef_decl.type_info = type_info; decl->type->name = decl->name; decl->type->canonical = type_info->type->canonical; params_decls[decls++] = decl; } Module *new_module = compiler_find_or_create_module(path, NULL); new_module->is_generic = false; new_module->generic_module = module; FOREACH(CompilationUnit *, unit, module->units) { vec_add(new_module->units, unit_copy(new_module, unit)); } CompilationUnit *first_context = new_module->units[0]; for (unsigned i = 0; i < decls; i++) { vec_add(first_context->global_decls, params_decls[i]); } if (module->contracts) { copy_begin(); new_module->contracts = astid(copy_ast_macro(astptr(module->contracts))); copy_end(); } return new_module; } static bool sema_generate_parameterized_name_to_scratch(SemaContext *context, Module *module, Expr **params, bool mangled) { // First resolve FOREACH_IDX(i, Expr *, param, params) { if (param->expr_kind == EXPR_TYPEINFO) { TypeInfo *type_info = param->type_expr; if (!sema_resolve_type_info(context, type_info, RESOLVE_TYPE_DEFAULT)) return false; Type *type = type_info->type->canonical; if (type->type_kind == TYPE_OPTIONAL) RETURN_SEMA_ERROR(type_info, "Expected a non-optional type."); switch (type_storage_type(type)) { case STORAGE_NORMAL: break; case STORAGE_VOID: RETURN_SEMA_ERROR(type_info, "A 'void' type cannot be used as a parameter type."); case STORAGE_WILDCARD: RETURN_SEMA_ERROR(type_info, "The type is undefined and cannot be used as a parameter type."); case STORAGE_COMPILE_TIME: RETURN_SEMA_ERROR(type_info, "Expected a runtime type."); case STORAGE_UNKNOWN: RETURN_SEMA_ERROR(type_info, "%s doesn't have a well defined size and cannot be used as a parameter type.", type_quoted_error_string(type)); } if (type_is_func_ptr(type)) { if (!sema_resolve_type_decl(context, type->pointer)) return false; } } else { if (!sema_analyse_ct_expr(context, param)) return false; Type *type = param->type->canonical; bool is_enum_like = type_kind_is_enumlike(type->type_kind); if (!type_is_integer_or_bool_kind(type) && !is_enum_like) { SEMA_ERROR(param, "Only integer, bool, fault and enum values may be generic arguments."); return poisoned_decl; } assert(expr_is_const(param)); } } scratch_buffer_clear(); if (mangled) { scratch_buffer_append_module(module, true); scratch_buffer_append("$"); } else { scratch_buffer_append("(<"); } FOREACH_IDX(j, Expr *, param, params) { if (j != 0) { scratch_buffer_append(mangled ? "$" : ", "); } if (param->expr_kind == EXPR_TYPEINFO) { Type *type = param->type_expr->type->canonical; if (mangled) { type_mangle_introspect_name_to_buffer(type); } else { scratch_buffer_append(type->name); } } else { Type *type = param->type->canonical; bool is_enum_like = type_kind_is_enumlike(type->type_kind); if (type == type_bool) { if (mangled) { scratch_buffer_append_char(param->const_expr.b ? 't' : 'f'); } else { scratch_buffer_append(param->const_expr.b ? "true" : "false"); } } else if (is_enum_like) { Decl *enum_like = param->const_expr.enum_err_val; type_mangle_introspect_name_to_buffer(enum_like->type->canonical); scratch_buffer_append(mangled ? "_" : ":"); scratch_buffer_append(enum_like->name); } else { char *maybe_neg = &scratch_buffer.str[scratch_buffer.len]; if (type->type_kind == TYPE_I128 || type->type_kind == TYPE_U128) { char *str = int_to_str(param->const_expr.ixx, 10); scratch_buffer_append(str); } else { if (type_is_signed(type)) { scratch_buffer_append_signed_int(param->const_expr.ixx.i.low); } else { scratch_buffer_append_unsigned_int(param->const_expr.ixx.i.low); } } if (mangled) { // Replace - with _ if (maybe_neg[0] == '-') maybe_neg[0] = '_'; } } } } scratch_buffer_append(mangled ? "$" : ">)"); return true; } static bool sema_analyse_generic_module_contracts(SemaContext *c, Module *module, SourceSpan error_span) { assert(module->contracts); AstId contract = module->contracts; while (contract) { Ast *ast = astptr(contract); contract = ast->next; assert(ast->ast_kind == AST_CONTRACT); SemaContext temp_context; assert(ast->contract_stmt.kind == CONTRACT_REQUIRE); SemaContext *new_context = context_transform_for_eval(c, &temp_context, module->units[0]); FOREACH(Expr *, expr, ast->contract_stmt.contract.decl_exprs->expression_list) { CondResult res = sema_check_comp_time_bool(new_context, expr); if (res == COND_MISSING) goto FAIL; if (res == COND_TRUE) continue; if (ast->contract_stmt.contract.comment) { sema_error_at(c, error_span, "Parameter(s) would violate constraint: %s.", ast->contract_stmt.contract.comment); } else { sema_error_at(c, error_span, "Parameter(s) failed validation: %s", ast->contract_stmt.contract.expr_string); } FAIL: sema_context_destroy(&temp_context); return false; } sema_context_destroy(&temp_context); } return true; } static bool sema_analyse_parameterized_define(SemaContext *c, Decl *decl) { Path *decl_path; const char *name; SourceSpan span; switch (decl->define_decl.define_kind) { case DEFINE_IDENT_GENERIC: decl_path = decl->define_decl.path; name = decl->define_decl.ident; span = decl->define_decl.span; break; default: UNREACHABLE } Expr **params = decl->define_decl.generic_params; Decl *symbol = sema_analyse_parameterized_identifier(c, decl_path, name, span, params); if (!decl_ok(symbol)) return decl_poison(decl); switch (decl->define_decl.define_kind) { case DEFINE_IDENT_GENERIC: decl->define_decl.alias = symbol; decl->type = symbol->type; return true; default: UNREACHABLE } } Decl *sema_analyse_parameterized_identifier(SemaContext *c, Path *decl_path, const char *name, SourceSpan span, Expr **params) { NameResolve name_resolve = { .path = decl_path, .span = span, .symbol = name }; Decl *alias = unit_resolve_parameterized_symbol(NULL, c->unit, &name_resolve); if (!decl_ok(alias)) return poisoned_decl; Module *module = decl_module(alias); unsigned parameter_count = vec_size(module->parameters); assert(parameter_count > 0); if (parameter_count != vec_size(params)) { assert(vec_size(params)); sema_error_at(c, extend_span_with_token(params[0]->span, vectail(params)->span), "The generic module expected %d arguments, but you supplied %d, did you make a mistake?", parameter_count, vec_size(params)); return poisoned_decl; } if (!sema_generate_parameterized_name_to_scratch(c, module, params, true)) return poisoned_decl; TokenType ident_type = TOKEN_IDENT; const char *path_string = scratch_buffer_interned(); Module *instantiated_module = global_context_find_module(path_string); bool was_initiated = false; if (!instantiated_module) { was_initiated = true; Path *path = CALLOCS(Path); path->module = path_string; path->span = module->name->span; path->len = scratch_buffer.len; instantiated_module = module_instantiate_generic(c, module, path, params, span); if (!sema_generate_parameterized_name_to_scratch(c, module, params, false)) return poisoned_decl; if (!instantiated_module) return poisoned_decl; instantiated_module->generic_suffix = scratch_buffer_copy(); if (c->unit->module->generic_module) { sema_analyze_stage(instantiated_module, c->unit->module->stage); } else { sema_analyze_stage(instantiated_module, c->unit->module->stage - 1); } } if (global_context.errors_found) return poisoned_decl; Decl *symbol = module_find_symbol(instantiated_module, name); if (!symbol) { sema_error_at(c, span, "The generic module '%s' does not have '%s' for this parameterization.", module->name->module, name); return poisoned_decl; } if (was_initiated && instantiated_module->contracts) { SourceSpan error_span = extend_span_with_token(params[0]->span, params[parameter_count - 1]->span); if (!sema_analyse_generic_module_contracts(c, instantiated_module, error_span)) { return poisoned_decl; } } if (!sema_analyse_decl(c, symbol)) return poisoned_decl; unit_register_external_symbol(c->compilation_unit, symbol); return symbol; } static inline bool sema_analyse_attribute_decl(SemaContext *context, SemaContext *c, Decl *decl, bool *erase_decl) { if (!sema_analyse_attributes(c, decl, decl->attributes, ATTR_DEF, erase_decl)) return decl_poison(decl); if (*erase_decl) return true; Decl **params = decl->attr_decl.params; unsigned param_count = vec_size(params); for (unsigned i = 0; i < param_count; i++) { Decl *param = params[i]; if (param->var.kind != VARDECL_PARAM) RETURN_SEMA_ERROR(param, "Expected a simple replacement parameter e.g. 'val' here."); if (param->var.type_info) RETURN_SEMA_ERROR(param, "Type is not allowed on attribute parameters."); if (param->var.init_expr) RETURN_SEMA_ERROR(param, "Attribute parameters may not have default values."); param->resolve_status = RESOLVE_DONE; for (int j = 0; j < i; j++) { if (param[j].name == param->name) { RETURN_SEMA_ERROR(param, "Duplicate parameter name '%s'.", param->name); } } } return true; } static inline bool sema_analyse_define(SemaContext *c, Decl *decl, bool *erase_decl) { if (!sema_analyse_attributes(c, decl, decl->attributes, ATTR_DEF, erase_decl)) return decl_poison(decl); if (*erase_decl) return true; // 1. The plain define if (decl->define_decl.define_kind == DEFINE_IDENT_ALIAS) { Decl *symbol = sema_resolve_symbol(c, decl->define_decl.ident, decl->define_decl.path, decl->define_decl.span); if (!sema_analyse_decl(c, symbol)) return false; decl->type = symbol->type; decl->define_decl.alias = symbol; return true; } // 2. Handle type generics. return sema_analyse_parameterized_define(c, decl); } bool sema_resolve_type_structure(SemaContext *context, Type *type, SourceSpan span) { RETRY: switch (type->type_kind) { case TYPE_INTERFACE: case TYPE_ANY: case TYPE_POISONED: case TYPE_VOID: case TYPE_BOOL: case ALL_INTS: case ALL_FLOATS: case TYPE_ANYFAULT: case TYPE_TYPEID: case TYPE_UNTYPED_LIST: case TYPE_WILDCARD: case TYPE_TYPEINFO: case TYPE_MEMBER: return true; case TYPE_FUNC_RAW: if (!type->decl) return true; FALLTHROUGH; case TYPE_ENUM: case TYPE_STRUCT: case TYPE_UNION: case TYPE_BITSTRUCT: case TYPE_FAULTTYPE: return sema_analyse_decl(context, type->decl); case TYPE_POINTER: case TYPE_FUNC_PTR: type = type->pointer; goto RETRY; case TYPE_TYPEDEF: type = type->canonical; goto RETRY; case TYPE_DISTINCT: if (!sema_analyse_decl(context, type->decl)) return false; type = type->decl->distinct->type; goto RETRY; case TYPE_ARRAY: case TYPE_SLICE: case TYPE_FLEXIBLE_ARRAY: case TYPE_INFERRED_ARRAY: case TYPE_VECTOR: case TYPE_INFERRED_VECTOR: type = type->array.base; goto RETRY; case TYPE_OPTIONAL: type = type->optional; goto RETRY; } UNREACHABLE } bool sema_analyse_decl(SemaContext *context, Decl *decl) { if (decl->resolve_status == RESOLVE_DONE) return decl_ok(decl); DEBUG_LOG(">>> Analyse declaration [%s] in %s.", decl_safe_name(decl), context_filename(context)); SemaContext temp_context; context = context_transform_for_eval(context, &temp_context, decl->unit); if (decl->resolve_status == RESOLVE_RUNNING) { SEMA_ERROR(decl, decl->name ? "Recursive definition of '%s'." : "Recursive definition of anonymous declaration.", decl->name); goto FAILED; } decl->resolve_status = RESOLVE_RUNNING; assert(decl->unit); bool erase_decl = false; switch (decl->decl_kind) { case DECL_ERASED: break; case DECL_INTERFACE: if (!sema_analyse_interface(context, decl, &erase_decl)) goto FAILED; break; case DECL_BITSTRUCT: if (!sema_analyse_bitstruct(context, decl, &erase_decl)) goto FAILED; break; case DECL_STRUCT: case DECL_UNION: if (!sema_analyse_struct_union(context, decl, &erase_decl)) goto FAILED; break; case DECL_FNTYPE: if (!sema_analyse_fntype(context, decl, &erase_decl)) goto FAILED; break; case DECL_FUNC: if (!sema_analyse_func(context, decl, &erase_decl)) goto FAILED; break; case DECL_MACRO: if (!sema_analyse_macro(context, decl, &erase_decl)) goto FAILED; break; case DECL_VAR: if (!sema_analyse_var_decl(context, decl, false)) goto FAILED; break; case DECL_ATTRIBUTE: if (!sema_analyse_attribute_decl(context, context, decl, &erase_decl)) goto FAILED; break; case DECL_DISTINCT: if (!sema_analyse_distinct(context, decl, &erase_decl)) goto FAILED; break; case DECL_TYPEDEF: if (!sema_analyse_typedef(context, decl, &erase_decl)) goto FAILED; break; case DECL_ENUM: if (!sema_analyse_enum(context, decl, &erase_decl)) goto FAILED; break; case DECL_FAULT: if (!sema_analyse_error(context, decl, &erase_decl)) goto FAILED; break; case DECL_DEFINE: if (!sema_analyse_define(context, decl, &erase_decl)) goto FAILED; break; case DECL_POISONED: case DECL_IMPORT: case DECL_ENUM_CONSTANT: case DECL_LABEL: case DECL_CT_ASSERT: case DECL_CT_ECHO: case DECL_FAULTVALUE: case DECL_DECLARRAY: case DECL_BODYPARAM: case DECL_CT_INCLUDE: case DECL_CT_EXEC: case DECL_GLOBALS: UNREACHABLE } if (erase_decl) { decl->decl_kind = DECL_ERASED; } decl->resolve_status = RESOLVE_DONE; sema_context_destroy(&temp_context); DEBUG_LOG("<<< Analysis of [%s] successful.", decl_safe_name(decl)); return true; FAILED: sema_context_destroy(&temp_context); DEBUG_LOG("<<< Analysis of [%s] failed.", decl_safe_name(decl)); return decl_poison(decl); }