Pattern matching in instanceof sometimes cannot resolve theoretically resolvable scope

Viewed 98
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

List<Node> list = new ArrayList<>();
Node x = list.get(0);
if (!(x instanceof Comment comment)) {
    if (!(x instanceof Document doc && doc.getFirstChild() instanceof Comment comment)) {
        return null;
    }
}
System.out.println(comment.ToString()); // cannot resolve comment

Variable comment cannot be resolved but it should be - we covered 2 mutually exclusive happy paths and each of them assigns Comment comment - and system out should be able to see comment, otherwise return null would have been triggered.

1 Answers

You can't declare the same variable in two different places using pattern matching. Those are still two different variables, with two different scopes.

Perhaps, we can clear the confusion by giving them different names:

if (!(x instanceof Comment comment)) {
    if (!(x instanceof Document doc && doc.getFirstChild() instanceof Comment comment2)) {
        return null;
    }
    //comment2 and doc are in scope here,
    //but their scope ends with an enclosing block
    System.out.println(comment2.toString());
}
//FAIL! You can't access comment2 outside its scope
System.out.println(comment2.toString()); 
//FAIL! You can't access comment here because 
//the compiler can't guarantee that the block above exited.
System.out.prinltn(comment.toString()

You can verify that pattern-matched variables still remain in their enclosing scope using a simple example like this:

{
    Object o = null;
    if(!(o instanceof String s)) {
          return;
    }
    System.out.println(s); //OK
}
System.out.println(s); //Doesn't work

Sorry, I don't really have an elegant alternative for you to use. You can achieve what you want with a somewhat ugly workaround:

Comment comment;
if (!(x instanceof Comment c)) {
    if (!(x instanceof Document doc && doc.getFirstChild() instanceof Comment c)) {
        return null;
    }
    comment = c;
} else {
    comment = c;
}
System.out.println(comment.toString());

This workaround gets better if you can move this snippet into a separate method:

private static Comment extractComment(Node x) {
    if (!(x instanceof Comment comment)) {
        if (!(x instanceof Document doc && doc.getFirstChild() instanceof Comment comment)) {
            return null;
        }
        return comment;
    } 
    return comment;
}

Or even:

private static Comment extractComment(Node x) {
    if (x instanceof Comment comment) {
        return comment;
    }     
    if (x instanceof Document doc && doc.getFirstChild() instanceof Comment comment) {
        return comment;
    }
    return null;
}
Related