From f2e69f8fdcc1b40f80882e3e87fd71483b8e771d Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Sat, 11 Jan 2025 20:07:16 +0100 Subject: [PATCH] Fix bug with defer assignment in macro #1807. --- releasenotes.md | 1 + src/compiler/compiler_internal.h | 1 + src/compiler/expr.c | 100 ++++++++++++++++++++ src/compiler/sema_expr.c | 68 +++++++------ src/compiler/sema_liveness.c | 3 +- test/test_suite/macros/typed_hash_access.c3 | 11 +++ test/test_suite/statements/defer_hash.c3t | 45 +++++++++ 7 files changed, 201 insertions(+), 28 deletions(-) create mode 100644 test/test_suite/macros/typed_hash_access.c3 create mode 100644 test/test_suite/statements/defer_hash.c3t diff --git a/releasenotes.md b/releasenotes.md index 896474bbb..ed3ed6b4a 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -74,6 +74,7 @@ - `#foo` style arguments were not type checked when given a type. #1790 - Bug when using +++ on value build a slice or array: the rhs cast was not done. - Fix bug preventing compile time slices from being iterated over with `$foreach`. +- Fix bug with defer assignment in macro #1807. ### Stdlib changes - Increase BitWriter.write_bits limit up to 32 bits. diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 9b4ada64a..7a0aed78e 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -2221,6 +2221,7 @@ Expr *expr_new_const_bool(SourceSpan span, Type *type, bool value); Expr *expr_new_const_typeid(SourceSpan span, Type *type); Expr *expr_new_const_string(SourceSpan span, const char *string); Expr *expr_new_const_initializer(SourceSpan span, Type *type, ConstInitializer *initializer); +const char *expr_kind_to_string(ExprKind kind); bool expr_is_simple(Expr *expr, bool to_float); bool expr_is_pure(Expr *expr); bool expr_is_runtime_const(Expr *expr); diff --git a/src/compiler/expr.c b/src/compiler/expr.c index 2d456e3a1..c31eb7438 100644 --- a/src/compiler/expr.c +++ b/src/compiler/expr.c @@ -8,6 +8,106 @@ static inline bool expr_list_is_constant_eval(Expr **exprs); static inline bool expr_unary_addr_is_constant_eval(Expr *expr); static inline ConstInitializer *initializer_for_index(ConstInitializer *initializer, ArraySize index, bool from_back); +const char *expr_kind_to_string(ExprKind kind) +{ + switch (kind) + { + case EXPR_ACCESS: return "access"; + case EXPR_ANYSWITCH: return "anyswitch"; + case EXPR_ASM: return "asm"; + case EXPR_BENCHMARK_HOOK: return "benchmark_hook"; + case EXPR_BINARY: return "binary"; + case EXPR_BITACCESS: return "bitaccess"; + case EXPR_BITASSIGN: return "bitassign"; + case EXPR_BUILTIN: return "builtin"; + case EXPR_BUILTIN_ACCESS: return "builtin_access"; + case EXPR_CALL: return "call"; + case EXPR_CAST: return "cast"; + case EXPR_CATCH_UNWRAP: return "catch_unwrap"; + case EXPR_COMPILER_CONST: return "compiler_const"; + case EXPR_COMPOUND_LITERAL: return "compound_litera"; + case EXPR_COND: return "cond"; + case EXPR_CONST: return "const"; + case EXPR_TYPECALL: return "typecall"; + case EXPR_CT_AND_OR: return "ct_and_or"; + case EXPR_CT_ARG: return "ct_arg"; + case EXPR_CT_APPEND: return "ct_append"; + case EXPR_CT_CALL: return "ct_call"; + case EXPR_CT_CASTABLE: return "ct_castable"; + case EXPR_CT_CONCAT: return "ct_concat"; + case EXPR_CT_DEFINED: return "ct_defined"; + case EXPR_CT_EVAL: return "ct_eval"; + case EXPR_CT_IDENT: return "ct_ident"; + case EXPR_CT_IS_CONST: return "ct_is_const"; + case EXPR_DECL: return "decl"; + case EXPR_DEFAULT_ARG: return "default_arg"; + case EXPR_DESIGNATED_INITIALIZER_LIST: return "designated_initializer_list"; + case EXPR_DESIGNATOR: return "designator"; + case EXPR_DISCARD: return "discard"; + case EXPR_EMBED: return "embed"; + case EXPR_VECTOR_TO_ARRAY: return "vector_to_array"; + case EXPR_SLICE_TO_VEC_ARRAY: return "slice_to_vec_array"; + case EXPR_SCALAR_TO_VECTOR: return "scalar_to_vector"; + case EXPR_EXPRESSION_LIST: return "expression_list"; + case EXPR_EXPR_BLOCK: return "expr_block"; + case EXPR_FORCE_UNWRAP: return "force_unwrap"; + case EXPR_FLOAT_TO_INT: return "float_to_int"; + case EXPR_GENERIC_IDENT: return "generic_ident"; + case EXPR_HASH_IDENT: return "hash_ident"; + case EXPR_IDENTIFIER: return "identifier"; + case EXPR_INITIALIZER_LIST: return "initializer_list"; + case EXPR_INT_TO_FLOAT: return "int_to_float"; + case EXPR_INT_TO_PTR: return "int_to_ptr"; + case EXPR_PTR_TO_INT: return "ptr_to_int"; + case EXPR_ANYFAULT_TO_FAULT: return "anyfault_to_fault"; + case EXPR_LAMBDA: return "lambda"; + case EXPR_LAST_FAULT: return "last_fault"; + case EXPR_MACRO_BLOCK: return "macro_block"; + case EXPR_MACRO_BODY: return "macro_body"; + case EXPR_MACRO_BODY_EXPANSION: return "macro_body_expansion"; + case EXPR_MAKE_ANY: return "make_any"; + case EXPR_MAKE_SLICE: return "make_slice"; + case EXPR_MEMBER_GET: return "member_get"; + case EXPR_NAMED_ARGUMENT: return "named_argument"; + case EXPR_NOP: return "nop"; + case EXPR_OPERATOR_CHARS: return "operator_chars"; + case EXPR_OPTIONAL: return "optional"; + case EXPR_ENUM_FROM_ORD: return "enum_from_ord"; + case EXPR_OTHER_CONTEXT: return "other_context"; + case EXPR_POINTER_OFFSET: return "pointer_offset"; + case EXPR_ADDR_CONVERSION: return "addr_conversion"; + case EXPR_POISONED: return "poisoned"; + case EXPR_PTR_ACCESS: return "ptr_access"; + case EXPR_POST_UNARY: return "post_unary"; + case EXPR_RETHROW: return "rethrow"; + case EXPR_RETVAL: return "retval"; + case EXPR_RVALUE: return "rvalue"; + case EXPR_RECAST: return "recast"; + case EXPR_SLICE: return "slice"; + case EXPR_SLICE_LEN: return "slice_len"; + case EXPR_SLICE_ASSIGN: return "slice_assign"; + case EXPR_SLICE_COPY: return "slice_copy"; + case EXPR_SPLAT: return "splat"; + case EXPR_STRINGIFY: return "stringify"; + case EXPR_SUBSCRIPT: return "subscript"; + case EXPR_SUBSCRIPT_ADDR: return "subscript_addr"; + case EXPR_SUBSCRIPT_ASSIGN: return "subscript_assign"; + case EXPR_SWIZZLE: return "swizzle"; + case EXPR_TERNARY: return "ternary"; + case EXPR_TEST_HOOK: return "test_hook"; + case EXPR_TRY_UNWRAP: return "try_unwrap"; + case EXPR_TRY_UNWRAP_CHAIN: return "try_unwrap_chain"; + case EXPR_TYPEID: return "typeid"; + case EXPR_TYPEID_INFO: return "typeid_info"; + case EXPR_TYPEINFO: return "typeinfo"; + case EXPR_UNARY: return "unary"; + case EXPR_VASPLAT: return "vasplat"; + case EXPR_VECTOR_FROM_ARRAY: return "vector_from_array"; + case EXPR_EXT_TRUNC: return "ext_trunc"; + case EXPR_INT_TO_BOOL: return "int_to_bool"; + default: return "???"; + } +} Expr *expr_negate_expr(Expr *expr) { if (expr->expr_kind == EXPR_UNARY && expr->unary_expr.operator == UNARYOP_NEG) diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 7265518f5..837ddf6f1 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -1409,6 +1409,7 @@ static bool sema_analyse_parameter(SemaContext *context, Expr *arg, Decl *param, Expr *inner = expr_copy(arg); arg->expr_kind = EXPR_OTHER_CONTEXT; arg->expr_other_context = (ExprOtherContext) { .context = context, .inner = inner }; + arg->resolve_status = RESOLVE_NOT_DONE; } break; case VARDECL_PARAM_CT: @@ -3662,29 +3663,33 @@ static inline bool sema_expr_analyse_slice(SemaContext *context, Expr *expr, Che */ Expr *sema_expr_resolve_access_child(SemaContext *context, Expr *child, bool *missing) { + SourceSpan span = child->span; + bool in_hash = false; RETRY: - if (!sema_expr_fold_hash(context, child)) return false; switch (child->expr_kind) { + case EXPR_HASH_IDENT: + if (!sema_expr_fold_hash(context, child)) return false; + in_hash = true; + goto RETRY; case EXPR_OTHER_CONTEXT: { Expr *inner = child->expr_other_context.inner; - SemaContext *c2 = child->expr_other_context.context; - return sema_expr_resolve_access_child(c2, inner, missing); + context = child->expr_other_context.context; + child = inner; + goto RETRY; } case EXPR_IDENTIFIER: + if (child->resolve_status == RESOLVE_DONE) goto ALREADY_RESOLVED; // A path is not allowed. - ASSERT_SPAN(child, child->resolve_status != RESOLVE_DONE); if (child->identifier_expr.path) break; return child; case EXPR_CT_IDENT: - ASSERT_SPAN(child, child->resolve_status != RESOLVE_DONE); + if (child->resolve_status == RESOLVE_DONE) goto ALREADY_RESOLVED; return child; case EXPR_TYPEINFO: if (child->type_expr->kind == TYPE_INFO_CT_IDENTIFIER) return child; break; - case EXPR_HASH_IDENT: - UNREACHABLE case EXPR_CT_EVAL: { ASSERT_SPAN(child, child->resolve_status != RESOLVE_DONE); @@ -3704,7 +3709,17 @@ RETRY: break; } - SEMA_ERROR(child, "Expected an identifier here."); + sema_error_at(context, span, "Expected an identifier here."); + return NULL; +ALREADY_RESOLVED: + if (in_hash) + { + sema_error_at(context, span, "An expression cannot already be resolved when used as '.foo'. " + "One way this might happen is if you pass a '#foo' style " + "parameter that is already assigned a type when declared: 'macro @test(int #foo) { ... }'."); + return NULL; + } + sema_error_at(context, span, "This expression was already resolved to an identifier before it was used."); return NULL; } @@ -5675,7 +5690,8 @@ static bool sema_expr_analyse_ct_type_identifier_assign(SemaContext *context, Ex static bool sema_expr_fold_hash(SemaContext *context, Expr *expr) { - if (expr->expr_kind == EXPR_HASH_IDENT) + assert(expr->expr_kind == EXPR_HASH_IDENT); + while (expr->expr_kind == EXPR_HASH_IDENT) { ASSERT0(expr && expr->hash_ident_expr.identifier); DEBUG_LOG("Resolving identifier '%s'", expr->hash_ident_expr.identifier); @@ -5685,12 +5701,11 @@ static bool sema_expr_fold_hash(SemaContext *context, Expr *expr) if (!decl) return expr_poison(expr); ASSERT_SPAN(expr, decl->decl_kind == DECL_VAR); + DEBUG_LOG("Replacing expr (%p) with '%s' (%p) expression resolve: %d", expr, expr_kind_to_string(decl->var.init_expr->expr_kind), decl->var.init_expr, decl->var.init_expr->resolve_status); expr_replace(expr, copy_expr_single(decl->var.init_expr)); - ASSERT0(expr->expr_kind == EXPR_OTHER_CONTEXT); REMINDER("Handle inlining at"); - return true; } - return true; + return expr_ok(expr); } /** * Analyse a = b @@ -5698,10 +5713,13 @@ static bool sema_expr_fold_hash(SemaContext *context, Expr *expr) */ static bool sema_expr_analyse_assign(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref) { - if (!sema_expr_fold_hash(context, left)) return false; +RETRY:; // 1. Evaluate left side switch (left->expr_kind) { + case EXPR_HASH_IDENT: + if (!sema_expr_fold_hash(context, left)) return false; + goto RETRY; case EXPR_CT_IDENT: // $foo = ... return sema_expr_analyse_ct_identifier_assign(context, expr, left, right); @@ -7030,9 +7048,10 @@ static const char *sema_addr_check_may_take(Expr *inner) */ static inline bool sema_expr_analyse_addr(SemaContext *context, Expr *expr, bool *failed_ref, CheckType check) { +RETRY:; // 1. Evaluate the expression Expr *inner = expr->unary_expr.expr; - if (!sema_expr_fold_hash(context, inner)) return false; + if (inner->resolve_status == RESOLVE_DONE) goto RESOLVED; switch (inner->expr_kind) { case EXPR_POISONED: @@ -7045,14 +7064,8 @@ static inline bool sema_expr_analyse_addr(SemaContext *context, Expr *expr, bool return sema_expr_analyse_addr(c2, expr, failed_ref, check); } case EXPR_HASH_IDENT: - { - Decl *decl = sema_resolve_symbol(context, inner->hash_ident_expr.identifier, NULL, inner->span); - if (!decl) return expr_poison(expr); - ASSERT_SPAN(expr, decl->decl_kind == DECL_VAR); - expr_replace(inner, copy_expr_single(decl->var.init_expr)); - if (!sema_expr_analyse_addr(context, expr, failed_ref, check)) return decl_poison(decl); - return true; - } + if (!sema_expr_fold_hash(context, inner)) return false; + goto RETRY; case EXPR_SUBSCRIPT: inner->expr_kind = EXPR_SUBSCRIPT_ADDR; if (failed_ref) @@ -7077,7 +7090,7 @@ static inline bool sema_expr_analyse_addr(SemaContext *context, Expr *expr, bool default: break; } - +RESOLVED: if (inner->expr_kind == EXPR_CT_IDENT) { RETURN_SEMA_ERROR(expr, "It's not possible to take the address of a compile time value."); @@ -9536,7 +9549,7 @@ static inline bool sema_analyse_expr_dispatch(SemaContext *context, Expr *expr, return sema_expr_analyse_ct_call(context, expr); case EXPR_HASH_IDENT: if (!sema_expr_fold_hash(context, expr)) return false; - return sema_analyse_expr_dispatch(context, expr, check); + return sema_analyse_expr_check(context, expr, check); case EXPR_CT_IDENT: return sema_expr_analyse_ct_identifier(context, expr, check); case EXPR_OPTIONAL: @@ -10009,6 +10022,7 @@ RETRY: bool sema_analyse_inferred_expr(SemaContext *context, Type *infer_type, Expr *expr) { infer_type = type_no_optional(infer_type); +RETRY: switch (expr->resolve_status) { case RESOLVE_NOT_DONE: @@ -10026,10 +10040,12 @@ bool sema_analyse_inferred_expr(SemaContext *context, Type *infer_type, Expr *ex UNREACHABLE } - if (!sema_expr_fold_hash(context, expr)) return false; expr->resolve_status = RESOLVE_RUNNING; switch (expr->expr_kind) { + case EXPR_HASH_IDENT: + if (!sema_expr_fold_hash(context, expr)) return false; + goto RETRY; case EXPR_OTHER_CONTEXT: { InliningSpan *new_span = context->inlined_at; @@ -10057,8 +10073,6 @@ bool sema_analyse_inferred_expr(SemaContext *context, Type *infer_type, Expr *ex case EXPR_TERNARY: if (!sema_expr_analyse_ternary(context, infer_type, expr)) return expr_poison(expr); break; - case EXPR_HASH_IDENT: - UNREACHABLE case EXPR_CT_ARG: if (!sema_expr_analyse_ct_arg(context, infer_type, expr)) return expr_poison(expr); break; diff --git a/src/compiler/sema_liveness.c b/src/compiler/sema_liveness.c index e2c66b909..ba0914b31 100644 --- a/src/compiler/sema_liveness.c +++ b/src/compiler/sema_liveness.c @@ -262,10 +262,11 @@ RETRY: case EXPR_GENERIC_IDENT: case EXPR_EMBED: case EXPR_MACRO_BODY: - case EXPR_OTHER_CONTEXT: case EXPR_MEMBER_GET: case EXPR_NAMED_ARGUMENT: UNREACHABLE + case EXPR_OTHER_CONTEXT: + UNREACHABLE case EXPR_DESIGNATOR: sema_trace_expr_liveness(expr->designator_expr.value); return; diff --git a/test/test_suite/macros/typed_hash_access.c3 b/test/test_suite/macros/typed_hash_access.c3 new file mode 100644 index 000000000..a0cf3291e --- /dev/null +++ b/test/test_suite/macros/typed_hash_access.c3 @@ -0,0 +1,11 @@ + +macro void @foo(int #a) +{ + var x = float.#a; // #error: cannot already be resolved +} + +fn void main() +{ + int inf; + @foo(inf); +} \ No newline at end of file diff --git a/test/test_suite/statements/defer_hash.c3t b/test/test_suite/statements/defer_hash.c3t new file mode 100644 index 000000000..7e689fe4e --- /dev/null +++ b/test/test_suite/statements/defer_hash.c3t @@ -0,0 +1,45 @@ +import std::io; + +struct Version { + uint major; + uint minor; +} + +fn void main() +{ + String s = "123.456"; + + Version v; + v.major = @read_int(s); + s = s[1..]; + v.minor = @read_int(s); + + io::printfn("Version{.major = %d, .minor = %d}", v.major, v.minor); +} + +<* + @require values::@is_lvalue(#s) +*> +macro int @read_int(String #s) +{ + int res = 0; + int i = 0; + + String s = #s; + defer #s = s[i..]; // commenting out this lines leads to a successful compilation + + while (i < s.len && s[i].is_space()) + { + ++i; + } + + if (i >= s.len) return res; + + char c; + while (i < s.len && (c = s[i]).is_digit()) + { + res = (10 * res) + (c - '0'); + ++i; + } + return res; +} \ No newline at end of file