Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 94 additions & 12 deletions analyser/module_analyser.c2
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import label_vector local;
import name_vector local;
import src_loc local;
import scope;
import string_buffer;
import string_pool;
import warning_flags;
import struct_func_list as sf_list;
Expand All @@ -37,6 +38,7 @@ import value_type local;
import ctype;
import stdarg local;
import stdlib;
import string;

const u32 MaxDepth = 8;
const u32 LHS = 0x01;
Expand Down Expand Up @@ -509,37 +511,117 @@ fn void Analyser.handleTypeDecl(void* arg, Decl* d) {
ma.analyseGlobalDecl(d);
}

fn void Analyser.printLiteral(Analyser* ma, string_buffer.Buf *buf,
const char* s, const Expr* e, Value val,
bool as_is = false)
{
buf.add(s);
u32 off = buf.size();
e.printLiteral(buf);
const char* str = val.str();
if (as_is) {
buf.print(" is %s", str);
} else
if (string.strcmp((const char*)buf.data() + off, str)) {
buf.print(" (%s)", str);
}
}

fn bool isTrueLiteral(Expr* e) {
switch (e.getKind()) {
case IntegerLiteral: return ((IntegerLiteral*)e).isDecimal();
case FloatLiteral: return true;
case BooleanLiteral: return true;
case CharLiteral: return false;
case StringLiteral: return false;
case Nil: return false;
default: return false;
}
}

