Skip to content

Commit

Permalink
feat(fe): warn on confusing operator precedence of & and >>
Browse files Browse the repository at this point in the history
  • Loading branch information
toastin0 committed Oct 14, 2023
1 parent ce51f08 commit 14110af
Show file tree
Hide file tree
Showing 12 changed files with 101 additions and 6 deletions.
17 changes: 17 additions & 0 deletions docs/errors/E0715.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# E0715: unintuitive operator precedence when using & and << or >>
Javascript's operator precedence can cause confusing situations and bugs when it
comes to bitshifting with a bitwise and.
```js
let a_bitwise_variable = another_variable & 0x1 << 0x2
```
The code above looks like it evaluates `another_variable & 0x1` first,
but it instead evaluates `0x1 << 0x2` first.
If this is intended behavior, it may be easier to read if you write:
```js
another_variable & (0x1 << 0x2)
```
Otherwise, to get the order of operations correct, do:
```js
(another_variable & 0x1) << 0x2
```

4 changes: 4 additions & 0 deletions po/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -2065,6 +2065,10 @@ msgstr ""
msgid "'async' keyword is not allowed on getters or setters"
msgstr ""

#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp
msgid "unintuitive operator precedence when using & and '{0}'; '{0}' evaluates before &"
msgstr ""

#: test/test-diagnostic-formatter.cpp
#: test/test-vim-qflist-json-diag-reporter.cpp
msgid "something happened"
Expand Down
14 changes: 14 additions & 0 deletions src/quick-lint-js/diag/diagnostic-metadata-generated.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6361,6 +6361,20 @@ const QLJS_CONSTINIT Diagnostic_Info all_diagnostic_infos[] = {
},
},
},

// Diag_Unintuitive_Bitshift_Precedence
{
.code = 715,
.severity = Diagnostic_Severity::warning,
.message_formats = {
QLJS_TRANSLATABLE("unintuitive operator precedence when using & and '{0}'; '{0}' evaluates before &"),
},
.message_args = {
{
Diagnostic_Message_Arg_Info(offsetof(Diag_Unintuitive_Bitshift_Precedence, bitshift_operator), Diagnostic_Arg_Type::source_code_span),
},
},
},
};
}

Expand Down
3 changes: 2 additions & 1 deletion src/quick-lint-js/diag/diagnostic-metadata-generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -436,10 +436,11 @@ namespace quick_lint_js {
QLJS_DIAG_TYPE_NAME(Diag_Missing_Comma_Between_Array_Elements) \
QLJS_DIAG_TYPE_NAME(Diag_Class_Generator_On_Getter_Or_Setter) \
QLJS_DIAG_TYPE_NAME(Diag_Class_Async_On_Getter_Or_Setter) \
QLJS_DIAG_TYPE_NAME(Diag_Unintuitive_Bitshift_Precedence) \
/* END */
// clang-format on

inline constexpr int Diag_Type_Count = 425;
inline constexpr int Diag_Type_Count = 426;

extern const Diagnostic_Info all_diagnostic_infos[Diag_Type_Count];
}
Expand Down
8 changes: 8 additions & 0 deletions src/quick-lint-js/diag/diagnostic-types-2.h
Original file line number Diff line number Diff line change
Expand Up @@ -3279,6 +3279,14 @@ struct Diag_Class_Async_On_Getter_Or_Setter {
Source_Code_Span async_keyword;
Source_Code_Span getter_setter_keyword;
};

struct Diag_Unintuitive_Bitshift_Precedence {
[[qljs::diag("E0715", Diagnostic_Severity::warning)]] //
[[qljs::message(
"unintuitive operator precedence when using & and '{0}'; "
"'{0}' evaluates before &",
ARG(bitshift_operator))]] Source_Code_Span bitshift_operator;
};
}
QLJS_WARNING_POP

