required methods are implemented, and to reject source files that do not meet
these requirements) and at run time (to perform linkage dynamically.) It is
important to ensure that linkage decisions made at compile time and run time
are consistent, but issues like separate compilation and dynamic linking pose
challenges to this goal, since class files compiled separately may not provide a
consistent view of the type hierarchy at run time, which may lead to runtime
errors.
This model divides rules into two categories – typing-related (those beginning
with T-) and linkage-related (those beginning with R-). A compiler would
execute and enforce all the rules; the runtime would execute only the linkage
rules. In a well-typed program, the compiler and runtime view of the world
should be identical; in an inconsistently-typed program (say, due to separate
compilation), it may still be possible to perform method linkage at runtime
according to the R- rules. The treatment of separate compilation and dynamic
linkage is outside the scope of this model.
5 Method typing
Figure 3 shows the typing rules for resolving the type of method m() in classes
and interfaces. It defines the lookup function mtype(T ), which describes the
return type of the method m() in T .
Central to the typing analysis is identifying whether a method m() in type
T (whether defined explicitly or inherited) can legally override all signatures of
m() from supertypes of T . Java supports covariant overrides – where a method
in a subclass overrides a method in a supertype, but provides a narrower return
type. If a method m() in T overrides one or more methods from supertypes, the
return type of m() in T must be a subtype of each of the return types of m()
from supertypes. If there is such a type, we say m() has a well-defined return
type in T .
The value of mtype(T ) is a set of types, and is one of the following:
• The empty set {}, indicating that the method m() is not a member of T ;
• The set { U }, indicating that the method m() has a well-defined return
type in T , and that return type is U;
• undefined, indicating that the method m() does not have a well-defined
return type in T . The compiler should reject types for which mtype(T ) is
undefined, but having a well-defined return type is not sufficient to declare
that T is well-formed (for example, T could have conflicting defaults or
problems with covariant overrides.)
By defining mtype in this way, we can explicitly distinguish between the
case where we know that type T does not have a member m() (mtype(T ) =
{}), and the case where we cannot assign a well-defined type for m() in T
(mtype(T ) = undefined). If we bind a symbol to mtype(T ) in the precondition of
a typing judgement (such as the binding of S in T-ClassNone), and mtype(T )
is undefined, then the binding fails and therefore the judgement does not apply.
3