-
-
Notifications
You must be signed in to change notification settings - Fork 353
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: minimise printing parenthesis for type casts in expression #4995
fix: minimise printing parenthesis for type casts in expression #4995
Conversation
I also noticed in |
21524bb
to
a566d5e
Compare
@I-Al-Istannen @MartinWitt can you drop your reviews? I think we need better tests. Maybe you could help me with that? |
Minimising parentheses for casts is conservative at the moment, but mainly because it's not trivial to do right. We can surely think about cases where the parentheses are not required (for example, a direct assignment like your test might be such case), but I'd prefer to still be conservative and only drop the brackets if we are really sure that it will still compile. |
I am sorry I am seeing this so late. It got lost somewhere in my notifications. Thanks for this test case. A patch like checking if the default expression is an instance of invocation and then checking if it has a target and executable ref can fix it.
We should. As of now, I do not have any more test cases in mind. I usually rely on clients reporting bugs in this area to improve the printer.
That is true. Maybe we can add an assertion in our test cases that compiles the pretty-printed string? |
The important point is that we go the safe route, so instead of "don't print parentheses unless ..." the code has to be "always print parentheses unless ...". Too many parentheses are okay, code that doesn't compile is not okay.
I think there are some larger tests doing that. Afaik there are some test cases that take the spoon source code, print it and compile it to make sure the printer works. Maybe we can reuse something like that with the minimize brackets setting. |
This is what is happening right now, don't you think? Initially, we used to print redundant brackets, but then clients requested minimisation so we started adding patches like #3823, #4809, and a few more to adhere to specific cases.
Yes, I plan to do that. Could you point to some test cases which do that? I could use them as an example? |
Co-Authored-By: Hannes Greule <SirYwell@users.noreply.github.com>
@SirYwell I tried to compile the pretty-printed string in I feel the changes in |
There seems to be some flakiness in the CI. Could anyone please run the workflow again? |
You should be able to just create a new Launcher and build the printed model. Set The failing workflow was https://github.com/INRIA/spoon/actions/runs/3438229451/jobs/5734018883, I will need to have a look at that later. A flaky CI is really annoying. |
This is not flaky. I think I introduced some regression. Let me check again. |
This is true for the linked PRs, but with the current state of this PR, we say The
I don't have anything in my mind right now, I would need to skim through existing tests first.
I'm not sure if that's doable, the tests are likely not consistent enough and if it does fail during the test, tooling might break.
Yes, that sounds like a good place for anything related to minimizeRoundBrackets. Generally, I think that should touch the DJPP as little as possible. |
Indeed that was the source. Thank you! I fixed it in recent commits. |
@SirYwell it is ready for review now. |
@@ -400,7 +402,7 @@ public DefaultJavaPrettyPrinter scan(CtElement e) { | |||
} | |||
|
|||
private boolean shouldSetBracket(CtExpression<?> e) { | |||
if (!e.getTypeCasts().isEmpty()) { | |||
if (!e.getTypeCasts().isEmpty() && !isMinimizeRoundBrackets()) { |
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 additional check can still cause issues, because now we can go through the whole method and return false
as a default value, even if there are type casts. It might work by returning !e.getTypeCasts().isEmpty()
at the bottom,
Consider following silly example:
class A {
A a;
int i;
}
class C extends A {
Object a;
}
(the subclass shadows a field of its superclass)
and following statement:
C c = null;
int i = ((A) c).a.i;
This currently does not enter the if body but instead return false
at the end of the method. Obviously, (A) c.a.i
does not compile.
Even worse, we can construct code that still compiles but has different semantics by adjusting the classes as follows:
class A {
A a;
int i;
}
class C extends A {
C a;
A i;
}
and the statements:
C c = null;
var i = ((A) c).a.i;
I think we should return
For this case, we need to enclose However, I think it is becoming very confusing to read through spoon/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java Lines 414 to 419 in 8e23bae
if-conditions should be built upon these three conditions:
|
From my understanding, the method should return
I think we're at a stage where we need to refactor something, but I'm not sure about the where and the how.
With the new additional check at the very top of the method, we do not longer return One idea would be to modify the first if (isMinimizeRoundBrackets()) {
// Do RoundBracketAnalyzer stuff here, make RoundBracketAnalyzer work with type casts too
} else if (!e.getTypeCasts().isEmpty()) {
return true; // keep behavior of non-minimizing printer
}
// drop RoundBracketAnalyzer stuff below, keep everything else as before this PR This way, we can make sure the behavior of non-minimizing printing just remains the same. |
Oh right. I meant I was trying to refactor similarly before I typed #4995 (comment). I made a lot of tests fail hence I delegated it to later PRs. Maybe I can have a look again :) |
@SirYwell do you think the refactoring makes it more readable? |
Yes! This definitely looks better. I'm still somewhat concerned as the RoundBracketAnalyzer is now called in cases with casts present. This wasn't the case before, so any behavior of the RoundBracketAnalyzer probably isn't properly tested for such cases. The trivial fix for now would be to also have an addtional isEmpty check in the return statement here https://github.com/INRIA/spoon/pull/4995/files#diff-69f595720b242e1a1322a888005148a4400e96e04f3fe5a2e898e44e6471ebf4R409 too. |
@SirYwell can you check now? |
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 is good from my side now, but someone else should probably take a look too (@I-Al-Istannen maybe?).
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.
Hey,
I had a look at it with Hannes and we came up with a few more failing cases. In particular we also need brackets around the target of casts sometimes: (int) (1 + 2)
. We hope our list is conservative.
Additionally we fixed
return requiresBrackets == RoundBracketAnalyzer.EncloseInRoundBrackets.YES && e.getTypeCasts().isEmpty();
to ensure it behaves the same as before and doesn't omit casts for some of the new tests we added.
Good luck 🐞
A patch
From c2a903a046d4781015a5f523bc250fbabbd8e619 Mon Sep 17 00:00:00 2001
From: I-Al-Istannen <i-al-istannen@users.noreply.github.com>
Date: Wed, 16 Nov 2022 18:45:34 +0100
Subject: [PATCH] Some comments
Co-authored-by: Hannes Greule <SirYwell@users.noreply.github.com>
---
.../visitor/DefaultJavaPrettyPrinter.java | 32 +++++++++++++++----
.../visitor/DefaultJavaPrettyPrinterTest.java | 4 ++-
src/test/java/spoon/test/eval/EvalTest.java | 2 +-
3 files changed, 29 insertions(+), 9 deletions(-)
diff --git a/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java b/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java
index 9a8b4a935..ede37c500 100644
--- a/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java
+++ b/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java
@@ -68,6 +68,7 @@ import spoon.reflect.code.CtTryWithResource;
import spoon.reflect.code.CtTypeAccess;
import spoon.reflect.code.CtTypePattern;
import spoon.reflect.code.CtUnaryOperator;
+import spoon.reflect.code.CtVariableAccess;
import spoon.reflect.code.CtVariableRead;
import spoon.reflect.code.CtVariableWrite;
import spoon.reflect.code.CtWhile;
@@ -296,7 +297,7 @@ public class DefaultJavaPrettyPrinter implements CtVisitor, PrettyPrinter {
elementPrinterHelper.writeComment(e, CommentOffset.BEFORE);
}
getPrinterHelper().mapLine(e, sourceCompilationUnit);
- if (shouldSetBracket(e)) {
+ if (shouldSetBracketAroundExpressionAndCast(e)) {
context.parenthesedExpression.push(e);
printer.writeSeparator("(");
}
@@ -305,12 +306,29 @@ public class DefaultJavaPrettyPrinter implements CtVisitor, PrettyPrinter {
printer.writeSeparator("(");
scan(r);
printer.writeSeparator(")").writeSpace();
- if (!isMinimizeRoundBrackets()) {
- printer.writeSeparator("(");
- context.parenthesedExpression.push(e);
- }
}
+ if (shouldSetBracketAroundCastTarget(e)) {
+ printer.writeSeparator("(");
+ context.parenthesedExpression.push(e);
+ }
+ }
+ }
+
+ private boolean shouldSetBracketAroundCastTarget(CtExpression<?> expr) {
+ if (!isMinimizeRoundBrackets()) {
+ return true;
}
+
+ if (expr instanceof CtTargetedExpression) {
+ return false;
+ }
+ if (expr instanceof CtLiteral) {
+ return false;
+ }
+ if (expr instanceof CtVariableAccess) {
+ return false;
+ }
+ return true;
}
/**
@@ -401,12 +419,12 @@ public class DefaultJavaPrettyPrinter implements CtVisitor, PrettyPrinter {
return this;
}
- private boolean shouldSetBracket(CtExpression<?> e) {
+ private boolean shouldSetBracketAroundExpressionAndCast(CtExpression<?> e) {
if (isMinimizeRoundBrackets()) {
RoundBracketAnalyzer.EncloseInRoundBrackets requiresBrackets =
RoundBracketAnalyzer.requiresRoundBrackets(e);
if (requiresBrackets != RoundBracketAnalyzer.EncloseInRoundBrackets.UNKNOWN) {
- return requiresBrackets == RoundBracketAnalyzer.EncloseInRoundBrackets.YES && e.getTypeCasts().isEmpty();
+ return requiresBrackets == RoundBracketAnalyzer.EncloseInRoundBrackets.YES || !e.getTypeCasts().isEmpty();
}
if (e.isParentInitialized() && e.getParent() instanceof CtTargetedExpression && ((CtTargetedExpression) e.getParent()).getTarget() == e) {
return e instanceof CtVariableRead<?> && !e.getTypeCasts().isEmpty();
diff --git a/src/test/java/spoon/reflect/visitor/DefaultJavaPrettyPrinterTest.java b/src/test/java/spoon/reflect/visitor/DefaultJavaPrettyPrinterTest.java
index 6546fa2e7..84ff94fe4 100644
--- a/src/test/java/spoon/reflect/visitor/DefaultJavaPrettyPrinterTest.java
+++ b/src/test/java/spoon/reflect/visitor/DefaultJavaPrettyPrinterTest.java
@@ -67,7 +67,9 @@ public class DefaultJavaPrettyPrinterTest {
"1 | 2 & 3",
"(1 | 2) & 3",
"1 | 2 ^ 3",
- "(1 | 2) ^ 3"
+ "(1 | 2) ^ 3",
+ "((int) (1 + 2)) * 3",
+ "(int) (int) (1 + 1)",
})
public void testParenOptimizationCorrectlyPrintsParenthesesForExpressions(String rawExpression) {
// contract: When input expressions are minimally parenthesized, pretty-printed output
diff --git a/src/test/java/spoon/test/eval/EvalTest.java b/src/test/java/spoon/test/eval/EvalTest.java
index 8a565bde2..c6b55b009 100644
--- a/src/test/java/spoon/test/eval/EvalTest.java
+++ b/src/test/java/spoon/test/eval/EvalTest.java
@@ -96,7 +96,7 @@ public class EvalTest {
CtBlock<?> b = type.getMethodsByName("testDoNotSimplifyCasts").get(0).getBody();
assertEquals(1, b.getStatements().size());
b = b.partiallyEvaluate();
- assertEquals("return ((U) ((java.lang.Object) (spoon.test.eval.testclasses.ToEvaluate.castTarget(element).getClass())))", b.getStatements().get(0).toString());
+ assertEquals("return ((U) (java.lang.Object) (spoon.test.eval.testclasses.ToEvaluate.castTarget(element).getClass()))", b.getStatements().get(0).toString());
}
@Test
--
2.38.1
Co-authored-by: Hannes Greule <SirYwell@users.noreply.github.com>
It should have been this way to preserve the old behaviour. Thank you for noticing. The commit looks good to me and I have pushed it. |
@algomaster99 we had some changes to Qodana on the master branch. Could you rebase/merge with the master to run the checks with the current Qodana version? |
Done :)
…On Mon, Nov 21, 2022 at 8:59 PM MartinWitt ***@***.***> wrote:
@algomaster99 <https://github.com/algomaster99> we had some changes to
Qodana on the master branch. Could you rebase/merge with the master to run
the checks with the current Qodana version?
—
Reply to this email directly, view it on GitHub
<#4995 (comment)>, or
unsubscribe
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Thanks @algomaster99 |
Reference: #4881 (comment)
The changes fix bracket printing for expressions with type casts. It actually does two things:
((int) myInt)
are not printed if minimization is requested.((int) (myInt))
. Maybe, I should do this in a separate PR because it may entail a discussion.