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

Give line numbers for uninitialized fields when reporting error on an initializer #380

Merged
merged 7 commits into from Jan 12, 2020

Conversation

Lycheeicy
Copy link
Contributor

@Lycheeicy Lycheeicy commented Jan 9, 2020

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.

@claassistantio
Copy link

claassistantio commented Jan 9, 2020

CLA assistant check
All committers have signed the CLA.

Copy link
Collaborator

@lazaroclapp lazaroclapp left a 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;
Copy link
Collaborator

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 :)

Copy link
Collaborator

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.

Copy link
Contributor Author

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) {
Copy link
Collaborator

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).

Copy link
Collaborator

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.

Copy link
Collaborator

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 :)

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 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) {
Copy link
Collaborator

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.

Copy link
Collaborator

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.

Copy link
Contributor Author

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 ";
Copy link
Collaborator

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?

Copy link
Collaborator

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.

Copy link
Contributor Author

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
Copy link
Collaborator

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.

Copy link
Collaborator

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 😄

Copy link
Contributor Author

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.

Copy link
Collaborator

@msridhar msridhar left a 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) {
Copy link
Collaborator

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;
Copy link
Collaborator

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) {
Copy link
Collaborator

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 ";
Copy link
Collaborator

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:"
Copy link
Collaborator

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?

Copy link
Contributor Author

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
Copy link
Collaborator

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.");
Copy link
Collaborator

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.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Agreed.

Copy link
Contributor Author

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.
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

Copy link
Collaborator

@msridhar msridhar left a 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.");
Copy link
Collaborator

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 ");
Copy link
Collaborator

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)

Copy link
Contributor Author

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!

Copy link
Collaborator

@msridhar msridhar left a comment

Choose a reason for hiding this comment

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

🎉

@msridhar msridhar merged commit 71b7ec8 into uber:master Jan 12, 2020
@msridhar
Copy link
Collaborator

Thanks for the first contribution @Lycheeicy!

@Lycheeicy
Copy link
Contributor Author

Thanks for the first contribution @Lycheeicy!

My pleasure!

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.

None yet

4 participants