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
Give line numbers for uninitialized fields when reporting error on an initializer #380
Give line numbers for uninitialized fields when reporting error on an initializer #380
Conversation
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.
Much better PR!
I'd honestly not be opposed to accepting it as is. But I did have a few minor comments worth at least considering, plus I want to give @msridhar a chance to look over this too.
static String errMsgForInitializer(Set<Element> uninitFields) { | ||
static int GetLineforField(Element uninitField, VisitorState state) { | ||
Tree fieldTree = getTreesInstance(state).getTree(uninitField); | ||
DiagnosticPosition position = (DiagnosticPosition) fieldTree; |
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 recommend adding a comment over this line saying that we expect Tree
to be JCTree
and thus implement DiagnosticPosition
for statements in Java code.
Also, in case that assumption ever breaks, do you guys think we should crash with a cast exception or just return -1
or some other dummy value? My thoughts on this are: a) if we are just printing out the value, then returning -1
makes this code more robust, but b) if we ever use this method for something else where we are acting on the line number info, then we could just be hiding the issue. I think I vote on leaving the code itself as is (with the comment), and just having it crash early if it ever sees a Tree
it can't cast to DiagnosticPosition
, but I thought it was worth to bring up the issue explicitly and get your thoughts on it @Lycheeicy and @msridhar :)
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 think we should just fail fast, which is what the current code will do.
Another possible failure mode is getTreesInstance(state).getTree(uninitField)
returning null
, e.g., if uninitField
comes from bytecode. We should add a null check and throw a RuntimeException
with some appropriate message if that happens.
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.
Thanks for advice, I will add the comment as well as a null check later.
return source.getLineNumber(position.getStartPosition()); | ||
} | ||
|
||
static String errMsgForInitializer(Set<Element> uninitFields, VisitorState state) { |
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.
Technically not needed for non-public methods, but a Javadoc comment makes sense to me here (not needed for GetLineforField
, since the name is fairly self-documenting, although see comment above).
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.
Yes, makes sense. I think this method existed before this PR, but good to add some docs 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.
Sure, that was our issue, then, not a new one, but it's still worth fixing/improving :)
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.
Sounds great! I will add a javadoc comment for errMsgForInitializer!
@@ -302,12 +307,39 @@ boolean symbolHasSuppressWarningsAnnotation(Symbol symbol, String suppression) { | |||
return false; | |||
} | |||
|
|||
static String errMsgForInitializer(Set<Element> uninitFields) { | |||
static int GetLineforField(Element uninitField, VisitorState state) { |
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.
nit: getLineNumForField
would be even more self-documenting as the method name, and proper camel case.
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.
Yes we need to use camel case here (getLineNumForField
). Also, I believe this will work for any Element
coming from source code, not just fields, right? In which case, we can name the method getLineNumForElement
instead.
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.
Yes, I will change the method name to getLineNumForElement, that make great sense!
return source.getLineNumber(position.getStartPosition()); | ||
} | ||
|
||
static String errMsgForInitializer(Set<Element> uninitFields, VisitorState state) { | ||
String message = "initializer method does not guarantee @NonNull "; |
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.
Not sure if this matters for modern Java compilers but... given the amount of concatenations we are doing here, is there any advantage in using a StringBuilder
?
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 think using StringBuilder
is a bit cleaner, rather than all this string concatenation.
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.
Good idea, I will use the StringBuilder instead.
@@ -32,7 +32,7 @@ | |||
|
|||
Object f; | |||
|
|||
// BUG: Diagnostic contains: initializer method does not guarantee @NonNull field f is | |||
// BUG: Diagnostic contains: initializer method does not guarantee @NonNull field f(Line:33) is |
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 going to make adding test cases to these files a bit harder. But: 1) we do need to test the new line number printing code too, so using existing test cases for it makes sense, 2) I am actually fine with discouraging this kind of test cases going forward vs proper unit tests via .addLines(...)
, etc :)
p.s. nothing to change here, just a note.
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 agree. This will discourage us from messing with this older, worse way of writing tests in the future 😄
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.
Yes, thank you for pointing that out.
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.
Looks great overall! Just a few things to fix before we can land this
@@ -302,12 +307,39 @@ boolean symbolHasSuppressWarningsAnnotation(Symbol symbol, String suppression) { | |||
return false; | |||
} | |||
|
|||
static String errMsgForInitializer(Set<Element> uninitFields) { | |||
static int GetLineforField(Element uninitField, VisitorState state) { |
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.
Yes we need to use camel case here (getLineNumForField
). Also, I believe this will work for any Element
coming from source code, not just fields, right? In which case, we can name the method getLineNumForElement
instead.
static String errMsgForInitializer(Set<Element> uninitFields) { | ||
static int GetLineforField(Element uninitField, VisitorState state) { | ||
Tree fieldTree = getTreesInstance(state).getTree(uninitField); | ||
DiagnosticPosition position = (DiagnosticPosition) fieldTree; |
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 think we should just fail fast, which is what the current code will do.
Another possible failure mode is getTreesInstance(state).getTree(uninitField)
returning null
, e.g., if uninitField
comes from bytecode. We should add a null check and throw a RuntimeException
with some appropriate message if that happens.
return source.getLineNumber(position.getStartPosition()); | ||
} | ||
|
||
static String errMsgForInitializer(Set<Element> uninitFields, VisitorState state) { |
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.
Yes, makes sense. I think this method existed before this PR, but good to add some docs now.
return source.getLineNumber(position.getStartPosition()); | ||
} | ||
|
||
static String errMsgForInitializer(Set<Element> uninitFields, VisitorState state) { | ||
String message = "initializer method does not guarantee @NonNull "; |
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 think using StringBuilder
is a bit cleaner, rather than all this string concatenation.
message += | ||
"field " | ||
+ uninitField.toString() | ||
+ "(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.
Right now the line number gets formatted as (Line:XX)
. I think that (line XX)
would look better and be more consistent. @lazaroclapp?
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 agree with that, I will do this kind of change later.
@@ -32,7 +32,7 @@ | |||
|
|||
Object f; | |||
|
|||
// BUG: Diagnostic contains: initializer method does not guarantee @NonNull field f is | |||
// BUG: Diagnostic contains: initializer method does not guarantee @NonNull field f(Line:33) is |
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 agree. This will discourage us from messing with this older, worse way of writing tests in the future 😄
DiagnosticPosition position = (DiagnosticPosition) fieldTree; | ||
if (fieldTree == null) | ||
throw new RuntimeException( | ||
"When getting the line number for uninitialized field, can't get the fieldTree from the element."); |
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.
Given that this should work for arbitrary elements now, I'd change both this message and the variable name from fieldTree
to just tree
.
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.
Agreed.
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.
Yes, that make sense.
TreePath path = state.getPath(); | ||
JCCompilationUnit compilation = (JCCompilationUnit) path.getCompilationUnit(); | ||
JavaFileObject file = compilation.getSourceFile(); | ||
DiagnosticSource source = new DiagnosticSource(file, null); | ||
return source.getLineNumber(position.getStartPosition()); | ||
} | ||
|
||
// Generate the message for uninitialized fields, including the line number for fields. |
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.
Not really in JavaDoc format (see https://www.baeldung.com/javadoc ), but honestly it's enough by me.
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.
Got it, I will change it to be better.
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.
Just a couple more small things
DiagnosticPosition position = (DiagnosticPosition) fieldTree; | ||
if (fieldTree == null) | ||
throw new RuntimeException( | ||
"When getting the line number for uninitialized field, can't get the fieldTree from the element."); |
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.
Agreed.
uninitField = uninitFields.iterator().next(); | ||
message.append("field "); | ||
message.append(uninitField.toString()); | ||
message.append("(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.
Sorry to be picky, but can we have a space before the (
and a lowercase l
? So it will read, e.g., field f (line 33)
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.
It's good to be picky as it makes the code better! I will adjust the message!
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.
🎉
Thanks for the first contribution @Lycheeicy! |
My pleasure! |
This adds the line numbers for uninitialized fields when reporting error on an initializer. As a response to issue #95.
Summary of changes:
-Edit function errMsgForInitializer() in ErrorBuilder.
-Create a new function GetLineforField() which gives back a source line number.
-Adjust the expected error messages in the test cases.