United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: 6977800 Regression: invalid resolution of supertype for local class
6977800 : Regression: invalid resolution of supertype for local class

Details
Type:
Bug
Submit Date:
2010-08-17
Status:
Closed
Updated Date:
2011-03-07
Project Name:
JDK
Resolved Date:
2011-03-07
Component:
tools
OS:
generic
Sub-Component:
javac
CPU:
unknown
Priority:
P3
Resolution:
Fixed
Affected Versions:
7
Fixed Versions:
7

Related Reports
Relates:
Relates:

Sub Tasks

Description
Hi,
   recently, we have recieved a report about an incorrectly compiled class (http://netbeans.org/bugzilla/show_bug.cgi?id=187452). Consider the following code:
---------------------------------------------------------
public class p1 {

    public static void resolve() {
        class b {
            int x = 1;
        }
        class c extends b {}

        System.out.println(new c().x);
    }

    static class b {}
}
---------------------------------------------------------

Using a recent JDK7 javac, there is a compilation error that "x" cannot be resolved on the line with "new c().x". This compilation error does not seem correct to me. JDK6 javac accepts that code. I tried to find out the cause, and it seems that this was introduced by a fix for bug 5060485.

                                    

Comments
SUGGESTED FIX

A webrev of this fix is available at the following URL:
http://hg.openjdk.java.net/jdk7/tl/langtools/rev/a75770c0d7f6
                                     
2010-08-19
EVALUATION

This problem is the result of many contributing factors.

First, it seems each time a new class is entered (Enter.java), a new environment is created using Enter.classEnv. This has the effect of creating a new attribution environment nested inside the current one, with its own scope. Enter.java however creates an additional scope that gets then assigned to the class symbol's members fields. As a result, we have two (!!) different scopes at the end of the entering process - the scope in env.info.scope and the one in csym.members() - some symbols are added to the former (e.g. type-parameters, etc.) while other symbols are only added to the latter (e.g. member types).

In fact, member types get added to the 'enclosing scope' and such enclosing scope is retrieved using a special method in Enter that uses the current env - if the env is a class, then the enclosing env is the class scope (members()), otherwise it uses env.info.scope. The enter/memberEnter code has to deal with this scope duplicity and there are places (as MemberEnter.baseEnv()) in which class type-variables have to be added manually to a fake empty scope so that correct rules are enforced as expected [i.e. type-variables shadow outer type-decls].

The type resolution routine in Resolve.java is compatible with this duplication; in fact, the routine performs two rather orthogonal steps repeatedly:

*) first the current scope is accessed to find a type (using env.info.scope)
*) if nothing has been found, try accessing all member types in the enclosing class' scope (env.enclClass.sym.members())

Now, the problem with this example is that local classes are somewhat in between; the resolution routine would expect to see a local class in the first scope (env.info.scope) - however, when it comes to attributing extends/implements clause, the synthetic scope created  for attributing such statements is missing local class symbols. Which means that step 1 of type resolution fails, and type resolution defaults back to step 2, which means finding all member types in the enclosing class declaration - here's why resolution strangely yields p.b rather than resolve().b.

The solution is to slightly change the synthetic scope used for attributing extends/implements clause, so that it also imports all local classes from the enclosing scope.

[NOTE]

The scope for attributing a superclass/superinterface is the enclosing scope which contains the class declaration whose extends/implements clause needs to be attributed; for instance, given the following declarations:

class P {
    interface I {}
}

class T extends P implements I { // error: no I in scope
}

when we attribute P/I, we use as scope the toplevel scope (!!) containing P/T

Unfortunately, this needs a trick - since a superclass declaration might refer to the type-variable of the declaration we are attributing, we need to manually adds type-variables to the scope used to attribute supertypes.

Why all this machinery? Because in the above case we have that T extends P, so all members of P are inherithed in T, which means they are also available in T scope as unqualified names, which would make the above program perfectly legal.
                                     
2010-08-17



Hardware and Software, Engineered to Work Together