From 82cc49b388f41c5af80c4a47bd8281216e8559e0 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Sun, 16 Mar 2025 23:57:30 +0100 Subject: [PATCH] - `!!foo` now works same as as `! ! foo`. - Incorrectly allowed getting pointer to a macro #2049. --- releasenotes.md | 2 ++ src/compiler/ast.c | 1 + src/compiler/parse_expr.c | 12 +++++-- src/compiler/sema_expr.c | 12 ++++--- test/test_suite/expressions/negneg.c3t | 37 ++++++++++++++++++++++ test/test_suite/macros/address_of_macro.c3 | 28 ++++++++++++++++ 6 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 test/test_suite/expressions/negneg.c3t create mode 100644 test/test_suite/macros/address_of_macro.c3 diff --git a/releasenotes.md b/releasenotes.md index 48ce621a1..6b94a393c 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -36,6 +36,7 @@ - Order of attribute declaration is changed for `alias`. - Added `LANGUAGE_DEV_VERSION` env constant. - Rename `anyfault` -> `fault`. +- `!!foo` now works same as as `! ! foo`. ### Fixes - Fix address sanitizer to work on MachO targets (e.g. MacOS). @@ -48,6 +49,7 @@ - Crash when trying to convert a struct slice to a vector #2039. - Crash resolving a method on `Foo[2]` when `Foo` is distinct #2042. - Bug due to missing cast when doing `$i[$x] = $z`. +- Incorrectly allowed getting pointer to a macro #2049. ### Stdlib changes - `new_*` functions in general moved to version without `new_` prefix. diff --git a/src/compiler/ast.c b/src/compiler/ast.c index 22526e43f..965ddfadd 100644 --- a/src/compiler/ast.c +++ b/src/compiler/ast.c @@ -239,6 +239,7 @@ UnaryOp unary_op[TOKEN_LAST + 1] = { [TOKEN_AND] = UNARYOP_TADDR, [TOKEN_BIT_NOT] = UNARYOP_BITNEG, [TOKEN_BANG] = UNARYOP_NOT, + [TOKEN_BANGBANG] = UNARYOP_NOT, [TOKEN_MINUS] = UNARYOP_NEG, [TOKEN_PLUS] = UNARYOP_PLUS, [TOKEN_PLUSPLUS] = UNARYOP_INC, diff --git a/src/compiler/parse_expr.c b/src/compiler/parse_expr.c index a9ca80b92..d8ce055d3 100644 --- a/src/compiler/parse_expr.c +++ b/src/compiler/parse_expr.c @@ -701,15 +701,21 @@ static Expr *parse_unary_expr(ParseContext *c, Expr *left) { ASSERT(!left && "Did not expect a left hand side!"); + bool is_bangbang = tok_is(c, TOKEN_BANGBANG); Expr *unary = EXPR_NEW_TOKEN(EXPR_UNARY); unary->unary_expr.operator = unaryop_from_token(c->tok); advance(c); Expr *right_side = parse_precedence(c, PREC_UNARY); CHECK_EXPR_OR_RET(right_side); - unary->unary_expr.expr = right_side; RANGE_EXTEND_PREV(unary); + if (is_bangbang) + { + Expr *outer = expr_new_expr(EXPR_UNARY, unary); + outer->unary_expr = (ExprUnary) { .expr = unary, .operator = UNARYOP_NOT }; + return outer; + } return unary; } @@ -757,7 +763,7 @@ static Expr *parse_ternary_expr(ParseContext *c, Expr *left_side) // If we have no expression following *or* it is a '!' followed by no expression // in this case it's an optional expression. - if (!rules[c->tok].prefix || (c->tok == TOKEN_BANG && !rules[peek(c)].prefix)) + if (!rules[c->tok].prefix || ((c->tok == TOKEN_BANG || c->tok == TOKEN_BANGBANG) && !rules[peek(c)].prefix)) { expr->expr_kind = EXPR_OPTIONAL; expr->inner_expr = left_side; @@ -1958,7 +1964,7 @@ ParseRule rules[TOKEN_EOF + 1] = { [TOKEN_PLUSPLUS] = { parse_unary_expr, parse_post_unary, PREC_CALL }, [TOKEN_MINUSMINUS] = { parse_unary_expr, parse_post_unary, PREC_CALL }, [TOKEN_LPAREN] = { parse_grouping_expr, parse_call_expr, PREC_CALL }, - [TOKEN_BANGBANG] = { NULL, parse_force_unwrap_expr, PREC_CALL }, + [TOKEN_BANGBANG] = { parse_unary_expr, parse_force_unwrap_expr, PREC_CALL }, [TOKEN_LBRACKET] = { NULL, parse_subscript_expr, PREC_CALL }, [TOKEN_MINUS] = { parse_unary_expr, parse_binary, PREC_ADDITIVE }, [TOKEN_PLUS] = { parse_unary_expr, parse_binary, PREC_ADDITIVE }, diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 86049ed44..5ed4754b0 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -7193,12 +7193,16 @@ static const char *sema_addr_check_may_take(Expr *inner) case EXPR_ACCESS_RESOLVED: { Decl *decl = inner->access_resolved_expr.ref; - if (decl->decl_kind == DECL_FUNC) + switch (decl->decl_kind) { - if (decl->func_decl.attr_interface_method) return NULL; - return "Taking the address of a method should be done through the type e.g. '&Foo.method' not through the value."; + case DECL_FUNC: + if (decl->func_decl.attr_interface_method) return NULL; + return "Taking the address of a method should be done through the type e.g. '&Foo.method' not through the value."; + case DECL_MACRO: + return "It's not possible to take the address of a macro."; + default: + return sema_addr_check_may_take(inner->access_resolved_expr.parent); } - return sema_addr_check_may_take(inner->access_resolved_expr.parent); } case EXPR_SUBSCRIPT_ADDR: return NULL; diff --git a/test/test_suite/expressions/negneg.c3t b/test/test_suite/expressions/negneg.c3t new file mode 100644 index 000000000..e3f751045 --- /dev/null +++ b/test/test_suite/expressions/negneg.c3t @@ -0,0 +1,37 @@ +// #target: macos-x64 +module test; +import std; +fn void test(bool b) {} +fn void main() +{ + int a; + test(!a); + test(!!a); + test(!!!a); + test(!!!!a); +} + +/* #expect: test.ll + +define void @test.main() #0 { +entry: + %a = alloca i32, align 4 + store i32 0, ptr %a, align 4 + %0 = load i32, ptr %a, align 4 + %i2nb = icmp eq i32 %0, 0 + %1 = zext i1 %i2nb to i8 + call void @test.test(i8 zeroext %1) + %2 = load i32, ptr %a, align 4 + %i2b = icmp ne i32 %2, 0 + %3 = zext i1 %i2b to i8 + call void @test.test(i8 zeroext %3) + %4 = load i32, ptr %a, align 4 + %i2nb1 = icmp eq i32 %4, 0 + %5 = zext i1 %i2nb1 to i8 + call void @test.test(i8 zeroext %5) + %6 = load i32, ptr %a, align 4 + %i2b2 = icmp ne i32 %6, 0 + %7 = zext i1 %i2b2 to i8 + call void @test.test(i8 zeroext %7) + ret void +} diff --git a/test/test_suite/macros/address_of_macro.c3 b/test/test_suite/macros/address_of_macro.c3 new file mode 100644 index 000000000..436c914dd --- /dev/null +++ b/test/test_suite/macros/address_of_macro.c3 @@ -0,0 +1,28 @@ +import std::io; +// Issue #2049 +struct Container +{ + int[] items; +} + +macro int Container.get(&this, usz i) @operator([]) +{ + return this.items[i]; +} + +fn int Container.get2(&this, usz i) +{ + return this.items[i]; +} + +alias ProcGetItem = fn int(usz); +fn void process(int input, ProcGetItem getter) +{ +} + +fn void main() +{ + Container c = { .items = { 2, 3, 4, 5 } }; + process(10, &c.get); // #error: It's not possible to take the address + +} \ No newline at end of file