Expand Down
2 changes: 2 additions & 0 deletions src/quick-lint-js/fe/parse-expression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ void Parser::visit_expression(Expression* ast, Parse_Visitor_Base& v,
static_cast<Expression::Binary_Operator*>(ast));
this->warn_on_xor_operator_as_exponentiation(
static_cast<Expression::Binary_Operator*>(ast));
this->warn_on_unintuitive_bitshift_precedence(
static_cast<Expression::Binary_Operator*>(ast));
break;
case Expression_Kind::Trailing_Comma: {
auto& trailing_comma_ast = static_cast<Expression::Trailing_Comma&>(*ast);
Expand Down
18 changes: 17 additions & 1 deletion src/quick-lint-js/fe/parse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,23 @@ void Parser::warn_on_comma_operator_in_index(Expression* ast,
}
}
}

void Parser::warn_on_unintuitive_bitshift_precedence(Expression* ast) {
if (ast->kind() != Expression_Kind::Binary_Operator) return;
if (ast->child_count() <= 2) return;
auto* binary_op = static_cast<Expression::Binary_Operator*>(ast);
Source_Code_Span left_op = binary_op->operator_spans_[0];
Source_Code_Span right_op = binary_op->operator_spans_[1];
if (left_op.string_view() == "&" &&
(right_op.string_view() == ">>" || right_op.string_view() == "<<")) {
if (binary_op->child(0)->kind() == Expression_Kind::Variable &&
binary_op->child(1)->kind() == Expression_Kind::Literal &&
binary_op->child(2)->kind() == Expression_Kind::Literal) {
this->diag_reporter_->report(
quick_lint_js::Diag_Unintuitive_Bitshift_Precedence{
.bitshift_operator = right_op});
}
}
}
void Parser::error_on_pointless_string_compare(
Expression::Binary_Operator* ast) {
auto is_comparison_operator = [](String8_View s) {
Expand Down
1 change: 1 addition & 0 deletions src/quick-lint-js/fe/parse.h
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ class Parser {
void warn_on_comma_operator_in_conditional_statement(Expression *);
void warn_on_comma_operator_in_index(Expression *, Source_Code_Span);
void warn_on_xor_operator_as_exponentiation(Expression::Binary_Operator *);
void warn_on_unintuitive_bitshift_precedence(Expression *ast);
void error_on_pointless_string_compare(Expression::Binary_Operator *);
void error_on_pointless_compare_against_literal(
Expression::Binary_Operator *);
Expand Down
4 changes: 3 additions & 1 deletion src/quick-lint-js/i18n/translation-table-generated.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,8 @@ const Translation_Table translation_data = {
{180, 42, 159, 156, 171, 156}, //
{0, 0, 0, 0, 0, 65}, //
{92, 45, 78, 81, 70, 43}, //
{98, 37, 86, 82, 83, 77}, //
{0, 0, 0, 0, 0, 77}, //
{98, 37, 86, 82, 83, 81}, //
{38, 35, 17, 23, 13, 14}, //
{38, 27, 34, 28, 33, 27}, //
{26, 41, 26, 32, 0, 22}, //
Expand Down Expand Up @@ -2282,6 +2283,7 @@ const Translation_Table translation_data = {
u8"unexpected token in variable declaration; expected variable name\0"
u8"unexpected whitespace between '!' and '=='\0"
u8"unicode byte order mark (BOM) cannot appear before #! at beginning of script\0"
u8"unintuitive operator precedence when using & and '{0}'; '{0}' evaluates before &\0"
u8"unmatched '}'\0"
u8"unmatched indexing bracket\0"
u8"unmatched parenthesis\0"
Expand Down
5 changes: 3 additions & 2 deletions src/quick-lint-js/i18n/translation-table-generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ namespace quick_lint_js {
using namespace std::literals::string_view_literals;

constexpr std::uint32_t translation_table_locale_count = 5;
constexpr std::uint16_t translation_table_mapping_table_size = 518;
constexpr std::size_t translation_table_string_table_size = 79733;
constexpr std::uint16_t translation_table_mapping_table_size = 519;
constexpr std::size_t translation_table_string_table_size = 79814;
constexpr std::size_t translation_table_locale_table_size = 35;

QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up(
Expand Down Expand Up @@ -508,6 +508,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up(
"unexpected token in variable declaration; expected variable name"sv,
"unexpected whitespace between '!' and '=='"sv,
"unicode byte order mark (BOM) cannot appear before #! at beginning of script"sv,
"unintuitive operator precedence when using & and '{0}'; '{0}' evaluates before &"sv,
"unmatched '}'"sv,
"unmatched indexing bracket"sv,
"unmatched parenthesis"sv,
Expand Down
13 changes: 12 additions & 1 deletion src/quick-lint-js/i18n/translation-table-test-generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ struct Translated_String {
};

// clang-format off
inline const Translated_String test_translation_table[517] = {
inline const Translated_String test_translation_table[518] = {
{
"\"global-groups\" entries must be strings"_translatable,
u8"\"global-groups\" entries must be strings",
Expand Down Expand Up @@ -5330,6 +5330,17 @@ inline const Translated_String test_translation_table[517] = {
u8"unicode byte ordningsm\u00e4rke (BOM) kan inte f\u00f6rekomma f\u00f6re #! i b\u00f6rjan av skript",
},
},
{
"unintuitive operator precedence when using & and '{0}'; '{0}' evaluates before &"_translatable,
u8"unintuitive operator precedence when using & and '{0}'; '{0}' evaluates before &",
{
u8"unintuitive operator precedence when using & and '{0}'; '{0}' evaluates before &",
u8"unintuitive operator precedence when using & and '{0}'; '{0}' evaluates before &",
u8"unintuitive operator precedence when using & and '{0}'; '{0}' evaluates before &",
u8"unintuitive operator precedence when using & and '{0}'; '{0}' evaluates before &",
u8"unintuitive operator precedence when using & and '{0}'; '{0}' evaluates before &",
},
},
{
"unmatched '}'"_translatable,
u8"unmatched '}'",
Expand Down
18 changes: 18 additions & 0 deletions test/test-parse-warning.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,24 @@ TEST_F(Test_Parse_Warning, warn_on_xor_operation_used_as_exponentiation) {
test_parse_and_visit_expression(u8"4 ^ 3"_sv, no_diags);
test_parse_and_visit_expression(u8"(x+2)^a"_sv, no_diags);
}
TEST_F(Test_Parse_Warning, warn_on_unintuitive_precedence_when_using_bitshift) {
test_parse_and_visit_expression(
u8"var1 & 0x01 >> 0x02"_sv,
u8" ^^ Diag_Unintuitive_Bitshift_Precedence"_diag);
test_parse_and_visit_expression(
u8"var2 & 0x1234 << 0x4321"_sv,
u8" ^^ Diag_Unintuitive_Bitshift_Precedence"_diag);
test_parse_and_visit_statement(
u8"const x = a & 10 << 12"_sv,
u8" ^^ Diag_Unintuitive_Bitshift_Precedence"_diag);
test_parse_and_visit_expression(u8"0x111 << 0x222 & var1"_sv, no_diags);
test_parse_and_visit_expression(u8"a&b>>c"_sv, no_diags);
test_parse_and_visit_expression(u8"x & y << z"_sv, no_diags);
test_parse_and_visit_expression(u8"0xABCD & 0xDCBA << 0xFFFF"_sv, no_diags);
test_parse_and_visit_expression(u8"(a & 0o12) >> 0xBEEF"_sv, no_diags);
test_parse_and_visit_expression(u8"a & (b >> 0xBEEF)"_sv, no_diags);
test_parse_and_visit_expression(u8"a & b >> c"_sv, no_diags);
}
}
}

Expand Down

0 comments on commit 14110af

Please sign in to comment.