diff --git a/releasenotes.md b/releasenotes.md index 27c1fde24..08e3265e4 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -16,6 +16,7 @@ - Improve error on unsigned implicit conversion to signed. - Update error message for struct initialization #2286 - `$is_const` is deprecated in favour of `@is_const` based on `$defined`. +- Multiline contract comments #2113 ### Fixes - mkdir/rmdir would not work properly with substring paths on non-windows platforms. @@ -45,6 +46,7 @@ - Correctly poison the analysis after a failed $assert or $error. #2284 - `$foo` variables could be assigned non-compile time values. - `$foo[0] = ...` was incorrectly requiring that the assigned values were compile time constants. +- "Inlined at" would sometimes show the current location. ### Stdlib changes - Improve contract for readline. #2280 diff --git a/src/compiler/c_codegen.c b/src/compiler/c_codegen.c index d02587438..88998397c 100644 --- a/src/compiler/c_codegen.c +++ b/src/compiler/c_codegen.c @@ -637,9 +637,50 @@ static void c_emit_local_decl(GenContext *c, Decl *decl, CValue *value) value->kind = CV_VALUE; } - PRINT("/* TODO ZERO INIT */\n"); - //llvm_store_zero(c, value); - //llvm_value_set(value, llvm_get_zero(c, var_type), var_type); + switch (var_type->type_kind) + { + case TYPE_BOOL: + PRINTF("___var_%d = false;\n", value->var); + break; + case ALL_INTS: + case ALL_FLOATS: + PRINTF("___var_%d = 0;\n", value->var); + break; + case TYPE_POISONED: + case TYPE_VOID: + case TYPE_DISTINCT: + case TYPE_CONST_ENUM: + case TYPE_FUNC_RAW: + case TYPE_BITSTRUCT: + case TYPE_TYPEDEF: + case TYPE_UNTYPED_LIST: + case TYPE_FLEXIBLE_ARRAY: + case TYPE_INFERRED_ARRAY: + case TYPE_INFERRED_VECTOR: + case TYPE_OPTIONAL: + case TYPE_WILDCARD: + case TYPE_TYPEINFO: + case TYPE_MEMBER: + UNREACHABLE + case TYPE_ANY: + case TYPE_INTERFACE: + PRINTF("___var_%d = (c3_any_t){ NULL, NULL };\n", value->var); + break; + case TYPE_ANYFAULT: + case TYPE_TYPEID: + case TYPE_FUNC_PTR: + case TYPE_POINTER: + case TYPE_ENUM: + PRINTF("___var_%d = 0;\n", value->var); + break; + case TYPE_STRUCT: + case TYPE_UNION: + case TYPE_SLICE: + case TYPE_ARRAY: + case TYPE_VECTOR: + PRINT("/* TODO ZERO INIT */\n"); + + } } static void c_emit_return(GenContext *c, Ast *stmt) @@ -758,9 +799,9 @@ static void c_emit_stmt(GenContext *c, Ast *stmt) case AST_CASE_STMT: break; case AST_COMPOUND_STMT: - PRINT(" {\n"); + PRINT("{\n"); c_emit_stmt_chain(c, stmt->compound_stmt.first_stmt); - PRINT(" }\n"); + PRINT("}\n"); return; case AST_CONTINUE_STMT: break; diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 1c7f5acb9..18df7b91e 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -67,6 +67,8 @@ typedef uint16_t FileId; #define SEMA_NOTE(_node, ...) sema_note_prev_at((_node)->span, __VA_ARGS__) #define SEMA_DEPRECATED(_node, ...) do { if (compiler.build.test_output && !compiler.build.silence_deprecation) print_error_at((_node)->span, __VA_ARGS__); if (!compiler.build.silence_deprecation) \ sema_note_prev_at((_node)->span, __VA_ARGS__); } while (0) +#define PRINT_DEPRECATED_AT(span__, ...) do { if (compiler.build.test_output && !compiler.build.silence_deprecation) print_error_at(span__, __VA_ARGS__); if (!compiler.build.silence_deprecation) \ +sema_note_prev_at(span__, __VA_ARGS__); } while (0) #define EXPAND_EXPR_STRING(str_) (str_)->const_expr.bytes.len, (str_)->const_expr.bytes.ptr #define TABLE_MAX_LOAD 0.5 @@ -2982,7 +2984,7 @@ INLINE bool type_convert_will_trunc(Type *destination, Type *source) // Useful sanity check function. INLINE void advance_and_verify(ParseContext *context, TokenType token_type) { - ASSERT(context->tok == token_type); + ASSERT_SPAN(context, context->tok == token_type); advance(context); } diff --git a/src/compiler/parse_expr.c b/src/compiler/parse_expr.c index 534909a27..a7144454c 100644 --- a/src/compiler/parse_expr.c +++ b/src/compiler/parse_expr.c @@ -1904,20 +1904,16 @@ static Expr *parse_double(ParseContext *c, Expr *left, SourceSpan lhs_start) return number; } -/** - * string_literal ::= STRING+ - */ -static Expr *parse_string_literal(ParseContext *c, Expr *left, SourceSpan lhs_start) -{ - ASSERT(!left && "Had left hand side"); - Expr *expr_string = EXPR_NEW_TOKEN(EXPR_CONST); +bool parse_joined_strings(ParseContext *c, const char **str_ref, size_t *len_ref) +{ const char *str = symstr(c); size_t len = c->data.strlen; advance_and_verify(c, TOKEN_STRING); - + if (!str_ref) scratch_buffer_append(str); // This is wasteful for adding many tokens together // and can be optimized. + if (tok_is(c, TOKEN_DOCS_EOL) && peek(c) == TOKEN_STRING) advance(c); while (tok_is(c, TOKEN_STRING)) { // Grab the token. @@ -1928,21 +1924,44 @@ static Expr *parse_string_literal(ParseContext *c, Expr *left, SourceSpan lhs_st advance_and_verify(c, TOKEN_STRING); continue; } - // Create new string and copy. - char *buffer = malloc_string(len + next_len + 1); - memcpy(buffer, str, len); - memcpy(buffer + len, symstr(c), next_len); - len += next_len; - buffer[len] = '\0'; - str = buffer; + if (!str_ref) + { + scratch_buffer_append(symstr(c)); + } + else + { + // Create new string and copy. + char *buffer = malloc_string(len + next_len + 1); + memcpy(buffer, str, len); + memcpy(buffer + len, symstr(c), next_len); + len += next_len; + buffer[len] = '\0'; + str = buffer; + } advance_and_verify(c, TOKEN_STRING); + if (tok_is(c, TOKEN_DOCS_EOL) && peek(c) == TOKEN_STRING) advance(c); } if (len > UINT32_MAX) { PRINT_ERROR_HERE("String exceeded max size."); - return poisoned_expr; + return false; } + if (!str_ref) return true; ASSERT(str); + *str_ref = str; + *len_ref = len; + return true; +} +/** + * string_literal ::= STRING+ + */ +static Expr *parse_string_literal(ParseContext *c, Expr *left, SourceSpan lhs_start) +{ + ASSERT(!left && "Had left hand side"); + Expr *expr_string = EXPR_NEW_TOKEN(EXPR_CONST); + const char *str; + size_t len; + if (!parse_joined_strings(c, &str, &len)) return poisoned_expr; expr_string->const_expr.bytes.ptr = str; expr_string->const_expr.bytes.len = (uint32_t)len; expr_string->type = type_string; diff --git a/src/compiler/parse_global.c b/src/compiler/parse_global.c index 650c56f11..2a3611c5f 100644 --- a/src/compiler/parse_global.c +++ b/src/compiler/parse_global.c @@ -2756,6 +2756,49 @@ INLINE bool parse_doc_to_eol(ParseContext *c) return false; } +INLINE bool parse_doc_check_skip_string_eos(ParseContext *c) +{ + if (tok_is(c, TOKEN_STRING)) return true; + if (!tok_is(c, TOKEN_DOCS_EOL)) return false; + if (peek(c) != TOKEN_STRING) return false; + advance_and_verify(c, TOKEN_DOCS_EOL); + return true; +} + +INLINE bool parse_docs_to_comment(ParseContext *c) +{ + if (try_consume(c, TOKEN_COLON)) return true; + if (!tok_is(c, TOKEN_DOCS_EOL)) return false; + if (peek(c) == TOKEN_COLON) + { + advance_and_verify(c, TOKEN_DOCS_EOL); + advance_and_verify(c, TOKEN_COLON); + return true; + } + return false; +} + +static bool parse_doc_discarded_comment(ParseContext *c) +{ + if (try_consume(c, TOKEN_STRING)) + { + PRINT_DEPRECATED_AT(c->span, "Not using ':' before the description is deprecated"); + return true; + } + if (!parse_docs_to_comment(c)) return true; + if (!parse_doc_check_skip_string_eos(c)) return true; + return parse_joined_strings(c, NULL, NULL); +} +static bool parse_doc_direct_comment(ParseContext *c) +{ + if (tok_is(c, TOKEN_DOCS_EOL) && peek(c) == TOKEN_STRING) + { + advance(c); + } + if (!tok_is(c, TOKEN_STRING)) return true; + return parse_joined_strings(c, NULL, NULL); +} + /** * contract ::= expression_list (':'? STRING)? */ @@ -2782,9 +2825,11 @@ static inline bool parse_doc_contract(ParseContext *c, AstId *docs, AstId **docs } scratch_buffer_append_remove_space(start, (int)(end - start)); scratch_buffer_append("\" violated"); - if (try_consume(c, TOKEN_COLON)) + bool docs_to_comment = false; + if (parse_docs_to_comment(c)) { - if (!tok_is(c, TOKEN_STRING)) + docs_to_comment = true; + if (!parse_doc_check_skip_string_eos(c)) { print_error_at(c->prev_span, "Expected a string after ':'"); return false; @@ -2793,10 +2838,13 @@ static inline bool parse_doc_contract(ParseContext *c, AstId *docs, AstId **docs if (tok_is(c, TOKEN_STRING)) { scratch_buffer_append(": '"); - scratch_buffer_append(symstr(c)); + if (!parse_joined_strings(c, NULL, NULL)) return false; scratch_buffer_append("'."); ast->contract_stmt.contract.comment = scratch_buffer_copy(); - advance(c); + if (!docs_to_comment) + { + SEMA_DEPRECATED(ast, "Not using ':' before the description is deprecated"); + } } else { @@ -2855,17 +2903,21 @@ static inline bool parse_contract_param(ParseContext *c, AstId *docs, AstId **do case TOKEN_HASH_IDENT: break; default: - PRINT_ERROR_HERE("Expected a parameter name here."); - return false; + RETURN_PRINT_ERROR_HERE("Expected a parameter name here."); } ast->contract_stmt.param.name = symstr(c); ast->contract_stmt.param.span = c->span; ast->contract_stmt.param.modifier = mod; ast->contract_stmt.param.by_ref = is_ref; advance(c); - if (try_consume(c, TOKEN_COLON)) + + if (parse_docs_to_comment(c)) { - CONSUME_OR_RET(TOKEN_STRING, false); + if (!parse_doc_check_skip_string_eos(c)) + { + RETURN_PRINT_ERROR_LAST("Expected a string after ':'"); + } + if (!parse_joined_strings(c, NULL, NULL)) return false; } else { @@ -2910,14 +2962,7 @@ static inline bool parse_doc_optreturn(ParseContext *c, AstId *docs, AstId **doc } RANGE_EXTEND_PREV(ast); // Just ignore our potential string: - if (try_consume(c, TOKEN_COLON)) - { - CONSUME_OR_RET(TOKEN_STRING, false); - } - else if (try_consume(c, TOKEN_STRING)) - { - RETURN_PRINT_ERROR_LAST("Expected a ':' before the description."); - } + if (!parse_doc_discarded_comment(c)) return false; ast->contract_stmt.faults = returns; append_docs(docs_next, docs, ast); return true; @@ -2944,13 +2989,10 @@ static bool parse_contracts(ParseContext *c, AstId *contracts_ref) while (!try_consume(c, TOKEN_DOCS_END)) { - // Skip empty lines. if (try_consume(c, TOKEN_DOCS_EOL)) continue; - if (!tok_is(c, TOKEN_AT_IDENT)) { - PRINT_ERROR_HERE("Expected a directive starting with '@' here, like '@param' or `@require`"); - return false; + RETURN_PRINT_ERROR_HERE("Expected a directive starting with '@' here, like '@param' or `@require`"); } const char *name = symstr(c); if (name == kw_at_param) @@ -2966,13 +3008,13 @@ static bool parse_contracts(ParseContext *c, AstId *contracts_ref) } else { - if (!consume(c, TOKEN_STRING, "Expected a string description.")) return false; + if (!parse_doc_direct_comment(c)) return false; } } else if (name == kw_at_deprecated) { advance(c); - (void)try_consume(c, TOKEN_STRING); + if (!parse_doc_discarded_comment(c)) return false; REMINDER("Implement @deprecated tracking"); } else if (name == kw_at_require) @@ -2987,17 +3029,19 @@ static bool parse_contracts(ParseContext *c, AstId *contracts_ref) { Ast *ast = ast_new_curr(c, AST_CONTRACT); ast->contract_stmt.kind = CONTRACT_PURE; - append_docs(next, contracts_ref, ast); advance(c); + if (!parse_doc_discarded_comment(c)) return false; + append_docs(next, contracts_ref, ast); } else { advance(c); + if (!parse_doc_direct_comment(c)) return false; if (parse_doc_to_eol(c)) continue; - if (!consume(c, TOKEN_STRING, "Expected a string description for the custom contract '%s'.", name)) return false; + RETURN_PRINT_ERROR_HERE("Expected a string description for the custom contract '%s'.", name); } if (parse_doc_to_eol(c)) continue; - PRINT_ERROR_HERE("Expected end of line here."); + PRINT_ERROR_HERE("Expected the end of the contract here."); return false; } return true; diff --git a/src/compiler/parser_internal.h b/src/compiler/parser_internal.h index c8c8f7d80..61fe7b2b1 100644 --- a/src/compiler/parser_internal.h +++ b/src/compiler/parser_internal.h @@ -54,7 +54,7 @@ Expr *parse_expression_list(ParseContext *c, bool allow_decls); Decl *parse_local_decl_after_type(ParseContext *c, TypeInfo *type); Decl *parse_var_decl(ParseContext *c); bool parse_current_is_expr(ParseContext *c); - +bool parse_joined_strings(ParseContext *c, const char **str_ref, size_t *len_ref); bool parse_expr_list(ParseContext *c, Expr ***exprs_ref, TokenType end_token); bool parse_parameters(ParseContext *c, Decl ***params_ref, Variadic *variadic, int *vararg_index_ref, ParameterParseKind parse_kind); diff --git a/src/compiler/sema_casts.c b/src/compiler/sema_casts.c index b2df06857..9ad6d3dcf 100644 --- a/src/compiler/sema_casts.c +++ b/src/compiler/sema_casts.c @@ -20,7 +20,7 @@ typedef struct // NOLINT bool is_binary_conversion; } CastContext; -#define RETURN_CAST_ERROR(_node, ...) do { print_error_at((_node)->span, __VA_ARGS__); sema_print_inline(cc->context); return false; } while (0) +#define RETURN_CAST_ERROR(_node, ...) do { print_error_at((_node)->span, __VA_ARGS__); sema_print_inline(cc->context, (_node)->span); return false; } while (0) static bool sema_error_const_int_out_of_range(CastContext *cc, Expr *expr, Expr *problem, Type *to_type); static Expr *recursive_may_narrow(Expr *expr, Type *type); diff --git a/src/compiler/sema_internal.h b/src/compiler/sema_internal.h index 871dc8d4b..aa1709fd2 100644 --- a/src/compiler/sema_internal.h +++ b/src/compiler/sema_internal.h @@ -20,6 +20,7 @@ #define SEMA_WARN(_node, ...) (sema_warn_at(context, (_node)->span, __VA_ARGS__)) #define SEMA_ERROR(_node, ...) sema_error_at(context, (_node)->span, __VA_ARGS__) #define RETURN_SEMA_ERROR(_node, ...) do { sema_error_at(context, (_node)->span, __VA_ARGS__); return false; } while (0) +#define RETURN_VAL_SEMA_ERROR(val__, _node, ...) do { sema_error_at(context, (_node)->span, __VA_ARGS__); return (val__); } while (0) #define RETURN_NULL_SEMA_ERROR(_node, ...) do { sema_error_at(context, (_node)->span, __VA_ARGS__); return NULL; } while (0) #define RETURN_SEMA_ERROR_AT(span__, ...) do { sema_error_at(context, span__, __VA_ARGS__); return false; } while (0) #define SCOPE_OUTER_START do { DynamicScope stored_scope = context->active_scope; context_change_scope_with_flags(context, SCOPE_NONE); @@ -55,7 +56,7 @@ void context_change_scope_with_flags(SemaContext *context, ScopeFlags flags); SemaContext *context_transform_for_eval(SemaContext *context, SemaContext *temp_context, CompilationUnit *eval_unit); TokenType sema_splitpathref(const char *string, ArraySize len, Path **path_ref, const char **ident_ref); -void sema_print_inline(SemaContext *context); +void sema_print_inline(SemaContext *context, SourceSpan span_original); void sema_error_at(SemaContext *context, SourceSpan span, const char *message, ...); bool sema_warn_at(SemaContext *context, SourceSpan span, const char *message, ...); diff --git a/src/compiler/sema_types.c b/src/compiler/sema_types.c index fd8116206..3df02f7c0 100644 --- a/src/compiler/sema_types.c +++ b/src/compiler/sema_types.c @@ -85,13 +85,12 @@ bool sema_resolve_array_like_len(SemaContext *context, TypeInfo *type_info, Arra { if (is_vector) { - SEMA_ERROR(len_expr, "A vector may not exceed %d in bit width.", compiler.build.max_vector_size); + RETURN_VAL_SEMA_ERROR(type_info_poison(type_info), len_expr, "A vector may not exceed %d in bit width.", compiler.build.max_vector_size); } else { - SEMA_ERROR(len_expr, "The array length may not exceed %lld.", MAX_ARRAY_SIZE); + RETURN_VAL_SEMA_ERROR(type_info_poison(type_info), len_expr, "The array length may not exceed %lld.", MAX_ARRAY_SIZE); } - return type_info_poison(type_info); } // We're done, return the size and mark it as a success. *len_ref = (ArraySize)len.i.low; diff --git a/src/compiler/semantic_analyser.c b/src/compiler/semantic_analyser.c index cea5f786a..107b15fe9 100644 --- a/src/compiler/semantic_analyser.c +++ b/src/compiler/semantic_analyser.c @@ -560,13 +560,16 @@ SemaContext *context_transform_for_eval(SemaContext *context, SemaContext *temp_ return temp_context; } -void sema_print_inline(SemaContext *context) +void sema_print_inline(SemaContext *context, SourceSpan original) { if (!context) return; InliningSpan *inlined_at = context->inlined_at; while (inlined_at) { - sema_note_prev_at(inlined_at->span, "Inlined from here."); + if (inlined_at->span.a != original.a) + { + sema_note_prev_at(inlined_at->span, "Inlined from here."); + } inlined_at = inlined_at->prev; } } @@ -577,7 +580,7 @@ void sema_error_at(SemaContext *context, SourceSpan span, const char *message, . va_start(list, message); sema_verror_range(span, message, list); va_end(list); - sema_print_inline(context); + sema_print_inline(context, span); } bool sema_warn_at(SemaContext *context, SourceSpan span, const char *message, ...) @@ -594,6 +597,6 @@ bool sema_warn_at(SemaContext *context, SourceSpan span, const char *message, .. sema_verror_range(span, message, list); } va_end(list); - sema_print_inline(context); + sema_print_inline(context, span); return is_warn; } diff --git a/test/test_suite/errors/contract_multi.c3 b/test/test_suite/errors/contract_multi.c3 new file mode 100644 index 000000000..d9681c03b --- /dev/null +++ b/test/test_suite/errors/contract_multi.c3 @@ -0,0 +1,40 @@ +module contract_err_tests; + +<* +Hello world + @param [in] a + : "param description" + @require 1 == 2 : "Some text, " + "that" " continues " + `-here ` + @require 1 = 1 + @return "hello" + "world" + @return? io::EOF : + "errors" +*> +macro usz @a(a) => a; + +<* +Hello world + @param [in] a : + "param description" + @require 1 == 2 + : + "Some text, " + "that" " continues " + `-here ` + @require 1 = 1 + @return + "hello" + "world" + @return? io::EOF + : "errors" +*> +macro usz @b(a) => a; + +fn int main() +{ + usz t = @a(1); // #error: 'Some text, that continues -here ' + return 0; +} \ No newline at end of file diff --git a/test/test_suite/generic/generic_lambda_complex.c3t b/test/test_suite/generic/generic_lambda_complex.c3t index 54a5fafe3..0b835d973 100644 --- a/test/test_suite/generic/generic_lambda_complex.c3t +++ b/test/test_suite/generic/generic_lambda_complex.c3t @@ -37,7 +37,7 @@ struct TextTag } <* - @require self.tags.len == 0 "template already initialized" + @require self.tags.len == 0 : "template already initialized" @require tag_start != tag_end *> fn void? TextTemplate.init(&self, String template, String tag_start = "{{", String tag_end = "}}", Allocator using = allocator::heap())