Skip to content

Commit 04ff3b9

Browse files
chqrliebvdberg
authored andcommitted
Analyser: force parentheses for ambiguous operator combinations
* flag use of combined operators with unintuitive relative precedence as constraint violations (analyser errors) * fix code like `if (buf.st_mode & S_IFMT == S_IFREG)` that now requires parentheses: `if ((buf.st_mode & S_IFMT) == S_IFREG)` * update tests
1 parent de969b7 commit 04ff3b9

File tree

14 files changed

+195
-46
lines changed

14 files changed

+195
-46
lines changed

analyser/module_analyser_binop.c2

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
module module_analyser;
1717

1818
import ast local;
19+
import c2_prec local;
1920
import conversion_checker;
2021
import ctv_analyser;
2122

@@ -400,6 +401,70 @@ const u8[elemsof(TypeKind)][elemsof(TypeKind)] BinOpConvComparision = {
400401
// Alias + Module are zero
401402
}
402403

404+
const Prec[elemsof(BinaryOpcode)] Prec_table = {
405+
[BinaryOpcode.Multiply] = Multiplicative,
406+
[BinaryOpcode.Divide] = Multiplicative,
407+
[BinaryOpcode.Remainder] = Multiplicative,
408+
[BinaryOpcode.Add] = Additive,
409+
[BinaryOpcode.Subtract] = Additive,
410+
[BinaryOpcode.ShiftLeft] = Shift,
411+
[BinaryOpcode.ShiftRight] = Shift,
412+
[BinaryOpcode.LessThan] = Relational,
413+
[BinaryOpcode.GreaterThan] = Relational,
414+
[BinaryOpcode.LessEqual] = Relational,
415+
[BinaryOpcode.GreaterEqual] = Relational,
416+
[BinaryOpcode.Equal] = Relational,
417+
[BinaryOpcode.NotEqual] = Relational,
418+
[BinaryOpcode.And] = Bitwise,
419+
[BinaryOpcode.Xor] = Bitwise,
420+
[BinaryOpcode.Or] = Bitwise,
421+
[BinaryOpcode.LAnd] = LogicalAndOr,
422+
[BinaryOpcode.LOr] = LogicalAndOr,
423+
[BinaryOpcode.Assign] = Assignment,
424+
[BinaryOpcode.MulAssign] = Assignment,
425+
[BinaryOpcode.DivAssign] = Assignment,
426+
[BinaryOpcode.RemAssign] = Assignment,
427+
[BinaryOpcode.AddAssign] = Assignment,
428+
[BinaryOpcode.SubAssign] = Assignment,
429+
[BinaryOpcode.ShlAssign] = Assignment,
430+
[BinaryOpcode.ShrAssign] = Assignment,
431+
[BinaryOpcode.AndAssign] = Assignment,
432+
[BinaryOpcode.XorAssign] = Assignment,
433+
[BinaryOpcode.OrAssign] = Assignment,
434+
}
435+
436+
const C_Prec[elemsof(BinaryOpcode)] C_Prec_table = {
437+
[BinaryOpcode.Multiply] = Multiplicative,
438+
[BinaryOpcode.Divide] = Multiplicative,
439+
[BinaryOpcode.Remainder] = Multiplicative,
440+
[BinaryOpcode.Add] = Additive,
441+
[BinaryOpcode.Subtract] = Additive,
442+
[BinaryOpcode.ShiftLeft] = Shift,
443+
[BinaryOpcode.ShiftRight] = Shift,
444+
[BinaryOpcode.LessThan] = Relative,
445+
[BinaryOpcode.GreaterThan] = Relative,
446+
[BinaryOpcode.LessEqual] = Relative,
447+
[BinaryOpcode.GreaterEqual] = Relative,
448+
[BinaryOpcode.Equal] = Equality,
449+
[BinaryOpcode.NotEqual] = Equality,
450+
[BinaryOpcode.And] = And,
451+
[BinaryOpcode.Xor] = Xor,
452+
[BinaryOpcode.Or] = Or,
453+
[BinaryOpcode.LAnd] = LogicalAnd,
454+
[BinaryOpcode.LOr] = LogicalOr,
455+
[BinaryOpcode.Assign] = Assignment,
456+
[BinaryOpcode.MulAssign] = Assignment,
457+
[BinaryOpcode.DivAssign] = Assignment,
458+
[BinaryOpcode.RemAssign] = Assignment,
459+
[BinaryOpcode.AddAssign] = Assignment,
460+
[BinaryOpcode.SubAssign] = Assignment,
461+
[BinaryOpcode.ShlAssign] = Assignment,
462+
[BinaryOpcode.ShrAssign] = Assignment,
463+
[BinaryOpcode.AndAssign] = Assignment,
464+
[BinaryOpcode.XorAssign] = Assignment,
465+
[BinaryOpcode.OrAssign] = Assignment,
466+
}
467+
403468
fn QualType Analyser.checkBinopComparison(Analyser* ma, BinaryOperator* b, QualType lhs, QualType rhs) {
404469
QualType lcanon = lhs.getCanonicalType();
405470
QualType rcanon = rhs.getCanonicalType();
@@ -475,6 +540,16 @@ fn QualType Analyser.checkPointerFuncComparison(Analyser* ma, BinaryOperator* b,
475540
return QualType_Invalid;
476541
}
477542

543+
fn bool incompatible_opcodes(BinaryOpcode op1, BinaryOpcode op2) {
544+
C_Prec c_p1 = C_Prec_table[op1];
545+
C_Prec c_p2 = C_Prec_table[op2];
546+
Prec c2_p1 = Prec_table[op1];
547+
Prec c2_p2 = Prec_table[op2];
548+
if (c2_p1 == c2_p2) return c2_p1 == Relational || c_p1 != c_p2;
549+
if (c2_p1 < c2_p2) return c_p1 >= c_p2;
550+
return c_p1 <= c_p2;
551+
}
552+
478553
fn QualType Analyser.analyseBinaryOperator(Analyser* ma, Expr** e_ptr) {
479554
Expr* e = *e_ptr;
480555
BinaryOperator* b = (BinaryOperator*)e;
@@ -510,6 +585,23 @@ fn QualType Analyser.analyseBinaryOperator(Analyser* ma, Expr** e_ptr) {
510585
Expr* lhs = b.getLHS();
511586
Expr* rhs = b.getRHS();
512587

588+
if (lhs.isBinaryOperator()) {
589+
BinaryOpcode op1 = cast<BinaryOperator*>(lhs).getOpcode();
590+
if (incompatible_opcodes(opcode, op1)) {
591+
ma.error(lhs.getLoc(), "operators '%s' and '%s' do not combine without parentheses",
592+
opcode.str(), op1.str());
593+
return QualType_Invalid;
594+
}
595+
}
596+
if (rhs.isBinaryOperator()) {
597+
BinaryOpcode op2 = cast<BinaryOperator*>(rhs).getOpcode();
598+
if (incompatible_opcodes(opcode, op2)) {
599+
ma.error(rhs.getLoc(), "operators '%s' and '%s' do not combine without parentheses",
600+
opcode.str(), op2.str());
601+
return QualType_Invalid;
602+
}
603+
}
604+
513605
if (!validBinOpKind(ltype) || ltype.isVoid()) {
514606
QualType tl = lhs.getType();
515607
ma.error(lhs.getLoc(), "invalid operand to binary expression '%s'", tl.diagName());

ast/binary_operator.c2

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ public fn bool BinaryOpcode.isComparison(BinaryOpcode opcode) {
9090
return opcode >= BinaryOpcode.LessThan && opcode <= BinaryOpcode.NotEqual;
9191
}
9292

93+
public fn const char* BinaryOpcode.str(BinaryOpcode opcode) {
94+
return binaryOpcode_names[opcode];
95+
}
96+
9397
type BinaryOperatorBits struct {
9498
u32 : NumExprBits;
9599
u32 kind : 5;

ast/expr.c2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ public fn bool Expr.isTilde(const Expr* e) {
237237
return false;
238238
}
239239

240-
fn bool Expr.isBinaryOperator(const Expr* e) {
240+
public fn bool Expr.isBinaryOperator(const Expr* e) {
241241
return e.getKind() == ExprKind.BinaryOperator;
242242
}
243243

ast_utils/c2_prec.c2

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/* Copyright 2022-2025 Bas van den Berg
2+
* Copyright 2025 Charlie Gordon
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
module c2_prec;
18+
19+
/// PrecedenceLevels - These have been altered from C99 to C2
20+
/// In particular, addition now comes after bitwise and shifts
21+
/// Bitwise is directly after shift and equality and relational have
22+
/// the same precedence.
23+
24+
public type Prec enum u8 {
25+
Unknown, // Not binary operator.
26+
Comma, // ,
27+
Assignment, // =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=
28+
Conditional, // ?
29+
LogicalAndOr, // &&, ||
30+
Relational, // ==, !=, >=, <=, >, <
31+
Additive, // -, +
32+
Bitwise, // ^, |, &
33+
Shift, // <<, >>
34+
Multiplicative, // *, /, %
35+
}
36+
37+
public type C_Prec enum u8 {
38+
Unknown, // Not binary operator.
39+
Comma, // ,
40+
Assignment, // =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=
41+
Conditional, // ?
42+
LogicalOr, // ||
43+
LogicalAnd, // &&
44+
Or, // |
45+
Xor, // ^
46+
And, // &
47+
Equality, // !=, ==
48+
Relative, // >=, <=, >, <
49+
Shift, // <<, >>
50+
Additive, // -, +
51+
Multiplicative, // *, /, %
52+
}

common/file/reader.c2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public fn bool Reader.open(Reader* file, const char* filename) {
5151
return false;
5252
}
5353

54-
if (statbuf.st_mode & S_IFMT != S_IFREG) {
54+
if ((statbuf.st_mode & S_IFMT) != S_IFREG) {
5555
file.error = Err_not_a_file;
5656
close(fd);
5757
return false;

common/utils.c2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public fn bool findProjectDir(PathInfo* info) {
7272
}
7373
} else {
7474
// must be file, not dir
75-
if (buf.st_mode & S_IFMT == S_IFREG) {
75+
if ((buf.st_mode & S_IFMT) == S_IFREG) {
7676
char* path_prefix = base_path + strlen(path);
7777
if (*path_prefix == '/') path_prefix++;
7878
if (info) {

parser/c2_parser_expr.c2

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ module c2_parser;
1717

1818
//import ast_builder local; // BUG: should have to be imported
1919
import ast local;
20+
import c2_prec local;
2021
import constants;
2122
//import number_radix local; // BUG: should have to be imported
2223
import token local;
@@ -25,24 +26,6 @@ import src_loc local;
2526
import ctype local;
2627
import string;
2728

28-
/// PrecedenceLevels - These have been altered from C99 to C2
29-
/// In particular, addition now comes after bitwise and shifts
30-
/// Bitwise is directly after shift and equality and relational have
31-
/// the same precedence.
32-
33-
type Prec enum u8 {
34-
Unknown = 0, // Not binary operator.
35-
Comma = 1, // ,
36-
Assignment = 2, // =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=
37-
Conditional = 3, // ?
38-
LogicalAndOr = 4, // &&, ||
39-
Relational = 5, // ==, !=, >=, <=, >, <
40-
Additive = 6, // -, +
41-
Bitwise = 7, // ^, |, &
42-
Shift = 8, // <<, >>
43-
Multiplicative = 9, // *, /, %
44-
}
45-
4629
fn Expr* Parser.parseExpr(Parser* p) {
4730
Expr* lhs = p.parseAssignmentExpression();
4831
if (lhs.isInitlistAssignment())

recipe.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,10 @@ executable c2c
172172
(ast)
173173
(yaml)
174174

175+
ast_utils/c2_prec.c2
176+
175177
(common)
178+
176179
common/build_file.c2
177180
common/bit_array.c2
178181
common/component_sorter.c2

test/c_generator/expr/check_shift.c2t

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
module test;
77

88
public fn i32 main(i32 argc, const char** argv) {
9-
i32 a = 1 << 2 + 1;
9+
i32 a = 1 << (2 + 1);
1010
i32 b = (1 << 2) + 1;
1111
i32 c = (1 >> 2) + 1;
1212
return 0;
@@ -16,7 +16,7 @@ public fn i32 main(i32 argc, const char** argv) {
1616

1717
int32_t main(int32_t argc, const char** argv)
1818
{
19-
int32_t a = ((1 << 2) + 1);
19+
int32_t a = (1 << ((2 + 1)));
2020
int32_t b = (((1 << 2)) + 1);
2121
int32_t c = (((1 >> 2)) + 1);
2222

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module test;
2+
3+
i32 a = 1 << 2 + 1; // @error{operators '+' and '<<' do not combine without parentheses}
4+
i32 b = 1 << 2 - 1; // @error{operators '-' and '<<' do not combine without parentheses}
5+
i32 c = 1 >> 2 + 1; // @error{operators '+' and '>>' do not combine without parentheses}
6+
i32 d = 1 >> 2 - 1; // @error{operators '-' and '>>' do not combine without parentheses}
7+
i32 a1 = 1 ^ 2 | 3 & 4; // @error{operators '|' and '^' do not combine without parentheses}
8+
i32 a2 = 1 && 2 || 3 && 4 || 5; // @error{operators '||' and '&&' do not combine without parentheses}
9+
i32 a3 = 1 ^ 2 | 3 & 4 == 5 || 6 <= 5; // @error{operators '|' and '^' do not combine without parentheses}

test/c_generator/expr/prec_expr.c2t

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,35 @@
66
module test;
77

88
public fn i32 main(i32 argc, const char** argv) {
9-
i32 a = 1 << 2 + 3;
10-
a = 1 ^ 2 | 3 & 4;
11-
a = 1 && 2 || 3 && 4 || 5;
12-
a = 1 ^ 2 | 3 & 4 == 5 || 6 <= 5;
9+
i32 a;
10+
// a = 1 << 2 + 3;
11+
// a = 1 << (2 + 3); // C
12+
a = (1 << 2) + 3; // C2
13+
14+
// a = 1 ^ 2 | 3 & 4;
15+
// a = (1 ^ 2) | (3 & 4); // C
16+
a = ((1 ^ 2) | 3) & 4; // C2
17+
18+
// a = ((1 && 2) || (3 && 4)) || 5; // C
19+
// a = 1 && 2 || 3 && 4 || 5;
20+
a = (((1 && 2) || 3) && 4) || 5; // C2
21+
22+
// a = 1 ^ 2 | 3 & 4 == 5 || 6 <= 5;
23+
// a = ((1 ^ 2) | (3 & (4 == 5))) || (6 <= 5); // C
24+
a = (((1 ^ 2) | 3) & 4) == 5 || 6 <= 5; // C2
25+
1326
return 0;
1427
}
1528

1629
// @expect{atleast, cgen/build.c}
1730

1831
int32_t main(int32_t argc, const char** argv)
1932
{
20-
int32_t a = ((1 << 2) + 3);
21-
// int32_t a = (1 << (2 + 3)); C99
22-
23-
a = (((1 ^ 2) | 3) & 4);
24-
// a = ((1 ^ 2) | (3 & 4)); C99
25-
26-
a = ((((1 && 2) || 3) && 4) || 5);
27-
// a = (((1 && 2) || (3 && 4)) || 5); C99
28-
29-
a = (((((1 ^ 2) | 3) & 4) == 5) || (6 <= 5));
30-
// a = (((1 ^ 2) | (3 & (4 == 5))) || (6 <= 5)); C99
31-
33+
int32_t a;
34+
a = (((1 << 2)) + 3);
35+
a = (((((1 ^ 2)) | 3)) & 4);
36+
a = (((((((1 && 2)) || 3)) && 4)) || 5);
37+
a = ((((((((1 ^ 2)) | 3)) & 4)) == 5) || (6 <= 5));
3238
return 0;
3339
}
3440

test/expr/constant.c2

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,19 @@ static_assert(1 << 3, 8);
2727
static_assert(1 << 4 >> 1, 8);
2828
static_assert(4 + 2 * 2, 8);
2929
static_assert(4 + 8 / 2, 8);
30-
static_assert(4 + 1 << 2, 8);
30+
static_assert(4 + 1 << 2, 8); // @error{operators '+' and '<<' do not combine without parentheses}
3131
static_assert(true, 1);
3232
static_assert(4 || 4, 1);
3333
static_assert(4 && 4, 1);
34-
static_assert(4 || 4 && 4, 1);
34+
static_assert(4 || 4 && 4, 1); // @error{operators '&&' and '||' do not combine without parentheses}
3535
static_assert(+true, 1);
3636
static_assert(+(4 || 4), 1);
3737
static_assert(+(4 && 4), 1);
38-
static_assert(+(4 || 4 && 4), 1);
38+
static_assert(+(4 || 4 && 4), 1); // @error{operators '&&' and '||' do not combine without parentheses}
3939
static_assert(0+true, 1);
4040
static_assert(0+(4 || 4), 1);
4141
static_assert(0+(4 && 4), 1);
42-
static_assert(0+(4 || 4 && 4), 1);
42+
static_assert(0+(4 || 4 && 4), 1); // @error{operators '&&' and '||' do not combine without parentheses}
4343
static_assert(1 ? 8 : 3, 8);
4444
static_assert(1 < 2, 1);
4545
static_assert(1 > 0, 1);

test/literals/init_operators.c2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ fn i32 test2() {
1616
}
1717

1818
fn void test3() {
19-
i32 a = 1 + 3 * 4 << 1;
19+
i32 a = 1 + 3 * 4 << 1; // @error{operators '+' and '<<' do not combine without parentheses}
2020
i32 b = 1 + 3 * 4 && 1;
2121
}
2222

tools/tester/tester.c2

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,9 +356,9 @@ public fn i32 main(i32 argc, char** argv) {
356356
usize len = strlen(target);
357357
if (len > 1 && target[len - 1] == '/') target[--len] = '\0';
358358

359-
if (statbuf.st_mode & S_IFMT == S_IFREG) {
359+
if ((statbuf.st_mode & S_IFMT) == S_IFREG) {
360360
handle_file(queue, target);
361-
} else if (statbuf.st_mode & S_IFMT == S_IFDIR) {
361+
} else if ((statbuf.st_mode & S_IFMT) == S_IFDIR) {
362362
handle_dir(queue, target);
363363
} else {
364364
print_error("argument must be a regular file a directory: %s", target);

0 commit comments

Comments
 (0)