-
-
Notifications
You must be signed in to change notification settings - Fork 636
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
Fix: Prevent ICE on final switch forward referencing its enum #21001
base: master
Are you sure you want to change the base?
Conversation
Thanks for your pull request and interest in making D better, @abulgit! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.
Please see CONTRIBUTING.md for more information. If you have addressed all reviews or aren't sure how to proceed, don't hesitate to ping us with a simple comment. Bugzilla referencesYour PR doesn't reference any Bugzilla issue. If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog. Testing this PR locallyIf you don't have a local development environment setup, you can use Digger to test this PR: dub run digger -- build "master + dmd#21001" |
Props for tackling this bug, forward references can be tricky. 👍 Your code for inferring whether an enum is incomplete looks functional, but there's a simpler way to check the semantic state of symbols that the rest of the compiler uses: the
For the tests, please minimize the amount of added test code, since the test suite is already so large.
So just keep test 1. |
Thank you for the review and suggestions! I've updated the PR
|
compiler/src/dmd/statementsem.d
Outdated
for (Scope* scx = sc; scx; scx = scx.enclosing) | ||
{ | ||
if (scx.scopesym && scx.scopesym.isEnumDeclaration() && scx.scopesym == ed) | ||
{ | ||
error(ss.loc, "cannot use `final switch` on enum `%s` while it is being defined", ed.toChars()); | ||
sc.pop(); | ||
return setError(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check still needs to be removed
compiler/src/dmd/statementsem.d
Outdated
@@ -1959,8 +1959,33 @@ Statement statementSemanticVisit(Statement s, Scope* sc) | |||
ed = ds.isEnumDeclaration(); // typedef'ed enum | |||
if (!ed && te && ((ds = te.toDsymbol(sc)) !is null)) | |||
ed = ds.isEnumDeclaration(); | |||
|
|||
// Circular references | |||
if (ed) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (ed) | |
if (ed && ed.semanticRun < PASS.semanticdone) |
compiler/src/dmd/statementsem.d
Outdated
if (ed.semanticRun < PASS.semanticdone) | ||
{ | ||
error(ss.loc, "cannot use `final switch` on enum `%s` while it is being defined", ed.toChars()); | ||
sc.pop(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you add this line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kept both checks thinking they might catch different cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind this comment, I was asking about sc.pop()
specifically, but I see now that a scope is pushed before and every return path needs to pop it.
compiler/src/dmd/statementsem.d
Outdated
if (ed && ss.cases.length < ed.members.length) | ||
{ | ||
// Remove the old check for incomplete enum members |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like an AI instruction accidentally left in here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no! I remove the previous enum members checks that's why I added that line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the comment doesn't make sense without the context of this Pull Request, and only adds confusion when someone reads it later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!ed && te && ((ds = te.toDsymbol(sc)) !is null))
ed = ds.isEnumDeclaration();
if (ed && ed.semanticRun < PASS.semanticdone)
{
error(ss.loc, "cannot use `final switch` on enum `%s` while it is being defined", ed.toChars());
sc.pop();
return setError();
}
if (ed && ss.cases.length < ed.members.length)
{
did u mean this, remove all the other checks only ed && ed.semanticRun < PASS.semanticdone
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the comment doesn't make sense without the context of this Pull Request, and only adds confusion when someone reads it later.
I wrote it for my own, mistakenlyforgot to remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I understand your point that the in-progress enum declarations should be done with the semanticRun state. So my question is -
You mentioned the semanticRun check should be sufficient. but how the enum's semantic state works.
Also, could you suggest specific debugging or maybe some instrumentation I could add to better understand this problem
Because I wanna understand this whole thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could print the value of ed.semanticRun at the time of the crash, and then locate where it was last set with a debugger / debug print statements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried adding debug points and print statements to trace how semanticRun interacts with enum member initializers, but things are getting so overwhelming for me.
If you or someone else solve this, I’d love to see the final implementation to better understand how this compiler tracks enum in these edge cases. A concrete example would help me learn how to handle similar issues in the future.
Thanks for your guidance!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found that at the time of the crash, em.semanticRun == PASS.semanticdone
which got set here:
dmd/compiler/src/dmd/enumsem.d
Line 195 in a7e2b2e
ed.semanticRun = PASS.semanticdone; |
I tried moving that to after members have run semantic, but that breaks things, so I think we need to use semantic2
here. For structs, semantic2 does analysis of members, and for variables, semantic2 does analysis of initializers. I think we can extend this distinction to enums: semantic(1) can mean 'enum base type is determined', while semantic2 can mean 'all member values have been computed'. So after moving the enum member analysis to semantic2, the final switch check can verify we're at PASS.semantic2done
and that should work.
If you want to give that a try, you can do so, but I can also take care of it. In that case, just close this PR and I'll open my own when I have time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I will give it a try.
I don't understand why this failed
|
Yes, that's why solving semantic analysis bugs is so hard! In this case, I think semantic2 doesn't get run. It's best to take small steps, so I'd start by simply setting the semantic2done flag at the end of the current enumSemantic function and see if that breaks anything. I'd only really extract / move the code as a follow-up refactoring PR. |
Yes, I Debugged the code and Tried so many different approaches, It took so many hrs and finally, this worked. |
Closes: #20867
Fix Details-
final switch
statement is used within an enum's own definitionIt now properly detects and reports the circular reference with the error message: "cannot use
final switch
on enum%s
while it is being defined".Testing-
I added a testcase to verify the fix:
fail_compilation/fix20867.d
This test includes:
Test Case 1: The exact scenario from the GitHub issue
Test Case 2: A variation with multiple enum members
Test Case 3: A regular use of final switch (should compile normally)
Test Case 4: A nested circular reference case
All test cases Work Fine.