Search code examples
javalanguage-lawyerjava-17

Why doesn't instanceof pattern matching work with else if in this particular case?


The following snippet does not compile on javac, version 17 (Temurin)

class Instanceof {
    static void doesNotWork(Object o) {
        if (o == null) {
            throw new Error();
        } else if (!(o instanceof String s)) {
            throw new Error();
        }   
        System.out.println(s); // error here
    }
}

It generates this error: cannot find symbol

cannot find symbol
symbol:   variable s
location: class Instanceof

However, the following (in my opinion) equivalent variations work: With an explicit else block:

static void doesWork(Object o) {
    if (o == null) {
        throw new Error();
    } else if (!(o instanceof String s)) {
        throw new Error();
    } else {
        System.out.println(s);
    }
}

Or without an else:

static void doesWork(Object o) {
    if (o == null) {
        throw new Error();
    }
    if (!(o instanceof String s)) {
        throw new Error();
    }
    System.out.println(s);
}

Or with a single if:

static void doesWork(Object o) {
    if (o == null || !(o instanceof String s)) {
        throw new Error();
    }
    System.out.println(s);
}

Is this a bug in javac? If yes, should I report this, but where exactly?


Solution

  • The doesNotWork case is equivalent to this:

    static void doesNotWork(Object o) {
        if (o == null) {
            throw new Error();
        } else {
            if (!(o instanceof String s)) {
                throw new Error();
            }
        }
        System.out.println(s); // error here
    }
    

    This makes it more obvious that String s is inside a block bounded by curly brackets and is therefore out of scope in the same way that this doesn't work either:

    static void doesNotWork(Object o) {
        {
            if (!(o instanceof String s)) {
                throw new Error();
            }
        }
        System.out.println(s); // error here
    }
    

    In the case where it does work, with the println inside the else, it's equivalent to this:

    if (o == null) {
        throw new Error();
    } else {
        if (!(o instanceof String s)) {
            throw new Error();
        } else {
            System.out.println(s);
        }
    }
    

    Which shows the println being in scope.