Tue, 08 Jan 2013 10:17:29 +0100
8005184: Restructure DeferredAttr to allow pluggable deferred type completers
Summary: Add hooks to generalize deferred type completion via custom helper objects
Reviewed-by: jjg
1 /*
2 * Copyright (c) 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 */
26 package com.sun.tools.javac.comp;
28 import com.sun.tools.javac.code.*;
29 import com.sun.tools.javac.tree.*;
30 import com.sun.tools.javac.util.*;
31 import com.sun.tools.javac.code.Symbol.*;
32 import com.sun.tools.javac.code.Type.*;
33 import com.sun.tools.javac.comp.Attr.ResultInfo;
34 import com.sun.tools.javac.comp.Infer.InferenceContext;
35 import com.sun.tools.javac.comp.Resolve.MethodResolutionPhase;
36 import com.sun.tools.javac.tree.JCTree.*;
38 import javax.tools.JavaFileObject;
40 import java.util.ArrayList;
41 import java.util.EnumSet;
42 import java.util.LinkedHashSet;
43 import java.util.Map;
44 import java.util.Queue;
45 import java.util.Set;
46 import java.util.WeakHashMap;
48 import static com.sun.tools.javac.code.TypeTag.*;
49 import static com.sun.tools.javac.tree.JCTree.Tag.*;
51 /**
52 * This is an helper class that is used to perform deferred type-analysis.
53 * Each time a poly expression occurs in argument position, javac attributes it
54 * with a temporary 'deferred type' that is checked (possibly multiple times)
55 * against an expected formal type.
56 *
57 * <p><b>This is NOT part of any supported API.
58 * If you write code that depends on this, you do so at your own risk.
59 * This code and its internal interfaces are subject to change or
60 * deletion without notice.</b>
61 */
62 public class DeferredAttr extends JCTree.Visitor {
63 protected static final Context.Key<DeferredAttr> deferredAttrKey =
64 new Context.Key<DeferredAttr>();
66 final Attr attr;
67 final Check chk;
68 final Enter enter;
69 final Infer infer;
70 final Log log;
71 final Symtab syms;
72 final TreeMaker make;
73 final Types types;
75 public static DeferredAttr instance(Context context) {
76 DeferredAttr instance = context.get(deferredAttrKey);
77 if (instance == null)
78 instance = new DeferredAttr(context);
79 return instance;
80 }
82 protected DeferredAttr(Context context) {
83 context.put(deferredAttrKey, this);
84 attr = Attr.instance(context);
85 chk = Check.instance(context);
86 enter = Enter.instance(context);
87 infer = Infer.instance(context);
88 log = Log.instance(context);
89 syms = Symtab.instance(context);
90 make = TreeMaker.instance(context);
91 types = Types.instance(context);
92 }
94 /**
95 * This type represents a deferred type. A deferred type starts off with
96 * no information on the underlying expression type. Such info needs to be
97 * discovered through type-checking the deferred type against a target-type.
98 * Every deferred type keeps a pointer to the AST node from which it originated.
99 */
100 public class DeferredType extends Type {
102 public JCExpression tree;
103 Env<AttrContext> env;
104 AttrMode mode;
105 SpeculativeCache speculativeCache;
107 DeferredType(JCExpression tree, Env<AttrContext> env) {
108 super(DEFERRED, null);
109 this.tree = tree;
110 this.env = env.dup(tree, env.info.dup());
111 this.speculativeCache = new SpeculativeCache();
112 }
114 /**
115 * A speculative cache is used to keep track of all overload resolution rounds
116 * that triggered speculative attribution on a given deferred type. Each entry
117 * stores a pointer to the speculative tree and the resolution phase in which the entry
118 * has been added.
119 */
120 class SpeculativeCache {
122 private Map<Symbol, List<Entry>> cache =
123 new WeakHashMap<Symbol, List<Entry>>();
125 class Entry {
126 JCTree speculativeTree;
127 Resolve.MethodResolutionPhase phase;
129 public Entry(JCTree speculativeTree, MethodResolutionPhase phase) {
130 this.speculativeTree = speculativeTree;
131 this.phase = phase;
132 }
134 boolean matches(Resolve.MethodResolutionPhase phase) {
135 return this.phase == phase;
136 }
137 }
139 /**
140 * Retrieve a speculative cache entry corresponding to given symbol
141 * and resolution phase
142 */
143 Entry get(Symbol msym, MethodResolutionPhase phase) {
144 List<Entry> entries = cache.get(msym);
145 if (entries == null) return null;
146 for (Entry e : entries) {
147 if (e.matches(phase)) return e;
148 }
149 return null;
150 }
152 /**
153 * Stores a speculative cache entry corresponding to given symbol
154 * and resolution phase
155 */
156 void put(Symbol msym, JCTree speculativeTree, MethodResolutionPhase phase) {
157 List<Entry> entries = cache.get(msym);
158 if (entries == null) {
159 entries = List.nil();
160 }
161 cache.put(msym, entries.prepend(new Entry(speculativeTree, phase)));
162 }
163 }
165 /**
166 * Get the type that has been computed during a speculative attribution round
167 */
168 Type speculativeType(Symbol msym, MethodResolutionPhase phase) {
169 SpeculativeCache.Entry e = speculativeCache.get(msym, phase);
170 return e != null ? e.speculativeTree.type : Type.noType;
171 }
173 /**
174 * Check a deferred type against a potential target-type. Depending on
175 * the current attribution mode, a normal vs. speculative attribution
176 * round is performed on the underlying AST node. There can be only one
177 * speculative round for a given target method symbol; moreover, a normal
178 * attribution round must follow one or more speculative rounds.
179 */
180 Type check(ResultInfo resultInfo) {
181 return check(resultInfo, stuckVars(tree, env, resultInfo), basicCompleter);
182 }
184 Type check(ResultInfo resultInfo, List<Type> stuckVars, DeferredTypeCompleter deferredTypeCompleter) {
185 DeferredAttrContext deferredAttrContext =
186 resultInfo.checkContext.deferredAttrContext();
187 Assert.check(deferredAttrContext != emptyDeferredAttrContext);
188 if (stuckVars.nonEmpty()) {
189 deferredAttrContext.addDeferredAttrNode(this, resultInfo, stuckVars);
190 return Type.noType;
191 } else {
192 try {
193 return deferredTypeCompleter.complete(this, resultInfo, deferredAttrContext);
194 } finally {
195 mode = deferredAttrContext.mode;
196 }
197 }
198 }
199 }
201 /**
202 * A completer for deferred types. Defines an entry point for type-checking
203 * a deferred type.
204 */
205 interface DeferredTypeCompleter {
206 /**
207 * Entry point for type-checking a deferred type. Depending on the
208 * circumstances, type-checking could amount to full attribution
209 * or partial structural check (aka potential applicability).
210 */
211 Type complete(DeferredType dt, ResultInfo resultInfo, DeferredAttrContext deferredAttrContext);
212 }
214 /**
215 * A basic completer for deferred types. This completer type-checks a deferred type
216 * using attribution; depending on the attribution mode, this could be either standard
217 * or speculative attribution.
218 */
219 DeferredTypeCompleter basicCompleter = new DeferredTypeCompleter() {
220 public Type complete(DeferredType dt, ResultInfo resultInfo, DeferredAttrContext deferredAttrContext) {
221 switch (deferredAttrContext.mode) {
222 case SPECULATIVE:
223 Assert.check(dt.mode == null ||
224 (dt.mode == AttrMode.SPECULATIVE &&
225 dt.speculativeType(deferredAttrContext.msym, deferredAttrContext.phase).hasTag(NONE)));
226 JCTree speculativeTree = attribSpeculative(dt.tree, dt.env, resultInfo);
227 dt.speculativeCache.put(deferredAttrContext.msym, speculativeTree, deferredAttrContext.phase);
228 return speculativeTree.type;
229 case CHECK:
230 Assert.check(dt.mode == AttrMode.SPECULATIVE);
231 return attr.attribTree(dt.tree, dt.env, resultInfo);
232 }
233 Assert.error();
234 return null;
235 }
236 };
238 /**
239 * The 'mode' in which the deferred type is to be type-checked
240 */
241 public enum AttrMode {
242 /**
243 * A speculative type-checking round is used during overload resolution
244 * mainly to generate constraints on inference variables. Side-effects
245 * arising from type-checking the expression associated with the deferred
246 * type are reversed after the speculative round finishes. This means the
247 * expression tree will be left in a blank state.
248 */
249 SPECULATIVE,
250 /**
251 * This is the plain type-checking mode. Produces side-effects on the underlying AST node
252 */
253 CHECK;
254 }
256 /**
257 * Routine that performs speculative type-checking; the input AST node is
258 * cloned (to avoid side-effects cause by Attr) and compiler state is
259 * restored after type-checking. All diagnostics (but critical ones) are
260 * disabled during speculative type-checking.
261 */
262 JCTree attribSpeculative(JCTree tree, Env<AttrContext> env, ResultInfo resultInfo) {
263 JCTree newTree = new TreeCopier<Object>(make).copy(tree);
264 Env<AttrContext> speculativeEnv = env.dup(newTree, env.info.dup(env.info.scope.dupUnshared()));
265 speculativeEnv.info.scope.owner = env.info.scope.owner;
266 final JavaFileObject currentSource = log.currentSourceFile();
267 Log.DeferredDiagnosticHandler deferredDiagnosticHandler =
268 new Log.DeferredDiagnosticHandler(log, new Filter<JCDiagnostic>() {
269 public boolean accepts(JCDiagnostic t) {
270 return t.getDiagnosticSource().getFile().equals(currentSource);
271 }
272 });
273 try {
274 attr.attribTree(newTree, speculativeEnv, resultInfo);
275 unenterScanner.scan(newTree);
276 return newTree;
277 } catch (Abort ex) {
278 //if some very bad condition occurred during deferred attribution
279 //we should dump all errors before killing javac
280 deferredDiagnosticHandler.reportDeferredDiagnostics();
281 throw ex;
282 } finally {
283 unenterScanner.scan(newTree);
284 log.popDiagnosticHandler(deferredDiagnosticHandler);
285 }
286 }
287 //where
288 protected TreeScanner unenterScanner = new TreeScanner() {
289 @Override
290 public void visitClassDef(JCClassDecl tree) {
291 ClassSymbol csym = tree.sym;
292 //if something went wrong during method applicability check
293 //it is possible that nested expressions inside argument expression
294 //are left unchecked - in such cases there's nothing to clean up.
295 if (csym == null) return;
296 enter.typeEnvs.remove(csym);
297 chk.compiled.remove(csym.flatname);
298 syms.classes.remove(csym.flatname);
299 super.visitClassDef(tree);
300 }
301 };
303 /**
304 * A deferred context is created on each method check. A deferred context is
305 * used to keep track of information associated with the method check, such as
306 * the symbol of the method being checked, the overload resolution phase,
307 * the kind of attribution mode to be applied to deferred types and so forth.
308 * As deferred types are processed (by the method check routine) stuck AST nodes
309 * are added (as new deferred attribution nodes) to this context. The complete()
310 * routine makes sure that all pending nodes are properly processed, by
311 * progressively instantiating all inference variables on which one or more
312 * deferred attribution node is stuck.
313 */
314 class DeferredAttrContext {
316 /** attribution mode */
317 final AttrMode mode;
319 /** symbol of the method being checked */
320 final Symbol msym;
322 /** method resolution step */
323 final Resolve.MethodResolutionPhase phase;
325 /** inference context */
326 final InferenceContext inferenceContext;
328 /** list of deferred attribution nodes to be processed */
329 ArrayList<DeferredAttrNode> deferredAttrNodes = new ArrayList<DeferredAttrNode>();
331 DeferredAttrContext(AttrMode mode, Symbol msym, MethodResolutionPhase phase, InferenceContext inferenceContext) {
332 this.mode = mode;
333 this.msym = msym;
334 this.phase = phase;
335 this.inferenceContext = inferenceContext;
336 }
338 /**
339 * Adds a node to the list of deferred attribution nodes - used by Resolve.rawCheckArgumentsApplicable
340 * Nodes added this way act as 'roots' for the out-of-order method checking process.
341 */
342 void addDeferredAttrNode(final DeferredType dt, ResultInfo resultInfo, List<Type> stuckVars) {
343 deferredAttrNodes.add(new DeferredAttrNode(dt, resultInfo, stuckVars));
344 }
346 /**
347 * Incrementally process all nodes, by skipping 'stuck' nodes and attributing
348 * 'unstuck' ones. If at any point no progress can be made (no 'unstuck' nodes)
349 * some inference variable might get eagerly instantiated so that all nodes
350 * can be type-checked.
351 */
352 void complete() {
353 while (!deferredAttrNodes.isEmpty()) {
354 Set<Type> stuckVars = new LinkedHashSet<Type>();
355 boolean progress = false;
356 //scan a defensive copy of the node list - this is because a deferred
357 //attribution round can add new nodes to the list
358 for (DeferredAttrNode deferredAttrNode : List.from(deferredAttrNodes)) {
359 if (!deferredAttrNode.isStuck()) {
360 deferredAttrNode.process();
361 deferredAttrNodes.remove(deferredAttrNode);
362 progress = true;
363 } else {
364 stuckVars.addAll(deferredAttrNode.stuckVars);
365 }
366 }
367 if (!progress) {
368 //remove all variables that have already been instantiated
369 //from the list of stuck variables
370 inferenceContext.solveAny(inferenceContext.freeVarsIn(List.from(stuckVars)), types, infer);
371 inferenceContext.notifyChange(types);
372 }
373 }
374 }
376 /**
377 * Class representing a deferred attribution node. It keeps track of
378 * a deferred type, along with the expected target type information.
379 */
380 class DeferredAttrNode implements Infer.InferenceContext.FreeTypeListener {
382 /** underlying deferred type */
383 DeferredType dt;
385 /** underlying target type information */
386 ResultInfo resultInfo;
388 /** list of uninferred inference variables causing this node to be stuck */
389 List<Type> stuckVars;
391 DeferredAttrNode(DeferredType dt, ResultInfo resultInfo, List<Type> stuckVars) {
392 this.dt = dt;
393 this.resultInfo = resultInfo;
394 this.stuckVars = stuckVars;
395 if (!stuckVars.isEmpty()) {
396 resultInfo.checkContext.inferenceContext().addFreeTypeListener(stuckVars, this);
397 }
398 }
400 @Override
401 public void typesInferred(InferenceContext inferenceContext) {
402 stuckVars = List.nil();
403 resultInfo = resultInfo.dup(inferenceContext.asInstType(resultInfo.pt, types));
404 }
406 /**
407 * is this node stuck?
408 */
409 boolean isStuck() {
410 return stuckVars.nonEmpty();
411 }
413 /**
414 * Process a deferred attribution node.
415 * Invariant: a stuck node cannot be processed.
416 */
417 void process() {
418 if (isStuck()) {
419 throw new IllegalStateException("Cannot process a stuck deferred node");
420 }
421 dt.check(resultInfo);
422 }
423 }
424 }
426 /** an empty deferred attribution context - all methods throw exceptions */
427 final DeferredAttrContext emptyDeferredAttrContext =
428 new DeferredAttrContext(AttrMode.CHECK, null, MethodResolutionPhase.BOX, null) {
429 @Override
430 void addDeferredAttrNode(DeferredType dt, ResultInfo ri, List<Type> stuckVars) {
431 Assert.error("Empty deferred context!");
432 }
433 @Override
434 void complete() {
435 Assert.error("Empty deferred context!");
436 }
437 };
439 /**
440 * Map a list of types possibly containing one or more deferred types
441 * into a list of ordinary types. Each deferred type D is mapped into a type T,
442 * where T is computed by retrieving the type that has already been
443 * computed for D during a previous deferred attribution round of the given kind.
444 */
445 class DeferredTypeMap extends Type.Mapping {
447 DeferredAttrContext deferredAttrContext;
449 protected DeferredTypeMap(AttrMode mode, Symbol msym, MethodResolutionPhase phase) {
450 super(String.format("deferredTypeMap[%s]", mode));
451 this.deferredAttrContext = new DeferredAttrContext(mode, msym, phase, infer.emptyContext);
452 }
454 protected boolean validState(DeferredType dt) {
455 return dt.mode != null &&
456 deferredAttrContext.mode.ordinal() <= dt.mode.ordinal();
457 }
459 @Override
460 public Type apply(Type t) {
461 if (!t.hasTag(DEFERRED)) {
462 return t.map(this);
463 } else {
464 DeferredType dt = (DeferredType)t;
465 Assert.check(validState(dt));
466 return typeOf(dt);
467 }
468 }
470 protected Type typeOf(DeferredType dt) {
471 switch (deferredAttrContext.mode) {
472 case CHECK:
473 return dt.tree.type == null ? Type.noType : dt.tree.type;
474 case SPECULATIVE:
475 return dt.speculativeType(deferredAttrContext.msym, deferredAttrContext.phase);
476 }
477 Assert.error();
478 return null;
479 }
480 }
482 /**
483 * Specialized recovery deferred mapping.
484 * Each deferred type D is mapped into a type T, where T is computed either by
485 * (i) retrieving the type that has already been computed for D during a previous
486 * attribution round (as before), or (ii) by synthesizing a new type R for D
487 * (the latter step is useful in a recovery scenario).
488 */
489 public class RecoveryDeferredTypeMap extends DeferredTypeMap {
491 public RecoveryDeferredTypeMap(AttrMode mode, Symbol msym, MethodResolutionPhase phase) {
492 super(mode, msym, phase != null ? phase : MethodResolutionPhase.BOX);
493 }
495 @Override
496 protected Type typeOf(DeferredType dt) {
497 Type owntype = super.typeOf(dt);
498 return owntype == Type.noType ?
499 recover(dt) : owntype;
500 }
502 @Override
503 protected boolean validState(DeferredType dt) {
504 return true;
505 }
507 /**
508 * Synthesize a type for a deferred type that hasn't been previously
509 * reduced to an ordinary type. Functional deferred types and conditionals
510 * are mapped to themselves, in order to have a richer diagnostic
511 * representation. Remaining deferred types are attributed using
512 * a default expected type (j.l.Object).
513 */
514 private Type recover(DeferredType dt) {
515 dt.check(attr.new RecoveryInfo(deferredAttrContext));
516 return super.apply(dt);
517 }
518 }
520 /**
521 * Retrieves the list of inference variables that need to be inferred before
522 * an AST node can be type-checked
523 */
524 @SuppressWarnings("fallthrough")
525 List<Type> stuckVars(JCTree tree, Env<AttrContext> env, ResultInfo resultInfo) {
526 if (resultInfo.pt.hasTag(NONE) || resultInfo.pt.isErroneous()) {
527 return List.nil();
528 } else {
529 return stuckVarsInternal(tree, resultInfo.pt, resultInfo.checkContext.inferenceContext());
530 }
531 }
532 //where
533 private List<Type> stuckVarsInternal(JCTree tree, Type pt, Infer.InferenceContext inferenceContext) {
534 StuckChecker sc = new StuckChecker(pt, inferenceContext);
535 sc.scan(tree);
536 return List.from(sc.stuckVars);
537 }
539 /**
540 * A special tree scanner that would only visit portions of a given tree.
541 * The set of nodes visited by the scanner can be customized at construction-time.
542 */
543 abstract static class FilterScanner extends TreeScanner {
545 final Filter<JCTree> treeFilter;
547 FilterScanner(final Set<JCTree.Tag> validTags) {
548 this.treeFilter = new Filter<JCTree>() {
549 public boolean accepts(JCTree t) {
550 return validTags.contains(t.getTag());
551 }
552 };
553 }
555 @Override
556 public void scan(JCTree tree) {
557 if (tree != null) {
558 if (treeFilter.accepts(tree)) {
559 super.scan(tree);
560 } else {
561 skip(tree);
562 }
563 }
564 }
566 /**
567 * handler that is executed when a node has been discarded
568 */
569 abstract void skip(JCTree tree);
570 }
572 /**
573 * A tree scanner suitable for visiting the target-type dependent nodes of
574 * a given argument expression.
575 */
576 static class PolyScanner extends FilterScanner {
578 PolyScanner() {
579 super(EnumSet.of(CONDEXPR, PARENS, LAMBDA, REFERENCE));
580 }
582 @Override
583 void skip(JCTree tree) {
584 //do nothing
585 }
586 }
588 /**
589 * A tree scanner suitable for visiting the target-type dependent nodes nested
590 * within a lambda expression body.
591 */
592 static class LambdaReturnScanner extends FilterScanner {
594 LambdaReturnScanner() {
595 super(EnumSet.of(BLOCK, CASE, CATCH, DOLOOP, FOREACHLOOP,
596 FORLOOP, RETURN, SYNCHRONIZED, SWITCH, TRY, WHILELOOP));
597 }
599 @Override
600 void skip(JCTree tree) {
601 //do nothing
602 }
603 }
605 /**
606 * This visitor is used to check that structural expressions conform
607 * to their target - this step is required as inference could end up
608 * inferring types that make some of the nested expressions incompatible
609 * with their corresponding instantiated target
610 */
611 class StuckChecker extends PolyScanner {
613 Type pt;
614 Infer.InferenceContext inferenceContext;
615 Set<Type> stuckVars = new LinkedHashSet<Type>();
617 StuckChecker(Type pt, Infer.InferenceContext inferenceContext) {
618 this.pt = pt;
619 this.inferenceContext = inferenceContext;
620 }
622 @Override
623 public void visitLambda(JCLambda tree) {
624 if (inferenceContext.inferenceVars().contains(pt)) {
625 stuckVars.add(pt);
626 }
627 if (!types.isFunctionalInterface(pt.tsym)) {
628 return;
629 }
630 Type descType = types.findDescriptorType(pt);
631 List<Type> freeArgVars = inferenceContext.freeVarsIn(descType.getParameterTypes());
632 if (!TreeInfo.isExplicitLambda(tree) &&
633 freeArgVars.nonEmpty()) {
634 stuckVars.addAll(freeArgVars);
635 }
636 scanLambdaBody(tree, descType.getReturnType());
637 }
639 @Override
640 public void visitReference(JCMemberReference tree) {
641 scan(tree.expr);
642 if (inferenceContext.inferenceVars().contains(pt)) {
643 stuckVars.add(pt);
644 return;
645 }
646 if (!types.isFunctionalInterface(pt.tsym)) {
647 return;
648 }
650 Type descType = types.findDescriptorType(pt);
651 List<Type> freeArgVars = inferenceContext.freeVarsIn(descType.getParameterTypes());
652 stuckVars.addAll(freeArgVars);
653 }
655 void scanLambdaBody(JCLambda lambda, final Type pt) {
656 if (lambda.getBodyKind() == JCTree.JCLambda.BodyKind.EXPRESSION) {
657 stuckVars.addAll(stuckVarsInternal(lambda.body, pt, inferenceContext));
658 } else {
659 LambdaReturnScanner lambdaScanner = new LambdaReturnScanner() {
660 @Override
661 public void visitReturn(JCReturn tree) {
662 if (tree.expr != null) {
663 stuckVars.addAll(stuckVarsInternal(tree.expr, pt, inferenceContext));
664 }
665 }
666 };
667 lambdaScanner.scan(lambda.body);
668 }
669 }
670 }
671 }