Tue, 12 Feb 2013 19:25:09 +0000
8007464: Add graph inference support
Summary: Add support for more aggressive type-inference scheme
Reviewed-by: jjg
1 /*
2 * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package com.sun.tools.javac.util;
27 import java.util.EnumSet;
28 import java.util.HashMap;
29 import java.util.LinkedHashMap;
30 import java.util.Locale;
31 import java.util.Map;
33 import com.sun.tools.javac.code.Kinds;
34 import com.sun.tools.javac.code.Printer;
35 import com.sun.tools.javac.code.Symbol;
36 import com.sun.tools.javac.code.Symbol.*;
37 import com.sun.tools.javac.code.Symtab;
38 import com.sun.tools.javac.code.Type;
39 import com.sun.tools.javac.code.Type.*;
40 import com.sun.tools.javac.code.Types;
42 import static com.sun.tools.javac.code.TypeTag.*;
43 import static com.sun.tools.javac.code.Flags.*;
44 import static com.sun.tools.javac.util.LayoutCharacters.*;
45 import static com.sun.tools.javac.util.RichDiagnosticFormatter.RichConfiguration.*;
47 /**
48 * A rich diagnostic formatter is a formatter that provides better integration
49 * with javac's type system. A diagostic is first preprocessed in order to keep
50 * track of each types/symbols in it; after these informations are collected,
51 * the diagnostic is rendered using a standard formatter, whose type/symbol printer
52 * has been replaced by a more refined version provided by this rich formatter.
53 * The rich formatter currently enables three different features: (i) simple class
54 * names - that is class names are displayed used a non qualified name (thus
55 * omitting package info) whenever possible - (ii) where clause list - a list of
56 * additional subdiagnostics that provide specific info about type-variables,
57 * captured types, intersection types that occur in the diagnostic that is to be
58 * formatted and (iii) type-variable disambiguation - when the diagnostic refers
59 * to two different type-variables with the same name, their representation is
60 * disambiguated by appending an index to the type variable name.
61 *
62 * <p><b>This is NOT part of any supported API.
63 * If you write code that depends on this, you do so at your own risk.
64 * This code and its internal interfaces are subject to change or
65 * deletion without notice.</b>
66 */
67 public class RichDiagnosticFormatter extends
68 ForwardingDiagnosticFormatter<JCDiagnostic, AbstractDiagnosticFormatter> {
70 final Symtab syms;
71 final Types types;
72 final JCDiagnostic.Factory diags;
73 final JavacMessages messages;
75 /* name simplifier used by this formatter */
76 protected ClassNameSimplifier nameSimplifier;
78 /* type/symbol printer used by this formatter */
79 private RichPrinter printer;
81 /* map for keeping track of a where clause associated to a given type */
82 Map<WhereClauseKind, Map<Type, JCDiagnostic>> whereClauses;
84 /** Get the DiagnosticFormatter instance for this context. */
85 public static RichDiagnosticFormatter instance(Context context) {
86 RichDiagnosticFormatter instance = context.get(RichDiagnosticFormatter.class);
87 if (instance == null)
88 instance = new RichDiagnosticFormatter(context);
89 return instance;
90 }
92 protected RichDiagnosticFormatter(Context context) {
93 super((AbstractDiagnosticFormatter)Log.instance(context).getDiagnosticFormatter());
94 setRichPrinter(new RichPrinter());
95 this.syms = Symtab.instance(context);
96 this.diags = JCDiagnostic.Factory.instance(context);
97 this.types = Types.instance(context);
98 this.messages = JavacMessages.instance(context);
99 whereClauses = new LinkedHashMap<WhereClauseKind, Map<Type, JCDiagnostic>>();
100 configuration = new RichConfiguration(Options.instance(context), formatter);
101 for (WhereClauseKind kind : WhereClauseKind.values())
102 whereClauses.put(kind, new LinkedHashMap<Type, JCDiagnostic>());
103 }
105 @Override
106 public String format(JCDiagnostic diag, Locale l) {
107 StringBuilder sb = new StringBuilder();
108 nameSimplifier = new ClassNameSimplifier();
109 for (WhereClauseKind kind : WhereClauseKind.values())
110 whereClauses.get(kind).clear();
111 preprocessDiagnostic(diag);
112 sb.append(formatter.format(diag, l));
113 if (getConfiguration().isEnabled(RichFormatterFeature.WHERE_CLAUSES)) {
114 List<JCDiagnostic> clauses = getWhereClauses();
115 String indent = formatter.isRaw() ? "" :
116 formatter.indentString(DetailsInc);
117 for (JCDiagnostic d : clauses) {
118 String whereClause = formatter.format(d, l);
119 if (whereClause.length() > 0) {
120 sb.append('\n' + indent + whereClause);
121 }
122 }
123 }
124 return sb.toString();
125 }
127 /**
128 * Sets the type/symbol printer used by this formatter.
129 * @param printer the rich printer to be set
130 */
131 protected void setRichPrinter(RichPrinter printer) {
132 this.printer = printer;
133 formatter.setPrinter(printer);
134 }
136 /**
137 * Gets the type/symbol printer used by this formatter.
138 * @return type/symbol rich printer
139 */
140 protected RichPrinter getRichPrinter() {
141 return printer;
142 }
144 /**
145 * Preprocess a given diagnostic by looking both into its arguments and into
146 * its subdiagnostics (if any). This preprocessing is responsible for
147 * generating info corresponding to features like where clauses, name
148 * simplification, etc.
149 *
150 * @param diag the diagnostic to be preprocessed
151 */
152 protected void preprocessDiagnostic(JCDiagnostic diag) {
153 for (Object o : diag.getArgs()) {
154 if (o != null) {
155 preprocessArgument(o);
156 }
157 }
158 if (diag.isMultiline()) {
159 for (JCDiagnostic d : diag.getSubdiagnostics())
160 preprocessDiagnostic(d);
161 }
162 }
164 /**
165 * Preprocess a diagnostic argument. A type/symbol argument is
166 * preprocessed by specialized type/symbol preprocessors.
167 *
168 * @param arg the argument to be translated
169 */
170 protected void preprocessArgument(Object arg) {
171 if (arg instanceof Type) {
172 preprocessType((Type)arg);
173 }
174 else if (arg instanceof Symbol) {
175 preprocessSymbol((Symbol)arg);
176 }
177 else if (arg instanceof JCDiagnostic) {
178 preprocessDiagnostic((JCDiagnostic)arg);
179 }
180 else if (arg instanceof Iterable<?>) {
181 for (Object o : (Iterable<?>)arg) {
182 preprocessArgument(o);
183 }
184 }
185 }
187 /**
188 * Build a list of multiline diagnostics containing detailed info about
189 * type-variables, captured types, and intersection types
190 *
191 * @return where clause list
192 */
193 protected List<JCDiagnostic> getWhereClauses() {
194 List<JCDiagnostic> clauses = List.nil();
195 for (WhereClauseKind kind : WhereClauseKind.values()) {
196 List<JCDiagnostic> lines = List.nil();
197 for (Map.Entry<Type, JCDiagnostic> entry : whereClauses.get(kind).entrySet()) {
198 lines = lines.prepend(entry.getValue());
199 }
200 if (!lines.isEmpty()) {
201 String key = kind.key();
202 if (lines.size() > 1)
203 key += ".1";
204 JCDiagnostic d = diags.fragment(key, whereClauses.get(kind).keySet());
205 d = new JCDiagnostic.MultilineDiagnostic(d, lines.reverse());
206 clauses = clauses.prepend(d);
207 }
208 }
209 return clauses.reverse();
210 }
212 private int indexOf(Type type, WhereClauseKind kind) {
213 int index = 1;
214 for (Type t : whereClauses.get(kind).keySet()) {
215 if (t.tsym == type.tsym) {
216 return index;
217 }
218 if (kind != WhereClauseKind.TYPEVAR ||
219 t.toString().equals(type.toString())) {
220 index++;
221 }
222 }
223 return -1;
224 }
226 private boolean unique(TypeVar typevar) {
227 int found = 0;
228 for (Type t : whereClauses.get(WhereClauseKind.TYPEVAR).keySet()) {
229 if (t.toString().equals(typevar.toString())) {
230 found++;
231 }
232 }
233 if (found < 1)
234 throw new AssertionError("Missing type variable in where clause " + typevar);
235 return found == 1;
236 }
237 //where
238 /**
239 * This enum defines all posssible kinds of where clauses that can be
240 * attached by a rich diagnostic formatter to a given diagnostic
241 */
242 enum WhereClauseKind {
244 /** where clause regarding a type variable */
245 TYPEVAR("where.description.typevar"),
246 /** where clause regarding a captured type */
247 CAPTURED("where.description.captured"),
248 /** where clause regarding an intersection type */
249 INTERSECTION("where.description.intersection");
251 /** resource key for this where clause kind */
252 private final String key;
254 WhereClauseKind(String key) {
255 this.key = key;
256 }
258 String key() {
259 return key;
260 }
261 }
263 // <editor-fold defaultstate="collapsed" desc="name simplifier">
264 /**
265 * A name simplifier keeps track of class names usages in order to determine
266 * whether a class name can be compacted or not. Short names are not used
267 * if a conflict is detected, e.g. when two classes with the same simple
268 * name belong to different packages - in this case the formatter reverts
269 * to fullnames as compact names might lead to a confusing diagnostic.
270 */
271 protected class ClassNameSimplifier {
273 /* table for keeping track of all short name usages */
274 Map<Name, List<Symbol>> nameClashes = new HashMap<Name, List<Symbol>>();
276 /**
277 * Add a name usage to the simplifier's internal cache
278 */
279 protected void addUsage(Symbol sym) {
280 Name n = sym.getSimpleName();
281 List<Symbol> conflicts = nameClashes.get(n);
282 if (conflicts == null) {
283 conflicts = List.nil();
284 }
285 if (!conflicts.contains(sym))
286 nameClashes.put(n, conflicts.append(sym));
287 }
289 public String simplify(Symbol s) {
290 String name = s.getQualifiedName().toString();
291 if (!s.type.isCompound() && !s.type.isPrimitive()) {
292 List<Symbol> conflicts = nameClashes.get(s.getSimpleName());
293 if (conflicts == null ||
294 (conflicts.size() == 1 &&
295 conflicts.contains(s))) {
296 List<Name> l = List.nil();
297 Symbol s2 = s;
298 while (s2.type.getEnclosingType().hasTag(CLASS)
299 && s2.owner.kind == Kinds.TYP) {
300 l = l.prepend(s2.getSimpleName());
301 s2 = s2.owner;
302 }
303 l = l.prepend(s2.getSimpleName());
304 StringBuilder buf = new StringBuilder();
305 String sep = "";
306 for (Name n2 : l) {
307 buf.append(sep);
308 buf.append(n2);
309 sep = ".";
310 }
311 name = buf.toString();
312 }
313 }
314 return name;
315 }
316 };
317 // </editor-fold>
319 // <editor-fold defaultstate="collapsed" desc="rich printer">
320 /**
321 * Enhanced type/symbol printer that provides support for features like simple names
322 * and type variable disambiguation. This enriched printer exploits the info
323 * discovered during type/symbol preprocessing. This printer is set on the delegate
324 * formatter so that rich type/symbol info can be properly rendered.
325 */
326 protected class RichPrinter extends Printer {
328 @Override
329 public String localize(Locale locale, String key, Object... args) {
330 return formatter.localize(locale, key, args);
331 }
333 @Override
334 public String capturedVarId(CapturedType t, Locale locale) {
335 return indexOf(t, WhereClauseKind.CAPTURED) + "";
336 }
338 @Override
339 public String visitType(Type t, Locale locale) {
340 String s = super.visitType(t, locale);
341 if (t == syms.botType)
342 s = localize(locale, "compiler.misc.type.null");
343 return s;
344 }
346 @Override
347 public String visitCapturedType(CapturedType t, Locale locale) {
348 if (getConfiguration().isEnabled(RichFormatterFeature.WHERE_CLAUSES)) {
349 return localize(locale,
350 "compiler.misc.captured.type",
351 indexOf(t, WhereClauseKind.CAPTURED));
352 }
353 else
354 return super.visitCapturedType(t, locale);
355 }
357 @Override
358 public String visitClassType(ClassType t, Locale locale) {
359 if (t.isCompound() &&
360 getConfiguration().isEnabled(RichFormatterFeature.WHERE_CLAUSES)) {
361 return localize(locale,
362 "compiler.misc.intersection.type",
363 indexOf(t, WhereClauseKind.INTERSECTION));
364 }
365 else
366 return super.visitClassType(t, locale);
367 }
369 @Override
370 protected String className(ClassType t, boolean longform, Locale locale) {
371 Symbol sym = t.tsym;
372 if (sym.name.length() == 0 ||
373 !getConfiguration().isEnabled(RichFormatterFeature.SIMPLE_NAMES)) {
374 return super.className(t, longform, locale);
375 }
376 else if (longform)
377 return nameSimplifier.simplify(sym).toString();
378 else
379 return sym.name.toString();
380 }
382 @Override
383 public String visitTypeVar(TypeVar t, Locale locale) {
384 if (unique(t) ||
385 !getConfiguration().isEnabled(RichFormatterFeature.UNIQUE_TYPEVAR_NAMES)) {
386 return t.toString();
387 }
388 else {
389 return localize(locale,
390 "compiler.misc.type.var",
391 t.toString(), indexOf(t, WhereClauseKind.TYPEVAR));
392 }
393 }
395 @Override
396 public String visitClassSymbol(ClassSymbol s, Locale locale) {
397 String name = nameSimplifier.simplify(s);
398 if (name.length() == 0 ||
399 !getConfiguration().isEnabled(RichFormatterFeature.SIMPLE_NAMES)) {
400 return super.visitClassSymbol(s, locale);
401 }
402 else {
403 return name;
404 }
405 }
407 @Override
408 public String visitMethodSymbol(MethodSymbol s, Locale locale) {
409 String ownerName = visit(s.owner, locale);
410 if (s.isStaticOrInstanceInit()) {
411 return ownerName;
412 } else {
413 String ms = (s.name == s.name.table.names.init)
414 ? ownerName
415 : s.name.toString();
416 if (s.type != null) {
417 if (s.type.hasTag(FORALL)) {
418 ms = "<" + visitTypes(s.type.getTypeArguments(), locale) + ">" + ms;
419 }
420 ms += "(" + printMethodArgs(
421 s.type.getParameterTypes(),
422 (s.flags() & VARARGS) != 0,
423 locale) + ")";
424 }
425 return ms;
426 }
427 }
428 };
429 // </editor-fold>
431 // <editor-fold defaultstate="collapsed" desc="type scanner">
432 /**
433 * Preprocess a given type looking for (i) additional info (where clauses) to be
434 * added to the main diagnostic (ii) names to be compacted.
435 */
436 protected void preprocessType(Type t) {
437 typePreprocessor.visit(t);
438 }
439 //where
440 protected Types.UnaryVisitor<Void> typePreprocessor =
441 new Types.UnaryVisitor<Void>() {
443 public Void visit(List<Type> ts) {
444 for (Type t : ts)
445 visit(t);
446 return null;
447 }
449 @Override
450 public Void visitForAll(ForAll t, Void ignored) {
451 visit(t.tvars);
452 visit(t.qtype);
453 return null;
454 }
456 @Override
457 public Void visitMethodType(MethodType t, Void ignored) {
458 visit(t.argtypes);
459 visit(t.restype);
460 return null;
461 }
463 @Override
464 public Void visitErrorType(ErrorType t, Void ignored) {
465 Type ot = t.getOriginalType();
466 if (ot != null)
467 visit(ot);
468 return null;
469 }
471 @Override
472 public Void visitArrayType(ArrayType t, Void ignored) {
473 visit(t.elemtype);
474 return null;
475 }
477 @Override
478 public Void visitWildcardType(WildcardType t, Void ignored) {
479 visit(t.type);
480 return null;
481 }
483 public Void visitType(Type t, Void ignored) {
484 return null;
485 }
487 @Override
488 public Void visitCapturedType(CapturedType t, Void ignored) {
489 if (indexOf(t, WhereClauseKind.CAPTURED) == -1) {
490 String suffix = t.lower == syms.botType ? ".1" : "";
491 JCDiagnostic d = diags.fragment("where.captured"+ suffix, t, t.bound, t.lower, t.wildcard);
492 whereClauses.get(WhereClauseKind.CAPTURED).put(t, d);
493 visit(t.wildcard);
494 visit(t.lower);
495 visit(t.bound);
496 }
497 return null;
498 }
500 @Override
501 public Void visitClassType(ClassType t, Void ignored) {
502 if (t.isCompound()) {
503 if (indexOf(t, WhereClauseKind.INTERSECTION) == -1) {
504 Type supertype = types.supertype(t);
505 List<Type> interfaces = types.interfaces(t);
506 JCDiagnostic d = diags.fragment("where.intersection", t, interfaces.prepend(supertype));
507 whereClauses.get(WhereClauseKind.INTERSECTION).put(t, d);
508 visit(supertype);
509 visit(interfaces);
510 }
511 }
512 nameSimplifier.addUsage(t.tsym);
513 visit(t.getTypeArguments());
514 if (t.getEnclosingType() != Type.noType)
515 visit(t.getEnclosingType());
516 return null;
517 }
519 @Override
520 public Void visitTypeVar(TypeVar t, Void ignored) {
521 if (indexOf(t, WhereClauseKind.TYPEVAR) == -1) {
522 //access the bound type and skip error types
523 Type bound = t.bound;
524 while ((bound instanceof ErrorType))
525 bound = ((ErrorType)bound).getOriginalType();
526 //retrieve the bound list - if the type variable
527 //has not been attributed the bound is not set
528 List<Type> bounds = (bound != null) &&
529 (bound.hasTag(CLASS) || bound.hasTag(TYPEVAR)) ?
530 types.getBounds(t) :
531 List.<Type>nil();
533 nameSimplifier.addUsage(t.tsym);
535 boolean boundErroneous = bounds.head == null ||
536 bounds.head.hasTag(NONE) ||
537 bounds.head.hasTag(ERROR);
539 if ((t.tsym.flags() & SYNTHETIC) == 0) {
540 //this is a true typevar
541 JCDiagnostic d = diags.fragment("where.typevar" +
542 (boundErroneous ? ".1" : ""), t, bounds,
543 Kinds.kindName(t.tsym.location()), t.tsym.location());
544 whereClauses.get(WhereClauseKind.TYPEVAR).put(t, d);
545 symbolPreprocessor.visit(t.tsym.location(), null);
546 visit(bounds);
547 } else {
548 Assert.check(!boundErroneous);
549 //this is a fresh (synthetic) tvar
550 JCDiagnostic d = diags.fragment("where.fresh.typevar", t, bounds);
551 whereClauses.get(WhereClauseKind.TYPEVAR).put(t, d);
552 visit(bounds);
553 }
555 }
556 return null;
557 }
558 };
559 // </editor-fold>
561 // <editor-fold defaultstate="collapsed" desc="symbol scanner">
562 /**
563 * Preprocess a given symbol looking for (i) additional info (where clauses) to be
564 * asdded to the main diagnostic (ii) names to be compacted
565 */
566 protected void preprocessSymbol(Symbol s) {
567 symbolPreprocessor.visit(s, null);
568 }
569 //where
570 protected Types.DefaultSymbolVisitor<Void, Void> symbolPreprocessor =
571 new Types.DefaultSymbolVisitor<Void, Void>() {
573 @Override
574 public Void visitClassSymbol(ClassSymbol s, Void ignored) {
575 nameSimplifier.addUsage(s);
576 return null;
577 }
579 @Override
580 public Void visitSymbol(Symbol s, Void ignored) {
581 return null;
582 }
584 @Override
585 public Void visitMethodSymbol(MethodSymbol s, Void ignored) {
586 visit(s.owner, null);
587 if (s.type != null)
588 typePreprocessor.visit(s.type);
589 return null;
590 }
591 };
592 // </editor-fold>
594 @Override
595 public RichConfiguration getConfiguration() {
596 //the following cast is always safe - see init
597 return (RichConfiguration)configuration;
598 }
600 /**
601 * Configuration object provided by the rich formatter.
602 */
603 public static class RichConfiguration extends ForwardingDiagnosticFormatter.ForwardingConfiguration {
605 /** set of enabled rich formatter's features */
606 protected java.util.EnumSet<RichFormatterFeature> features;
608 @SuppressWarnings("fallthrough")
609 public RichConfiguration(Options options, AbstractDiagnosticFormatter formatter) {
610 super(formatter.getConfiguration());
611 features = formatter.isRaw() ? EnumSet.noneOf(RichFormatterFeature.class) :
612 EnumSet.of(RichFormatterFeature.SIMPLE_NAMES,
613 RichFormatterFeature.WHERE_CLAUSES,
614 RichFormatterFeature.UNIQUE_TYPEVAR_NAMES);
615 String diagOpts = options.get("diags");
616 if (diagOpts != null) {
617 for (String args: diagOpts.split(",")) {
618 if (args.equals("-where")) {
619 features.remove(RichFormatterFeature.WHERE_CLAUSES);
620 }
621 else if (args.equals("where")) {
622 features.add(RichFormatterFeature.WHERE_CLAUSES);
623 }
624 if (args.equals("-simpleNames")) {
625 features.remove(RichFormatterFeature.SIMPLE_NAMES);
626 }
627 else if (args.equals("simpleNames")) {
628 features.add(RichFormatterFeature.SIMPLE_NAMES);
629 }
630 if (args.equals("-disambiguateTvars")) {
631 features.remove(RichFormatterFeature.UNIQUE_TYPEVAR_NAMES);
632 }
633 else if (args.equals("disambiguateTvars")) {
634 features.add(RichFormatterFeature.UNIQUE_TYPEVAR_NAMES);
635 }
636 }
637 }
638 }
640 /**
641 * Returns a list of all the features supported by the rich formatter.
642 * @return list of supported features
643 */
644 public RichFormatterFeature[] getAvailableFeatures() {
645 return RichFormatterFeature.values();
646 }
648 /**
649 * Enable a specific feature on this rich formatter.
650 * @param feature feature to be enabled
651 */
652 public void enable(RichFormatterFeature feature) {
653 features.add(feature);
654 }
656 /**
657 * Disable a specific feature on this rich formatter.
658 * @param feature feature to be disabled
659 */
660 public void disable(RichFormatterFeature feature) {
661 features.remove(feature);
662 }
664 /**
665 * Is a given feature enabled on this formatter?
666 * @param feature feature to be tested
667 */
668 public boolean isEnabled(RichFormatterFeature feature) {
669 return features.contains(feature);
670 }
672 /**
673 * The advanced formatting features provided by the rich formatter
674 */
675 public enum RichFormatterFeature {
676 /** a list of additional info regarding a given type/symbol */
677 WHERE_CLAUSES,
678 /** full class names simplification (where possible) */
679 SIMPLE_NAMES,
680 /** type-variable names disambiguation */
681 UNIQUE_TYPEVAR_NAMES;
682 }
683 }
684 }