fn void Analyser.handleStaticAssert(void* arg, StaticAssert* sa) {
Analyser* ma = arg;

Expr* lhs = sa.getLhs();
Expr* rhs = sa.getRhs();
Expr* lhs = sa.getLHS();
Expr* rhs = sa.getRHS();
const char* msg = "static_assert failed";
if (rhs && rhs.isStringLiteral()) {
StringLiteral* s = (StringLiteral*)rhs;
msg = ma.astPool.idx2str(s.getTextIndex());
rhs = nil;
}

// push a dummy declaration so ma.popCheck() does not set ma.scope to nil
if (!ma.pushCheck(nil, sa.getAST().getPtr(), nil)) return;
QualType t1 = ma.analyseConstExpr(&lhs, true);
QualType t2 = t1.isInvalid() ? t1 : ma.analyseConstExpr(&rhs, true);
QualType t2 = t1;
if (!t1.isInvalid() && rhs) t2 = ma.analyseConstExpr(&rhs, true);
ma.popCheck();
if (t1.isInvalid() || t2.isInvalid()) return;

bool error = false;
if (!lhs.isCtv()) {
ma.errorRange(lhs.getLoc(), lhs.getRange(), "static_assert element is not a compile-time value");
error = true;
return;
}
if (!rhs.isCtv()) {
if (rhs && !rhs.isCtv()) {
ma.errorRange(rhs.getLoc(), rhs.getRange(), "static_assert element is not a compile-time value");
error = true;
return;
}

if (error) return;
Expr* e = lhs;
BinaryOpcode opcode = Equal;
if (lhs.isComparison() && !rhs) {
BinaryOperator* b = (BinaryOperator*)e;
opcode = b.getOpcode();
lhs = b.getLHS();
if (lhs.isImplicitCast()) lhs = ((ImplicitCastExpr*)lhs).getInner();
rhs = b.getRHS();
if (rhs.isImplicitCast()) rhs = ((ImplicitCastExpr*)rhs).getInner();
}

Value val1 = ast.evalExpr(lhs);
Value val2 = ast.evalExpr(rhs);
Value val2 = {}
if (rhs) {
val2 = ast.evalExpr(rhs);
if (val1.is_equal(&val2)) return;
} else {
if (!val1.isZero()) return;
}

if (!val1.is_equal(&val2)) {
ma.errorRange(rhs.getStartLoc(), rhs.getRange(), "static_assert failed, expected %s, got %s", val1.str(), val2.str());
char[256] tmp;
string_buffer.Buf buf.init(tmp, elemsof(tmp), true, false, 0);

if (rhs) {
if (opcode == Equal) {
if (!lhs.isLiteral() && rhs.isLiteral()) {
ma.printLiteral(&buf, "", lhs, val1, true);
ma.printLiteral(&buf, ", expected ", rhs, val2);
} else
if (lhs.isLiteral() && !rhs.isLiteral()) {
ma.printLiteral(&buf, "", rhs, val2, true);
ma.printLiteral(&buf, ", expected ", lhs, val1);
} else {
ma.printLiteral(&buf, "values differ: ", lhs, val1);
ma.printLiteral(&buf, " <> ", rhs, val2);
}
} else {
buf.add("expected ");
lhs.printLiteral(&buf);
buf.print(" %s ", opcode.str());
rhs.printLiteral(&buf);
if (!isTrueLiteral(lhs)) {
ma.printLiteral(&buf, ", ", lhs, val1, true);
}
if (!isTrueLiteral(rhs)) {
ma.printLiteral(&buf, ", ", rhs, val2, true);
}
}
} else {
buf.add("expected ");
e.printLiteral(&buf);
buf.add(" != 0");
}
ma.errorRange(e.getStartLoc(), e.getRange(), "%s: %s", msg, buf.str());
buf.deinit();
}

fn void Analyser.handleVarDecl(void* arg, VarDecl* v) {
Expand Down
58 changes: 52 additions & 6 deletions analyser/module_analyser_stmt.c2
Original file line number Diff line number Diff line change
Expand Up @@ -445,24 +445,70 @@ fn void Analyser.analyseAssertStmt(Analyser* ma, Stmt* s) {
// add c2_assert.fail func designator
Expr* func = ma.builder.actOnMemberExpr(nil, ma.c2_assert_idx, 0, loc, ma.c2_assert_fail_idx);
// encode expression as a string
u32 num_args = 1;
Expr*[3] args;
char[128] tmp;
string_buffer.Buf buf.init(tmp, elemsof(tmp), true, false, 0);
inner.printLiteral(&buf);
u32 msg_len = buf.size();
u32 msg_idx = ma.astPool.add(buf.data(), msg_len, true);
string_buffer.Buf* out = &buf;
encode_expression(out, inner);
if (inner.isComparison()) {
BinaryOperator* b = (BinaryOperator*)inner;
Expr* lhs = b.getLHS();
const char* fmt1 = lhs.isLiteral() ? nil : get_type_format(lhs);
if (fmt1) {
out.add(", ");
encode_expression(out, lhs);
out.print(": %%%s", fmt1);
args[num_args++] = lhs;
}
Expr* rhs = b.getRHS();
const char* fmt2 = rhs.isLiteral() ? nil : get_type_format(rhs);
if (fmt2) {
out.add(", ");
encode_expression(out, rhs);
out.print(": %%%s", fmt2);
args[num_args++] = rhs;
}
}
u32 msg_len = out.size();
u32 msg_idx = ma.astPool.add(out.data(), msg_len, true);
buf.deinit();
SrcLoc eloc = inner.getStartLoc();
SrcLoc eloc_end = inner.getEndLoc();
Expr* arg = ma.builder.actOnStringLiteral(eloc, eloc_end - eloc, msg_idx, msg_len);
args[0] = ma.builder.actOnStringLiteral(eloc, eloc_end - eloc, msg_idx, msg_len);

// create call expression `c2_assert.c2_assert_fail("expression")
// create call expression `c2_assert.c2_assert_fail("expression...", ...)
Expr** callp = a.getCall2();
*callp = ma.builder.actOnCallExpr(loc, eloc_end + 1, func, &arg, 1);
*callp = ma.builder.actOnCallExpr(loc, eloc_end + 1, func, args, num_args);
qt = ma.analyseExpr(a.getCall2(), true, RHS);
if (qt.isInvalid()) return;
}
}

// encode expression as part of a format string
fn void encode_expression(string_buffer.Buf* out, Expr* e) {
char[128] tmp;
string_buffer.Buf buf.init(tmp, elemsof(tmp), false, false, 0);
e.printLiteral(&buf);
const char* s = buf.data();
for (const char* p = s; *p; p++) {
if (*p == '%') out.add1('%');
out.add1(*p);
}
buf.deinit();
}

fn const char* get_type_format(Expr* e) {
QualType qt = e.getType();
QualType canon = qt.getCanonicalType();
if (canon.isPointer() || canon.isFunction()) return "p";
if (canon.isBuiltin()) {
return canon.isFloat() ? "g" : "d";
}
if (canon.isEnum()) return "d";
return nil;
}

fn void Analyser.analyseReturnStmt(Analyser* ma, Stmt* s) {
ReturnStmt* r = (ReturnStmt*)s;

Expand Down
22 changes: 22 additions & 0 deletions ast/expr.c2
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,28 @@ public fn bool Expr.isStringLiteral(const Expr* e) {
return e.getKind() == StringLiteral;
}

public fn bool Expr.isLiteral(const Expr* e) {
switch (e.getKind()) {
case IntegerLiteral:
case FloatLiteral:
case BooleanLiteral:
case CharLiteral:
case StringLiteral:
case Nil:
return true;
default:
break;
}
return false;
}

public fn bool Expr.isComparison(const Expr* e) {
if (e.getKind() != BinaryOperator) return false;
BinaryOperator* binop = (BinaryOperator*)e;
BinaryOpcode opcode = binop.getOpcode();
return opcode.isComparison();
}

public fn bool Expr.isNil(const Expr* e) {
return e.getKind() == Nil;
}
Expand Down
2 changes: 0 additions & 2 deletions ast/integer_literal.c2
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,9 @@ fn u32 IntegerLiteral.getSrcLen(const IntegerLiteral* e) {
return e.base.base.integerLiteralBits.src_len;
}

/*
public fn bool IntegerLiteral.isDecimal(const IntegerLiteral* e) {
return e.base.base.integerLiteralBits.radix == Default;
}
*/

fn void printBinary(string_buffer.Buf* out, u64 value, u32 len) {
char[2+64+1] tmp;
Expand Down
4 changes: 2 additions & 2 deletions ast/static_assert.c2
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ public fn AST* StaticAssert.getAST(const StaticAssert* d) {
return idx2ast(d.ast_idx);
}

public fn Expr* StaticAssert.getLhs(const StaticAssert* d) {
public fn Expr* StaticAssert.getLHS(const StaticAssert* d) {
return d.lhs;
}

public fn Expr* StaticAssert.getRhs(const StaticAssert* d) {
public fn Expr* StaticAssert.getRHS(const StaticAssert* d) {
return d.rhs;
}

Expand Down
4 changes: 2 additions & 2 deletions generator/ast_visitor.c2
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ public fn void Visitor.free(Visitor* v) {

// only used by plugins
public fn void Visitor.handleAssert(Visitor* v, StaticAssert* a) @(unused) {
v.handleExpr(a.getLhs());
v.handleExpr(a.getRhs());
v.handleExpr(a.getLHS());
v.handleExpr(a.getRHS());
}

public fn void Visitor.handle(Visitor* v, Decl* d) {
Expand Down
10 changes: 8 additions & 2 deletions libs/libc/c2_assert.c2i
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
module c2_assert;

import stdio local;
import stdarg local;
import stdlib local;

public fn i32 c2_assert_fail(const char* filename @(auto_file),
u32 line @(auto_line),
const char* funcname @(auto_func),
const char* condstr)
const char* fmt @(printf_format), ...)
{
dprintf(2, "%s:%d: function %s: Assertion failed: %s\n", filename, line, funcname, condstr);
va_list args;
va_start(args, fmt);
dprintf(2, "%s:%d: function %s: assertion failed: ", filename, line, funcname);
vdprintf(2, fmt, args);
dprintf(2, "\n");
va_end(args);
abort();
return 0;
}
7 changes: 5 additions & 2 deletions parser/c2_parser.c2
Original file line number Diff line number Diff line change
Expand Up @@ -832,8 +832,11 @@ fn void Parser.parseStaticAssert(Parser* p) {

p.expectAndConsume(LParen);
Expr* lhs = p.parseExpr();
p.expectAndConsume(Comma);
Expr* rhs = p.parseExpr();
Expr* rhs = nil;
if (p.tok.kind == Comma) {
p.consumeToken();
rhs = p.parseExpr();
}
p.expectAndConsume(RParen);
p.expectAndConsume(Semicolon);
p.builder.actOnStaticAssert(loc, lhs, rhs);
Expand Down
13 changes: 11 additions & 2 deletions test/c_generator/stmts/assert.c2t
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@
module test;

i32 a = 10;
i32 b = 20;
void* p = nil;

public fn i32 main() {
assert(a);
assert(p);
assert(a);
assert(a == 10);
assert(a != 10);
assert(a > 10);
assert(a == b);
return 0;
}

Expand All @@ -19,8 +24,12 @@ int main(void);

int main(void)
{
(test_a) || c2_assert_fail("file1.c2", 7, "test.main", "a");
(test_p) || c2_assert_fail("file1.c2", 8, "test.main", "p");
(test_a) || c2_assert_fail("file1.c2", 9, "test.main", "a");
(test_a == 10) || c2_assert_fail("file1.c2", 10, "test.main", "a == 10, a: %d", test_a);
(test_a != 10) || c2_assert_fail("file1.c2", 11, "test.main", "a != 10, a: %d", test_a);
(test_a > 10) || c2_assert_fail("file1.c2", 12, "test.main", "a > 10, a: %d", test_a);
(test_a == test_b) || c2_assert_fail("file1.c2", 13, "test.main", "a == b, a: %d, b: %d", test_a, test_b);
return 0;
}

26 changes: 25 additions & 1 deletion test/globals/static_asserts/static_assert_int.c2
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
// @warnings{no-unused}
module test;

static_assert(3, 4); // @error{static_assert failed, expected 3, got 4}
type S struct { i32 x, y; }
type T struct { u32 x; }

static_assert(3, 4); // @error{static_assert failed: values differ: 3 <> 4}
static_assert(0xff, 0b11111110); // @error{static_assert failed: values differ: 0xFF (255) <> 0b11111110 (254)}
static_assert(sizeof(S), 4); // @error{static_assert failed: sizeof(S) is 8, expected 4}
static_assert(4, sizeof(S)); // @error{static_assert failed: sizeof(S) is 8, expected 4}
static_assert(sizeof(T), sizeof(S)); // @error{static_assert failed: values differ: sizeof(T) (4) <> sizeof(S) (8)}

static_assert(3 == 4); // @error{static_assert failed: values differ: 3 <> 4}
static_assert(0xff == 0b11111110); // @error{static_assert failed: values differ: 0xFF (255) <> 0b11111110 (254)}
static_assert(sizeof(S) == 4); // @error{static_assert failed: sizeof(S) is 8, expected 4}
static_assert(4 == sizeof(S)); // @error{static_assert failed: sizeof(S) is 8, expected 4}
static_assert(sizeof(T) == sizeof(S)); // @error{static_assert failed: values differ: sizeof(T) (4) <> sizeof(S) (8)}

static_assert(3 >= 4); // @error{static_assert failed: expected 3 >= 4}
static_assert(0xff <= 0b11111110); // @error{static_assert failed: expected 0xFF <= 0b11111110, 0xFF is 255, 0b11111110 is 254}
static_assert(sizeof(S) <= 4); // @error{static_assert failed: expected sizeof(S) <= 4, sizeof(S) is 8}
static_assert(4 >= sizeof(S)); // @error{static_assert failed: expected 4 >= sizeof(S), sizeof(S) is 8}
static_assert(sizeof(T) > sizeof(S)); // @error{static_assert failed: expected sizeof(T) > sizeof(S), sizeof(T) is 4, sizeof(S) is 8}

static_assert(1.5 >= 4); // @error{static_assert failed: expected 1.5 >= 4}
static_assert(true, 0); // @error{static_assert failed: values differ: true (1) <> 0}
static_assert(true, false); // @error{static_assert failed: values differ: true (1) <> false (0)}
static_assert(true, "unexpected failure");
static_assert(false, "expected failure"); // @error{expected failure: expected false != 0}
Loading
Loading