InferenceContext.java revision 3405:d671601a2f2e
1/* 2 * Copyright (c) 2015, 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 26package com.sun.tools.javac.comp; 27 28import java.util.Collections; 29import java.util.EnumSet; 30import java.util.HashMap; 31import java.util.HashSet; 32import java.util.LinkedHashMap; 33import java.util.Map; 34import java.util.Set; 35 36import com.sun.tools.javac.code.Type; 37import com.sun.tools.javac.code.Type.ArrayType; 38import com.sun.tools.javac.code.Type.ClassType; 39import com.sun.tools.javac.code.Type.TypeVar; 40import com.sun.tools.javac.code.Type.UndetVar; 41import com.sun.tools.javac.code.Type.UndetVar.InferenceBound; 42import com.sun.tools.javac.code.Type.WildcardType; 43import com.sun.tools.javac.code.TypeTag; 44import com.sun.tools.javac.code.Types; 45import com.sun.tools.javac.comp.Infer.FreeTypeListener; 46import com.sun.tools.javac.comp.Infer.GraphSolver; 47import com.sun.tools.javac.comp.Infer.GraphStrategy; 48import com.sun.tools.javac.comp.Infer.InferenceException; 49import com.sun.tools.javac.comp.Infer.InferenceStep; 50import com.sun.tools.javac.tree.JCTree; 51import com.sun.tools.javac.util.Assert; 52import com.sun.tools.javac.util.Filter; 53import com.sun.tools.javac.util.List; 54import com.sun.tools.javac.util.ListBuffer; 55import com.sun.tools.javac.util.Warner; 56 57/** 58 * An inference context keeps track of the set of variables that are free 59 * in the current context. It provides utility methods for opening/closing 60 * types to their corresponding free/closed forms. It also provide hooks for 61 * attaching deferred post-inference action (see PendingCheck). Finally, 62 * it can be used as an entry point for performing upper/lower bound inference 63 * (see InferenceKind). 64 * 65 * <p><b>This is NOT part of any supported API. 66 * If you write code that depends on this, you do so at your own risk. 67 * This code and its internal interfaces are subject to change or 68 * deletion without notice.</b> 69 */ 70class InferenceContext { 71 72 /** list of inference vars as undet vars */ 73 List<Type> undetvars; 74 75 Type update(Type t) { 76 return t; 77 } 78 79 /** list of inference vars in this context */ 80 List<Type> inferencevars; 81 82 Map<FreeTypeListener, List<Type>> freeTypeListeners = new LinkedHashMap<>(); 83 84 Types types; 85 Infer infer; 86 87 public InferenceContext(Infer infer, List<Type> inferencevars) { 88 this(infer, inferencevars, inferencevars.map(infer.fromTypeVarFun)); 89 } 90 91 public InferenceContext(Infer infer, List<Type> inferencevars, List<Type> undetvars) { 92 this.inferencevars = inferencevars; 93 this.undetvars = undetvars; 94 this.infer = infer; 95 this.types = infer.types; 96 } 97 98 /** 99 * add a new inference var to this inference context 100 */ 101 void addVar(TypeVar t) { 102 this.undetvars = this.undetvars.prepend(infer.fromTypeVarFun.apply(t)); 103 this.inferencevars = this.inferencevars.prepend(t); 104 } 105 106 /** 107 * returns the list of free variables (as type-variables) in this 108 * inference context 109 */ 110 List<Type> inferenceVars() { 111 return inferencevars; 112 } 113 114 /** 115 * returns the list of uninstantiated variables (as type-variables) in this 116 * inference context 117 */ 118 List<Type> restvars() { 119 return filterVars(new Filter<UndetVar>() { 120 public boolean accepts(UndetVar uv) { 121 return uv.getInst() == null; 122 } 123 }); 124 } 125 126 /** 127 * returns the list of instantiated variables (as type-variables) in this 128 * inference context 129 */ 130 List<Type> instvars() { 131 return filterVars(new Filter<UndetVar>() { 132 public boolean accepts(UndetVar uv) { 133 return uv.getInst() != null; 134 } 135 }); 136 } 137 138 /** 139 * Get list of bounded inference variables (where bound is other than 140 * declared bounds). 141 */ 142 final List<Type> boundedVars() { 143 return filterVars(new Filter<UndetVar>() { 144 public boolean accepts(UndetVar uv) { 145 return uv.getBounds(InferenceBound.UPPER) 146 .diff(uv.getDeclaredBounds()) 147 .appendList(uv.getBounds(InferenceBound.EQ, InferenceBound.LOWER)).nonEmpty(); 148 } 149 }); 150 } 151 152 /* Returns the corresponding inference variables. 153 */ 154 private List<Type> filterVars(Filter<UndetVar> fu) { 155 ListBuffer<Type> res = new ListBuffer<>(); 156 for (Type t : undetvars) { 157 UndetVar uv = (UndetVar)t; 158 if (fu.accepts(uv)) { 159 res.append(uv.qtype); 160 } 161 } 162 return res.toList(); 163 } 164 165 /** 166 * is this type free? 167 */ 168 final boolean free(Type t) { 169 return t.containsAny(inferencevars); 170 } 171 172 final boolean free(List<Type> ts) { 173 for (Type t : ts) { 174 if (free(t)) return true; 175 } 176 return false; 177 } 178 179 /** 180 * Returns a list of free variables in a given type 181 */ 182 final List<Type> freeVarsIn(Type t) { 183 ListBuffer<Type> buf = new ListBuffer<>(); 184 for (Type iv : inferenceVars()) { 185 if (t.contains(iv)) { 186 buf.add(iv); 187 } 188 } 189 return buf.toList(); 190 } 191 192 final List<Type> freeVarsIn(List<Type> ts) { 193 ListBuffer<Type> buf = new ListBuffer<>(); 194 for (Type t : ts) { 195 buf.appendList(freeVarsIn(t)); 196 } 197 ListBuffer<Type> buf2 = new ListBuffer<>(); 198 for (Type t : buf) { 199 if (!buf2.contains(t)) { 200 buf2.add(t); 201 } 202 } 203 return buf2.toList(); 204 } 205 206 /** 207 * Replace all free variables in a given type with corresponding 208 * undet vars (used ahead of subtyping/compatibility checks to allow propagation 209 * of inference constraints). 210 */ 211 final Type asUndetVar(Type t) { 212 return types.subst(t, inferencevars, undetvars); 213 } 214 215 final List<Type> asUndetVars(List<Type> ts) { 216 ListBuffer<Type> buf = new ListBuffer<>(); 217 for (Type t : ts) { 218 buf.append(asUndetVar(t)); 219 } 220 return buf.toList(); 221 } 222 223 List<Type> instTypes() { 224 ListBuffer<Type> buf = new ListBuffer<>(); 225 for (Type t : undetvars) { 226 UndetVar uv = (UndetVar)t; 227 buf.append(uv.getInst() != null ? uv.getInst() : uv.qtype); 228 } 229 return buf.toList(); 230 } 231 232 /** 233 * Replace all free variables in a given type with corresponding 234 * instantiated types - if one or more free variable has not been 235 * fully instantiated, it will still be available in the resulting type. 236 */ 237 Type asInstType(Type t) { 238 return types.subst(t, inferencevars, instTypes()); 239 } 240 241 List<Type> asInstTypes(List<Type> ts) { 242 ListBuffer<Type> buf = new ListBuffer<>(); 243 for (Type t : ts) { 244 buf.append(asInstType(t)); 245 } 246 return buf.toList(); 247 } 248 249 /** 250 * Add custom hook for performing post-inference action 251 */ 252 void addFreeTypeListener(List<Type> types, FreeTypeListener ftl) { 253 freeTypeListeners.put(ftl, freeVarsIn(types)); 254 } 255 256 /** 257 * Mark the inference context as complete and trigger evaluation 258 * of all deferred checks. 259 */ 260 void notifyChange() { 261 notifyChange(inferencevars.diff(restvars())); 262 } 263 264 void notifyChange(List<Type> inferredVars) { 265 InferenceException thrownEx = null; 266 for (Map.Entry<FreeTypeListener, List<Type>> entry : 267 new LinkedHashMap<>(freeTypeListeners).entrySet()) { 268 if (!Type.containsAny(entry.getValue(), inferencevars.diff(inferredVars))) { 269 try { 270 entry.getKey().typesInferred(this); 271 freeTypeListeners.remove(entry.getKey()); 272 } catch (InferenceException ex) { 273 if (thrownEx == null) { 274 thrownEx = ex; 275 } 276 } 277 } 278 } 279 //inference exception multiplexing - present any inference exception 280 //thrown when processing listeners as a single one 281 if (thrownEx != null) { 282 throw thrownEx; 283 } 284 } 285 286 /** 287 * Save the state of this inference context 288 */ 289 List<Type> save() { 290 ListBuffer<Type> buf = new ListBuffer<>(); 291 for (Type t : undetvars) { 292 buf.add(((UndetVar)t).dup(infer.types)); 293 } 294 return buf.toList(); 295 } 296 297 /** Restore the state of this inference context to the previous known checkpoint. 298 * Consider that the number of saved undetermined variables can be different to the current 299 * amount. This is because new captured variables could have been added. 300 */ 301 void rollback(List<Type> saved_undet) { 302 Assert.check(saved_undet != null); 303 //restore bounds (note: we need to preserve the old instances) 304 ListBuffer<Type> newUndetVars = new ListBuffer<>(); 305 ListBuffer<Type> newInferenceVars = new ListBuffer<>(); 306 while (saved_undet.nonEmpty() && undetvars.nonEmpty()) { 307 UndetVar uv = (UndetVar)undetvars.head; 308 UndetVar uv_saved = (UndetVar)saved_undet.head; 309 if (uv.qtype == uv_saved.qtype) { 310 uv_saved.dupTo(uv, types); 311 undetvars = undetvars.tail; 312 saved_undet = saved_undet.tail; 313 newUndetVars.add(uv); 314 newInferenceVars.add(uv.qtype); 315 } else { 316 undetvars = undetvars.tail; 317 } 318 } 319 undetvars = newUndetVars.toList(); 320 inferencevars = newInferenceVars.toList(); 321 } 322 323 /** 324 * Copy variable in this inference context to the given context 325 */ 326 void dupTo(final InferenceContext that) { 327 dupTo(that, false); 328 } 329 330 void dupTo(final InferenceContext that, boolean clone) { 331 that.inferencevars = that.inferencevars.appendList(inferencevars.diff(that.inferencevars)); 332 List<Type> undetsToPropagate = clone ? save() : undetvars; 333 that.undetvars = that.undetvars.appendList(undetsToPropagate.diff(that.undetvars)); //propagate cloned undet!! 334 //set up listeners to notify original inference contexts as 335 //propagated vars are inferred in new context 336 for (Type t : inferencevars) { 337 that.freeTypeListeners.put(new FreeTypeListener() { 338 public void typesInferred(InferenceContext inferenceContext) { 339 InferenceContext.this.notifyChange(); 340 } 341 }, List.of(t)); 342 } 343 } 344 345 InferenceContext min(List<Type> roots, boolean shouldSolve, Warner warn) { 346 ReachabilityVisitor rv = new ReachabilityVisitor(); 347 rv.scan(roots); 348 if (rv.min.size() == inferencevars.length()) { 349 return this; 350 } 351 352 List<Type> minVars = List.from(rv.min); 353 List<Type> redundantVars = inferencevars.diff(minVars); 354 355 //compute new undet variables (bounds associated to redundant variables are dropped) 356 ListBuffer<Type> minUndetVars = new ListBuffer<>(); 357 for (Type minVar : minVars) { 358 UndetVar uv = (UndetVar)asUndetVar(minVar); 359 Assert.check(uv.incorporationActions.size() == 0); 360 UndetVar uv2 = new UndetVar((TypeVar)minVar, infer.incorporationEngine(), types); 361 for (InferenceBound ib : InferenceBound.values()) { 362 List<Type> newBounds = uv.getBounds(ib).stream() 363 .filter(b -> !redundantVars.contains(b)) 364 .collect(List.collector()); 365 uv2.setBounds(ib, newBounds); 366 } 367 minUndetVars.add(uv2); 368 } 369 370 //compute new minimal inference context 371 InferenceContext minContext = new InferenceContext(infer, minVars, minUndetVars.toList()); 372 for (Type t : minContext.inferencevars) { 373 //add listener that forwards notifications to original context 374 minContext.addFreeTypeListener(List.of(t), (inferenceContext) -> { 375 List<Type> depVars = List.from(rv.minMap.get(t)); 376 solve(depVars, warn); 377 notifyChange(); 378 }); 379 } 380 if (shouldSolve) { 381 //solve definitively unreachable variables 382 List<Type> unreachableVars = redundantVars.diff(List.from(rv.equiv)); 383 solve(unreachableVars, warn); 384 } 385 return minContext; 386 } 387 388 class ReachabilityVisitor extends Types.UnaryVisitor<Void> { 389 390 Set<Type> equiv = new HashSet<>(); 391 Set<Type> min = new HashSet<>(); 392 Map<Type, Set<Type>> minMap = new HashMap<>(); 393 394 void scan(List<Type> roots) { 395 roots.stream().forEach(this::visit); 396 } 397 398 @Override 399 public Void visitType(Type t, Void _unused) { 400 return null; 401 } 402 403 @Override 404 public Void visitUndetVar(UndetVar t, Void _unused) { 405 if (min.add(t.qtype)) { 406 Set<Type> deps = minMap.getOrDefault(t.qtype, new HashSet<>(Collections.singleton(t.qtype))); 407 for (InferenceBound boundKind : InferenceBound.values()) { 408 for (Type b : t.getBounds(boundKind)) { 409 Type undet = asUndetVar(b); 410 if (!undet.hasTag(TypeTag.UNDETVAR)) { 411 visit(undet); 412 } else if (isEquiv(t, b, boundKind)) { 413 deps.add(b); 414 equiv.add(b); 415 } else { 416 visit(undet); 417 } 418 } 419 } 420 minMap.put(t.qtype, deps); 421 } 422 return null; 423 } 424 425 @Override 426 public Void visitWildcardType(WildcardType t, Void _unused) { 427 return visit(t.type); 428 } 429 430 @Override 431 public Void visitTypeVar(TypeVar t, Void aVoid) { 432 Type undet = asUndetVar(t); 433 if (undet.hasTag(TypeTag.UNDETVAR)) { 434 visitUndetVar((UndetVar)undet, null); 435 } 436 return null; 437 } 438 439 @Override 440 public Void visitArrayType(ArrayType t, Void _unused) { 441 return visit(t.elemtype); 442 } 443 444 @Override 445 public Void visitClassType(ClassType t, Void _unused) { 446 visit(t.getEnclosingType()); 447 for (Type targ : t.getTypeArguments()) { 448 visit(targ); 449 } 450 return null; 451 } 452 453 boolean isEquiv(UndetVar from, Type t, InferenceBound boundKind) { 454 UndetVar uv = (UndetVar)asUndetVar(t); 455 for (InferenceBound ib : InferenceBound.values()) { 456 List<Type> b1 = from.getBounds(ib); 457 if (ib == boundKind) { 458 b1 = b1.diff(List.of(t)); 459 } 460 List<Type> b2 = uv.getBounds(ib); 461 if (ib == boundKind.complement()) { 462 b2 = b2.diff(List.of(from.qtype)); 463 } 464 if (!b1.containsAll(b2) || !b2.containsAll(b1)) { 465 return false; 466 } 467 } 468 return true; 469 } 470 } 471 472 /** 473 * Solve with given graph strategy. 474 */ 475 private void solve(GraphStrategy ss, Warner warn) { 476 GraphSolver s = infer.new GraphSolver(this, warn); 477 s.solve(ss); 478 } 479 480 /** 481 * Solve all variables in this context. 482 */ 483 public void solve(Warner warn) { 484 solve(infer.new LeafSolver() { 485 public boolean done() { 486 return restvars().isEmpty(); 487 } 488 }, warn); 489 } 490 491 /** 492 * Solve all variables in the given list. 493 */ 494 public void solve(final List<Type> vars, Warner warn) { 495 solve(infer.new BestLeafSolver(vars) { 496 public boolean done() { 497 return !free(asInstTypes(vars)); 498 } 499 }, warn); 500 } 501 502 /** 503 * Solve at least one variable in given list. 504 */ 505 public void solveAny(List<Type> varsToSolve, Warner warn) { 506 solve(infer.new BestLeafSolver(varsToSolve.intersect(restvars())) { 507 public boolean done() { 508 return instvars().intersect(varsToSolve).nonEmpty(); 509 } 510 }, warn); 511 } 512 513 /** 514 * Apply a set of inference steps 515 */ 516 private List<Type> solveBasic(EnumSet<InferenceStep> steps) { 517 return solveBasic(inferencevars, steps); 518 } 519 520 List<Type> solveBasic(List<Type> varsToSolve, EnumSet<InferenceStep> steps) { 521 ListBuffer<Type> solvedVars = new ListBuffer<>(); 522 for (Type t : varsToSolve.intersect(restvars())) { 523 UndetVar uv = (UndetVar)asUndetVar(t); 524 for (InferenceStep step : steps) { 525 if (step.accepts(uv, this)) { 526 uv.setInst(step.solve(uv, this)); 527 solvedVars.add(uv.qtype); 528 break; 529 } 530 } 531 } 532 return solvedVars.toList(); 533 } 534 535 /** 536 * Instantiate inference variables in legacy mode (JLS 15.12.2.7, 15.12.2.8). 537 * During overload resolution, instantiation is done by doing a partial 538 * inference process using eq/lower bound instantiation. During check, 539 * we also instantiate any remaining vars by repeatedly using eq/upper 540 * instantiation, until all variables are solved. 541 */ 542 public void solveLegacy(boolean partial, Warner warn, EnumSet<InferenceStep> steps) { 543 while (true) { 544 List<Type> solvedVars = solveBasic(steps); 545 if (restvars().isEmpty() || partial) { 546 //all variables have been instantiated - exit 547 break; 548 } else if (solvedVars.isEmpty()) { 549 //some variables could not be instantiated because of cycles in 550 //upper bounds - provide a (possibly recursive) default instantiation 551 infer.instantiateAsUninferredVars(restvars(), this); 552 break; 553 } else { 554 //some variables have been instantiated - replace newly instantiated 555 //variables in remaining upper bounds and continue 556 for (Type t : undetvars) { 557 UndetVar uv = (UndetVar)t; 558 uv.substBounds(solvedVars, asInstTypes(solvedVars), types); 559 } 560 } 561 } 562 infer.doIncorporation(this, warn); 563 } 564 565 @Override 566 public String toString() { 567 return "Inference vars: " + inferencevars + '\n' + 568 "Undet vars: " + undetvars; 569 } 570 571 /* Method Types.capture() generates a new type every time it's applied 572 * to a wildcard parameterized type. This is intended functionality but 573 * there are some cases when what you need is not to generate a new 574 * captured type but to check that a previously generated captured type 575 * is correct. There are cases when caching a captured type for later 576 * reuse is sound. In general two captures from the same AST are equal. 577 * This is why the tree is used as the key of the map below. This map 578 * stores a Type per AST. 579 */ 580 Map<JCTree, Type> captureTypeCache = new HashMap<>(); 581 582 Type cachedCapture(JCTree tree, Type t, boolean readOnly) { 583 Type captured = captureTypeCache.get(tree); 584 if (captured != null) { 585 return captured; 586 } 587 588 Type result = types.capture(t); 589 if (result != t && !readOnly) { // then t is a wildcard parameterized type 590 captureTypeCache.put(tree, result); 591 } 592 return result; 593 } 594} 595