Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/warn missing fallthrough #1094

Closed

Conversation

yashmasani
Copy link
Contributor

@yashmasani yashmasani commented Oct 21, 2023

Add and report a diagnostic for missing fallthrough within switch statements.
Fixes #1081

Hash_Set<String8_View> cases;
while (keep_going) {
auto is_comment_line = [&]() -> bool {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@strager the function to find the comment, is_comment_line skips all whitespace, which is similar to the lexer function. However, the lexer handles many more cases of characters. Do you think all those cases should be included within is_comment_line as well ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must fix: We should not repeat comment-parsing code like you have done here.

I see two options:

  • Call Lexer's code directly instead of reimplementing or copy-pasting it here; or
  • Teach the lexer to communicate when a comment was seen before a token.
    We should try the latter solution as I think it will be more reliable and useful. I think adding a bool has_leading_comment; into the Token class should be simple enough for our needs. Token::has_leading_newline already exists, and the code for has_leading_comment might be very similar.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. I went with the suggestion of adding has_leading_comment and it worked very well 👍

Copy link
Collaborator

@strager strager left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must fix: The following code should not report any diagnostic, but your code reports several diagnostics for it:

function f(a) {
  for (let c in a) {
    switch (c) {
    case 1:
      return 42;
    case 2:
      break;
    case 3:
      continue;
    case 4:
      throw new Error();
    case 5:
    case 6:
      if (c > 5) {
        return 1;
      } else {
        return 2;
      }
    case 7:
    }
  }
}

Fixing most of these cases should be easy, but the if looks challenging. For now, I think it's fine if we allow some false negatives by turning off the diagnostic if we see an if, for, while, try, etc.

@@ -759,6 +759,13 @@ struct Diag_Duplicated_Cases_In_Switch_Statement {
Source_Code_Span duplicated_switch_case;
};

struct Diag_Explicit_Fallthrough_Comment_In_Switch {
[[qljs::diag("E0423", Diagnostic_Severity::warning)]] //
[[qljs::message("missing fallthrough comment",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: This message and the message in docs/errors/E0423.md should match.

@@ -2766,15 +2766,51 @@ void Parser::parse_and_visit_switch(Parse_Visitor_Base &v) {

bool keep_going = true;
bool is_before_first_switch_case = true;
Token prev_token;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I think this variable's name is bad. prev_token does not refer to the token just before the current token; it refers to a prior case or default token or the first token of a statement.

Maybe previous_statement_first_token is a better name?

@@ -759,6 +759,13 @@ struct Diag_Duplicated_Cases_In_Switch_Statement {
Source_Code_Span duplicated_switch_case;
};

struct Diag_Explicit_Fallthrough_Comment_In_Switch {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: We normally name the diagnostic struct after the problem, not the solution. Therefore this should be named something like Diag_Fallthrough_Without_Comment_In_Switch.

u8"switch(cond1){case 1:\nfoo()\ncase 2:\nlongBarFn()\ndefault:}"_sv, //
u8" ^^^^^^^^^ Diag_Explicit_Fallthrough_Comment_In_Switch"_diag, //
u8" ^^^ Diag_Explicit_Fallthrough_Comment_In_Switch"_diag);
// check for false positive
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Hash_Set<String8_View> cases;
while (keep_going) {
auto is_comment_line = [&]() -> bool {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must fix: We should not repeat comment-parsing code like you have done here.

I see two options:

  • Call Lexer's code directly instead of reimplementing or copy-pasting it here; or
  • Teach the lexer to communicate when a comment was seen before a token.
    We should try the latter solution as I think it will be more reliable and useful. I think adding a bool has_leading_comment; into the Token class should be simple enough for our needs. Token::has_leading_newline already exists, and the code for has_leading_comment might be very similar.

Comment on lines 441 to 442
u8"switch(cond1){case 1:\nfoo()\ncase 2:\nbar() //fallthrough\ndefault:}"_sv, //
u8" ^^^ Diag_Explicit_Fallthrough_Comment_In_Switch"_diag);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must fix: I think the diagnostic is in the wrong place. As a user, I would expect the diagnostic to be between the statement and the following case, not at the beginning of the statement. In other words, the diagnostic should point me to where I should fix the problem. For example, the diagnostic could be just before case:

      u8"switch(cond1){case 1:\nfoo()\ncase 2:\nbar() //fallthrough\ndefault:}"_sv,  //
      u8"                              ` Diag_Explicit_Fallthrough_Comment_In_Switch"_diag);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. I went with the suggestion of adding has_leading_comment 👍
Also switched the diag to be just before case or default.

@@ -0,0 +1,29 @@
# E0423: missing fallthrough comment in switch case
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"missing fallthrough comment" is good, but if the programmer intended to write a 'break', this diagnostic message is misleading.

Maybe this is a better message:

missing 'break;' or '// fallthrough' comment between statement and 'case'

For reference, here is the message from ESLint's no-fallthrough rule:

Expected a 'break' statement before 'case'.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.
Switched to missing 'break;' or '// fallthrough' comment between statement and 'case'
FYI: Also had to switch diag code to E0427 since E0423 was used recently.

@strager
Copy link
Collaborator

strager commented Oct 23, 2023

Thanks for the pull request. I think you're on the right track. There are a lot of things to consider, though, which is one reason I didn't mark the task as for-hire or good-first-issue.

@yashmasani
Copy link
Contributor Author

Thanks for the pull request. I think you're on the right track. There are a lot of things to consider, though, which is one reason I didn't mark the task as for-hire or good-first-issue.

Thank you for the review. All comments were very clear and concise so I could make the changes easily.

Copy link
Collaborator

@strager strager left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must fix: Your patch introduces a false positive diagnostic in the example for E0140:

function getText(node) {
  switch (node.nodeType) {
    case document.TEXT_NODE:
      return node.nodeValue;
    case: {
      let result = "";
      for (let child of node.childNodes) {
        result += getText(child, document);
      }
      return result;
    }
    default:
      throw new Error("Unsupported DOM node type");
  }
}

Comment on lines 10 to 12
foo();
default:
bar();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These examples assume that globals foo and bar exist, but they don't. This causes warning E0057 ('use of undeclared variable: foo').

To fix this, I suggest writing console.log calls instead. (The console global should exist in these examples.)

Hash_Set<String8_View> cases;
auto is_valid_end_of_case = [](Token_Type tk) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Nice helper function.

@@ -2824,6 +2843,13 @@ void Parser::parse_and_visit_switch(Parse_Visitor_Base &v) {
break;

case Token_Type::kw_case: {
if (!is_before_first_switch_case &&
!is_valid_end_of_case(previous_statement_first_token.type) &&
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GCC reports a warning because it thinks previous_statement_first_token might be uninitialized here.

To fix the warning, I think making previous_statement_first_token a std::optional is a decent solution.

@@ -263,6 +263,7 @@ TEST_F(Test_Parse, asi_between_expression_statement_and_switch_label) {
switch (x) {
case a:
f()
//fallthrough
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@strager
Copy link
Collaborator

strager commented Nov 16, 2023

If you want, I can fix the remaining issues. Let me know.

@yashmasani
Copy link
Contributor Author

If you want, I can fix the remaining issues. Let me know.

I addressed the remaining issues. Thanks!

@strager
Copy link
Collaborator

strager commented Nov 21, 2023

Landed as commit 422f3b4. 👍

@strager strager closed this Nov 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Warn on missing fallthrough comment in switch
2 participants