improving javac diagnostics - opinion needed

Maurizio Cimadamore Maurizio.Cimadamore at Sun.COM
Mon Mar 23 03:26:14 PDT 2009


Hi
as you might know I've spent some time in order to improve the quality 
of javac's error messages [2, 4]. Some chunks of this work have already 
been pushed to the tl workspace; those changes involved mostly the 
refactoring the error messages layout - e.g. the error line has been 
moved from the bottom of the message to the second line, so that now 
messages have the following structure (see [1, 5] for more detailed info):

<SUMMARY> (one line description of the error message)
<ERROR LINE>
<DETAILS> (more info about the error, e.g. expected/found types)

The remaining part of this work on error messages is to improve the 
integration between the javac diagnostic subsystem and the Java 
type-system. This involves a number of improvements involving how 
type-variables, intersection types and captured types are rendered in a 
given error message. Consider the following example (taken form [2]):

class Foo<T extends String> {
  <T extends Integer> void foo(T t) {           
      test(t);
  }
  void test(T t) {}
}

This used to generate the following diagnostic:

TestX.java:3: method test in class Foo<T> cannot be applied to given types
      test(t);
      ^
  required: T
  found: T
1 error

Once my work on diagnostics is complete, the above message will be 
displayed as follows:

Test.java:3: method test in class Foo<T#0> cannot be applied to given types
    test(t);
    ^
required: T#0
found: T#1
where T#0,T#1 are type­ variables:
 T#0 extends String
  (declared in class Foo)
 T#1 extends Integer
  (declared in method <T>foo(T))     
1 error

As you can see, the new error is more informative, as it contains a lot 
of details about the error message elements - e.g. for each type 
variables, the corresponding declared bound(s) is reported and clashing 
names are disambiguated with integer indexes (if needed) - in order to 
avoid confusion.

A similar technique is exploited for captured type-variables: consider 
the following example (taken from [3]):

interface List<E> {}

class Test {
    <T> void merge(List<T> l1, List<T> l2) {}
    void test(List<? extends Test> list) {
	merge(list, list);
    }
}

The standard javac diagnostic is:

../Test.java:6: method merge in class Test cannot be applied to given types
	merge(list, list);
	^
  required: List<T>,List<T>
  found: List<capture#1 of ? extends Test>,List<capture#2 of ? extends Test>
1 error

Which will be transformed to:

Test.java:6: method merge in class Test cannot be applied to given types
	merge(list, list);
	^
  required: List<T>,List<T>
  found: List<? #1>,List<? #2>
  where T is a type-variable:
    T extends Object
      (declared in method <T>merge(List<T>,List<T>))
  where ? #1,? #2 are fresh type-variables:
    ? #1 extends Test
      (capture of ? extends Test)
    ? #2 extends Test
      (capture of ? extends Test)
1 error


As you can see, captured types are represented with a '?' sign, followed 
by an integer index - at the end of the diagnostic a list of 'where' 
clauses collects all the info about the captured types that have been 
displayed such as upper/lower bounds and where the captured type comes 
from. For intersection types a similar technique has been exploited (for 
brevity I won't include an example here - an intersection type is 
represented by the '&' sign followed by an integer index - and the info 
about the types forming the intersection are given in a where clause 
similar to the ones shown above).

The new error messages are very powerful - and they provide a lot of 
info that can help the programmers to know more about an error messages 
- they proved to be a very useful tool in detecting ambiguities/name 
clashes. Their biggest disadvantage is perhaps in their verbosity - 
which leads to the question: should javac emit by default the new 
augmented diagnostics ? Or, perhaps, should javac just print old 
diagnostics (by default) *plus* a note at the end of the output, to 
advise the user that more info is available and that it can be retrieved 
by passing some verbosity option to the compiler?

When we designed this new diagnostic subsystem we felt that the more 
info available the better - we also didn't worry too much about the 
diagnostic verbosity as (i) an IDE could easily present the contents of 
the error message in a more sophisticated way (e.g. tree representation) 
so that the user can optionally 'click' on parts of the diagnostics in 
order to retrieve more details and (ii) the diagnostic subsystem (see 
[1]) is now mature enough to display only selected parts of a given 
diagnostic - which means that it would be quite easy to include options 
to optionally disable  the 'where' clauses in case the user doesn't need 
them. Moreover I expect standard error messages (e.g. not related to 
generics) not to be affected by where clauses at all.

So - how do you think the formal of the jdk 7's javac should look like? 
Do you prefer more informartive but longer diagnostics enabled by 
default, or do you think it would be better to generate additional info 
on-demand ?

Thanks
Maurizio

[1] http://blogs.sun.com/mcimadamore/entry/playing_with_formatters
[2] http://blogs.sun.com/mcimadamore/entry/improving_javac_diagnostics
[3] http://blogs.sun.com/ahe/entry/diagnosing_captured_wildcards
[4] http://monaco.sfbay.sun.com/detail.jsf?cr=6492019
[5] http://hg.openjdk.java.net/jdk7/tl/langtools/rev/6ada6122dd4f





More information about the compiler-dev mailing list