eclipse-xtext / eclipse-xtext/xtext
Quickfix NullPointerException in PartialSerializer
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 831
- Forks
- 330
- Avg merge
- 3d 7h
- Merged PRs (30d)
- 12
Description
https://www.eclipse.org/forums/index.php/m/1779172
I'm having problems in Xtext 2.13 with quickfixes that i didn't have in 2.12, i suppose related to the changes applied to support multiple quickfix resolutions.
The problem appears when a node A is added to the AST tree and then another node is added as a child of A
java.lang.NullPointerException
at org.eclipse.xtext.ide.serializer.impl.PartialSerializer$SerializeRecursiveStrategy.serialize(PartialSerializer.java:155)
at org.eclipse.xtext.ide.serializer.impl.PartialSerializer.serializeChanges(PartialSerializer.java:280)
at org.eclipse.xtext.ide.serializer.impl.RecordingXtextResourceUpdater.applyChange(RecordingXtextResourceUpdater.java:80)
at org.eclipse.xtext.ide.serializer.impl.ChangeSerializer.endRecordChanges(ChangeSerializer.java:162)
at org.eclipse.xtext.ide.serializer.impl.ChangeSerializer.applyModifications(ChangeSerializer.java:93)
at org.eclipse.xtext.ui.editor.model.edit.BatchModification.applyInWorkspace(BatchModification.java:142)
The problem happens with a single quickfix, but it can be avoided somehow waiting to add the node A until it's fully set.
The problem is much more difficult to solve if several quickfixes are applied together and the business rules are complex
As an example, i've made a simple test case
grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals
generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"
Model:
classes+=Class* statements+=Statement+;
Class:
'class' name=ID '{' vars+=Variable+ '}';
Variable: name=ID;
Statement: var=[Variable|VariableQN] '=' INT;
VariableQN hidden(): ID '.' ID;
@Fix(Diagnostic.LINKING_DIAGNOSTIC)
def void fixMissingEntity(Issue issue, IssueResolutionAcceptor acceptor) {
if (issue.message.contains("Variable")) {
fixMissingVariable(issue, acceptor);
}
}
private def fixMissingVariable(Issue issue, IssueResolutionAcceptor acceptor) {
acceptor.acceptMulti(
issue,
"Create missing variable definition",
"Create missing variable definition",
"Entity.gif",
[ Statement element, ICompositeModificationContext<Statement> context |
val fullReferenceName = getIssueMissingReferenceName(context.issue)
val nameArray = fullReferenceName.split("\\.")
val className = nameArray.get(0)
val fieldName = nameArray.get(1)
val root = element.eContainer as Model
context.addModification(root, [ctx |
var cl = root.classes.findFirst[it.name == className]
if (cl === null) {
// The class didn't exist
cl = MyDslFactory::eINSTANCE.createClass() => [
name = className
];
// before adding the class, we can add the field
addVariable(cl, fieldName)
ctx.classes.add(cl)
// after adding the class, adding the field will cause NullPointerException in Serialization
// addVariable(cl, fieldName)
} else {
// The class existed (maybe created in a previous multiquickfix)
// NullPointerException if 2 fixs are applied together and the parent class was added in the first fix
addVariable(cl, fieldName)
}
])
]
)
}
protected def boolean addVariable(Class cl, String fieldName) {
val atr = MyDslFactory::eINSTANCE.createVariable() => [
name = fieldName
];
cl.vars.add(atr)
}
private def String getIssueMissingReferenceName(Issue issue) {
// Not the best way ...
val issueMessage = issue.message
val j = issueMessage.lastIndexOf("'")
val i = issueMessage.lastIndexOf("'", j - 1)
issueMessage.substring(i+1, j)
}
Example code:
// aplying the fix to this 2 problems together works because they create their own classes
Class1.test1 = 1
Class2.test2 = 1
// aplying the fix to this 2 problems together don't work because the first adds the class plus field and the second adds another field to the new class
Class3.test3 = 1
Class3.test4 = 1
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at org.eclipse.xtext.ide.serializer.impl.PartialSerializer.java:155 and trace serializeChanges through RecordingXtextResourceUpdater.java and ChangeSerializer.java. Reproduce the provided multi-quickfix example where a class and child variable are added, then verify that applying single and combined quickfixes no longer causes a NullPointerException.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100