KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > dev > jjs > impl > TypeTightener


1 /*
2  * Copyright 2007 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */

16 package com.google.gwt.dev.jjs.impl;
17
18 import com.google.gwt.dev.jjs.ast.Context;
19 import com.google.gwt.dev.jjs.ast.JArrayRef;
20 import com.google.gwt.dev.jjs.ast.JBinaryOperation;
21 import com.google.gwt.dev.jjs.ast.JBinaryOperator;
22 import com.google.gwt.dev.jjs.ast.JCastOperation;
23 import com.google.gwt.dev.jjs.ast.JClassType;
24 import com.google.gwt.dev.jjs.ast.JExpression;
25 import com.google.gwt.dev.jjs.ast.JField;
26 import com.google.gwt.dev.jjs.ast.JFieldRef;
27 import com.google.gwt.dev.jjs.ast.JInstanceOf;
28 import com.google.gwt.dev.jjs.ast.JInterfaceType;
29 import com.google.gwt.dev.jjs.ast.JLocal;
30 import com.google.gwt.dev.jjs.ast.JLocalDeclarationStatement;
31 import com.google.gwt.dev.jjs.ast.JLocalRef;
32 import com.google.gwt.dev.jjs.ast.JMethod;
33 import com.google.gwt.dev.jjs.ast.JMethodCall;
34 import com.google.gwt.dev.jjs.ast.JModVisitor;
35 import com.google.gwt.dev.jjs.ast.JNullLiteral;
36 import com.google.gwt.dev.jjs.ast.JNullType;
37 import com.google.gwt.dev.jjs.ast.JParameter;
38 import com.google.gwt.dev.jjs.ast.JParameterRef;
39 import com.google.gwt.dev.jjs.ast.JProgram;
40 import com.google.gwt.dev.jjs.ast.JReferenceType;
41 import com.google.gwt.dev.jjs.ast.JReturnStatement;
42 import com.google.gwt.dev.jjs.ast.JTryStatement;
43 import com.google.gwt.dev.jjs.ast.JType;
44 import com.google.gwt.dev.jjs.ast.JTypeOracle;
45 import com.google.gwt.dev.jjs.ast.JVariable;
46 import com.google.gwt.dev.jjs.ast.JVariableRef;
47 import com.google.gwt.dev.jjs.ast.JVisitor;
48 import com.google.gwt.dev.jjs.ast.js.JsniFieldRef;
49 import com.google.gwt.dev.jjs.ast.js.JsniMethod;
50 import com.google.gwt.dev.jjs.ast.js.JsniMethodRef;
51
52 import java.util.ArrayList JavaDoc;
53 import java.util.HashSet JavaDoc;
54 import java.util.IdentityHashMap JavaDoc;
55 import java.util.Iterator JavaDoc;
56 import java.util.List JavaDoc;
57 import java.util.Map JavaDoc;
58 import java.util.Set JavaDoc;
59
60 /**
61  * The purpose of this pass is to record "type flow" information and then use
62  * the information to infer places where "tighter" (that is, more specific)
63  * types can be inferred for locals, fields, parameters, and method return
64  * types. We also optimize dynamic casts and instanceof operations.
65  *
66  * Type flow occurs automatically in most JExpressions. But locals, fields,
67  * parameters, and method return types serve as "way points" where type
68  * information is fixed based on the declared type. Type tightening can be done
69  * by analyzing the types "flowing" into each way point, and then updating the
70  * declared type of the way point to be a more specific type than it had before.
71  *
72  * Oddly, it's quite possible to tighten a variable to the Null type, which
73  * means either the variable was never assigned, or it was only ever assigned
74  * null. This is great for two reasons:
75  *
76  * 1) Once a variable has been tightened to null, it will no longer impact the
77  * variables that depend on it.
78  *
79  * 2) It creates some very interesting opportunities to optimize later, since we
80  * know statically that the value of the variable is always null.
81  *
82  * Open issue: we don't handle recursion where a method passes (some of) its own
83  * args to itself or returns its own call result. With our naive analysis, we
84  * can't figure out that tightening might occur.
85  *
86  * Type flow is not supported for primitive types, only reference types.
87  *
88  * TODO(later): handle recursion, self-assignment, arrays
89  */

90 public class TypeTightener {
91
92   /**
93    * Replaces dangling null references with dummy calls.
94    */

95   public class FixDanglingRefsVisitor extends JModVisitor {
96
97     // @Override
98
public void endVisit(JArrayRef x, Context ctx) {
99       JExpression instance = x.getInstance();
100       if (instance.getType() == typeNull) {
101         if (!instance.hasSideEffects()) {
102           instance = program.getLiteralNull();
103         }
104         JArrayRef arrayRef = new JArrayRef(program, x.getSourceInfo(),
105             instance, program.getLiteralInt(0));
106         ctx.replaceMe(arrayRef);
107       }
108     }
109
110     // @Override
111
public void endVisit(JFieldRef x, Context ctx) {
112       JExpression instance = x.getInstance();
113       boolean isStatic = x.getField().isStatic();
114       if (isStatic && instance != null) {
115         // this doesn't really belong here, but while we're here let's remove
116
// non-side-effect qualifiers to statics
117
if (!instance.hasSideEffects()) {
118           JFieldRef fieldRef = new JFieldRef(program, x.getSourceInfo(), null,
119               x.getField(), x.getEnclosingType());
120           ctx.replaceMe(fieldRef);
121         }
122       } else if (!isStatic && instance.getType() == typeNull) {
123         if (!instance.hasSideEffects()) {
124           instance = program.getLiteralNull();
125         }
126         JFieldRef fieldRef = new JFieldRef(program, x.getSourceInfo(),
127             instance, program.getNullField(), null);
128         ctx.replaceMe(fieldRef);
129       }
130     }
131
132     // @Override
133
public void endVisit(JMethodCall x, Context ctx) {
134       JExpression instance = x.getInstance();
135       JMethod method = x.getTarget();
136       boolean isStatic = method.isStatic();
137       boolean isStaticImpl = program.isStaticImpl(method);
138       if (isStatic && !isStaticImpl && instance != null) {
139         // this doesn't really belong here, but while we're here let's remove
140
// non-side-effect qualifiers to statics
141
if (!instance.hasSideEffects()) {
142           JMethodCall newCall = new JMethodCall(program, x.getSourceInfo(),
143               null, x.getTarget());
144           newCall.getArgs().addAll(x.getArgs());
145           ctx.replaceMe(newCall);
146         }
147       } else if (!isStatic && instance.getType() == typeNull) {
148         // bind null instance calls to the null method
149
if (!instance.hasSideEffects()) {
150           instance = program.getLiteralNull();
151         }
152         JMethodCall newCall = new JMethodCall(program, x.getSourceInfo(),
153             instance, program.getNullMethod());
154         ctx.replaceMe(newCall);
155       } else if (isStaticImpl && x.getArgs().size() > 0
156           && ((JExpression) x.getArgs().get(0)).getType() == typeNull) {
157         // bind null instance calls to the null method for static impls
158
instance = (JExpression) x.getArgs().get(0);
159         if (!instance.hasSideEffects()) {
160           instance = program.getLiteralNull();
161         }
162         JMethodCall newCall = new JMethodCall(program, x.getSourceInfo(),
163             instance, program.getNullMethod());
164         ctx.replaceMe(newCall);
165       }
166     }
167   }
168
169   /**
170    * Record "type flow" information. Variables receive type flow via assignment.
171    * As a special case, Parameters also receive type flow based on the types of
172    * arguments used when calling the containing method (think of this as a kind
173    * of assignment). Method return types receive type flow from their contained
174    * return statements, plus the return type of any methods that
175    * override/implement them.
176    *
177    * Note that we only have to run this pass ONCE to record the relationships,
178    * because type tightening never changes any relationships, only the types of
179    * the things related. In my original implementation, I had naively mapped
180    * nodes onto sets of JReferenceType directly, which meant I had to rerun this
181    * visitor each time.
182    */

183   public class RecordVisitor extends JVisitor {
184
185     private JMethod currentMethod;
186
187     // @Override
188
public void endVisit(JBinaryOperation x, Context ctx) {
189       if (x.isAssignment() && (x.getType() instanceof JReferenceType)) {
190         JExpression lhs = x.getLhs();
191         if (lhs instanceof JVariableRef) {
192           addAssignment(((JVariableRef) lhs).getTarget(), x.getRhs());
193         }
194       }
195     }
196
197     // @Override
198
public void endVisit(JClassType x, Context ctx) {
199       for (JClassType cur = x; cur != null; cur = cur.extnds) {
200         addImplementor(cur, x);
201         for (Iterator JavaDoc it = cur.implments.iterator(); it.hasNext();) {
202           JInterfaceType implment = (JInterfaceType) it.next();
203           addImplementor(implment, x);
204         }
205       }
206     }
207
208     // @Override
209
public void endVisit(JField x, Context ctx) {
210       if (x.constInitializer != null) {
211         addAssignment(x, x.constInitializer);
212       }
213       currentMethod = null;
214     }
215
216     // @Override
217
public void endVisit(JLocalDeclarationStatement x, Context ctx) {
218       JExpression initializer = x.getInitializer();
219       if (initializer != null) {
220         addAssignment(x.getLocalRef().getTarget(), initializer);
221       }
222     }
223
224     // @Override
225
public void endVisit(JMethod x, Context ctx) {
226       for (int i = 0; i < x.overrides.size(); ++i) {
227         JMethod method = (JMethod) x.overrides.get(i);
228         addOverrider(method, x);
229       }
230       JMethod[] allVirtualOverrides = program.typeOracle.getAllVirtualOverrides(x);
231       for (int i = 0; i < allVirtualOverrides.length; ++i) {
232         JMethod method = allVirtualOverrides[i];
233         addOverrider(method, x);
234       }
235       currentMethod = null;
236     }
237
238     // @Override
239
public void endVisit(JMethodCall x, Context ctx) {
240       // All of the params in the target method are considered to be assigned by
241
// the arguments from the caller
242
Iterator JavaDoc/* <JExpression> */argIt = x.getArgs().iterator();
243       ArrayList JavaDoc params = x.getTarget().params;
244       for (int i = 0; i < params.size(); ++i) {
245         JParameter param = (JParameter) params.get(i);
246         JExpression arg = (JExpression) argIt.next();
247         if (param.getType() instanceof JReferenceType) {
248           addAssignment(param, arg);
249         }
250       }
251     }
252
253     // @Override
254
public void endVisit(JReturnStatement x, Context ctx) {
255       if (currentMethod.getType() instanceof JReferenceType) {
256         addReturn(currentMethod, x.getExpr());
257       }
258     }
259
260     // @Override
261
public void endVisit(JsniFieldRef x, Context ctx) {
262       // If this happens in JSNI, we can't make any type-tightening assumptions
263
// Fake an assignment-to-self to prevent tightening
264
addAssignment(x.getTarget(), x);
265     }
266
267     // @Override
268
public void endVisit(JsniMethod x, Context ctx) {
269       endVisit((JMethod) x, ctx);
270     }
271
272     // @Override
273
public void endVisit(JsniMethodRef x, Context ctx) {
274       // If this happens in JSNI, we can't make any type-tightening assumptions
275
// Fake an assignment-to-self on all args to prevent tightening
276

277       JMethod method = x.getTarget();
278
279       for (int i = 0; i < method.params.size(); ++i) {
280         JParameter param = (JParameter) method.params.get(i);
281         addAssignment(param, new JParameterRef(program, null, param));
282       }
283     }
284
285     // @Override
286
public void endVisit(JTryStatement x, Context ctx) {
287       // Never tighten args to catch blocks
288
// Fake an assignment-to-self to prevent tightening
289
for (int i = 0; i < x.getCatchArgs().size(); ++i) {
290         JLocalRef arg = (JLocalRef) x.getCatchArgs().get(i);
291         addAssignment(arg.getTarget(), arg);
292       }
293     }
294
295     /**
296      * Merge param call args across overriders/implementors. We can't tighten a
297      * param type in an overriding method if the declaring method is looser.
298      */

299     // @Override
300
public boolean visit(JMethod x, Context ctx) {
301       currentMethod = x;
302
303       List JavaDoc/* <JMethod> */overrides = x.overrides;
304       JMethod[] virtualOverrides = program.typeOracle.getAllVirtualOverrides(x);
305
306       /*
307        * Special case: also add upRefs from a staticImpl's params to the params
308        * of the instance method it is implementing. Most of the time, this would
309        * happen naturally since the instance method delegates to the static.
310        * However, in cases where the static has been inlined into the instance
311        * method, future optimization could tighten an instance call into a
312        * static call at some other call site, and fail to inline. If we allowed
313        * a staticImpl param to be tighter than its instance param, badness would
314        * ensue.
315        */

316       JMethod staticImplFor = program.staticImplFor(x);
317       // Unless the instance method has already been pruned, of course.
318
if (staticImplFor != null
319           && !staticImplFor.getEnclosingType().methods.contains(staticImplFor)) {
320         staticImplFor = null;
321       }
322
323       if (overrides.isEmpty() && virtualOverrides.length == 0
324           && staticImplFor == null) {
325         return true;
326       }
327
328       for (int j = 0, c = x.params.size(); j < c; ++j) {
329         JParameter param = (JParameter) x.params.get(j);
330         Set JavaDoc/* <JParameter> */set = (Set JavaDoc) paramUpRefs.get(param);
331         if (set == null) {
332           set = new HashSet JavaDoc/* <JParameter> */();
333           paramUpRefs.put(param, set);
334         }
335         for (int i = 0; i < overrides.size(); ++i) {
336           JMethod baseMethod = (JMethod) overrides.get(i);
337           JParameter baseParam = (JParameter) baseMethod.params.get(j);
338           set.add(baseParam);
339         }
340         for (int i = 0; i < virtualOverrides.length; ++i) {
341           JMethod baseMethod = virtualOverrides[i];
342           JParameter baseParam = (JParameter) baseMethod.params.get(j);
343           set.add(baseParam);
344         }
345         if (staticImplFor != null && j > 1) {
346           // static impls have an extra first "this" arg
347
JParameter baseParam = (JParameter) staticImplFor.params.get(j - 1);
348           set.add(baseParam);
349         }
350       }
351
352       return true;
353     }
354
355     private void addAssignment(JVariable target, JExpression rhs) {
356       add(target, rhs, assignments);
357     }
358
359     private void addImplementor(JReferenceType target, JClassType implementor) {
360       add(target, implementor, implementors);
361     }
362
363     private void addOverrider(JMethod target, JMethod overrider) {
364       add(target, overrider, overriders);
365     }
366
367     private void addReturn(JMethod target, JExpression expr) {
368       add(target, expr, returns);
369     }
370   }
371
372   /**
373    * Wherever possible, use the type flow information recorded by RecordVisitor
374    * to change the declared type of a field, local, parameter, or method to a
375    * more specific type.
376    *
377    * Also optimize dynamic casts and instanceof operations where possible.
378    */

379   public class TightenTypesVisitor extends JModVisitor {
380
381     /**
382      * <code>true</code> if this visitor has changed the AST apart from calls
383      * to Context.
384      */

385     private boolean myDidChange = false;
386
387     public boolean didChange() {
388       return myDidChange || super.didChange();
389     }
390
391     public void endVisit(JCastOperation x, Context ctx) {
392       JType argType = x.getExpr().getType();
393       if (!(x.getCastType() instanceof JReferenceType)
394           || !(argType instanceof JReferenceType)) {
395         return;
396       }
397
398       JReferenceType toType = (JReferenceType) x.getCastType();
399       JReferenceType fromType = (JReferenceType) argType;
400
401       boolean triviallyTrue = false;
402       boolean triviallyFalse = false;
403
404       JTypeOracle typeOracle = program.typeOracle;
405       if (typeOracle.canTriviallyCast(fromType, toType)) {
406         triviallyTrue = true;
407       } else if (!typeOracle.isInstantiatedType(toType)) {
408         triviallyFalse = true;
409       } else if (!typeOracle.canTheoreticallyCast(fromType, toType)) {
410         triviallyFalse = true;
411       }
412
413       if (triviallyTrue) {
414         // remove the cast operation
415
ctx.replaceMe(x.getExpr());
416       } else if (triviallyFalse) {
417         // replace with a magic NULL cast
418
JCastOperation newOp = new JCastOperation(program, x.getSourceInfo(),
419             program.getTypeNull(), x.getExpr());
420         ctx.replaceMe(newOp);
421       }
422     }
423
424     // @Override
425
public void endVisit(JField x, Context ctx) {
426       tighten(x);
427     }
428
429     // @Override
430
public void endVisit(JInstanceOf x, Context ctx) {
431       JType argType = x.getExpr().getType();
432       if (!(argType instanceof JReferenceType)) {
433         // TODO: is this even possible? Replace with assert maybe.
434
return;
435       }
436
437       JReferenceType toType = x.getTestType();
438       JReferenceType fromType = (JReferenceType) argType;
439
440       boolean triviallyTrue = false;
441       boolean triviallyFalse = false;
442
443       JTypeOracle typeOracle = program.typeOracle;
444       if (fromType == program.getTypeNull()) {
445         // null is never instanceOf anything
446
triviallyFalse = true;
447       } else if (typeOracle.canTriviallyCast(fromType, toType)) {
448         triviallyTrue = true;
449       } else if (!typeOracle.isInstantiatedType(toType)) {
450         triviallyFalse = true;
451       } else if (!typeOracle.canTheoreticallyCast(fromType, toType)) {
452         triviallyFalse = true;
453       }
454
455       if (triviallyTrue) {
456         // replace with a simple null test
457
JNullLiteral nullLit = program.getLiteralNull();
458         JBinaryOperation neq = new JBinaryOperation(program, x.getSourceInfo(),
459             program.getTypePrimitiveBoolean(), JBinaryOperator.NEQ,
460             x.getExpr(), nullLit);
461         ctx.replaceMe(neq);
462       } else if (triviallyFalse) {
463         // replace with a false literal
464
ctx.replaceMe(program.getLiteralBoolean(false));
465       }
466     }
467
468     // @Override
469
public void endVisit(JLocal x, Context ctx) {
470       tighten(x);
471     }
472
473     /**
474      * Tighten based on return types and overrides.
475      */

476     // @Override
477
public void endVisit(JMethod x, Context ctx) {
478
479       if (!(x.getType() instanceof JReferenceType)) {
480         return;
481       }
482       JReferenceType refType = (JReferenceType) x.getType();
483
484       if (refType == typeNull) {
485         return;
486       }
487
488       // tighten based on non-instantiability
489
if (!program.typeOracle.isInstantiatedType(refType)) {
490         x.setType(typeNull);
491         myDidChange = true;
492         return;
493       }
494
495       // tighten based on both returned types and possible overrides
496
List JavaDoc/* <JReferenceType> */typeList = new ArrayList JavaDoc/* <JReferenceType> */();
497
498       /*
499        * Always assume at least one null assignment; if there really aren't any
500        * other assignments, then this variable will get the null type. If there
501        * are, it won't hurt anything because null type will always lose.
502        */

503       typeList.add(typeNull);
504
505       Set JavaDoc/* <JExpression> */myReturns = (Set JavaDoc) returns.get(x);
506       if (myReturns != null) {
507         for (Iterator JavaDoc iter = myReturns.iterator(); iter.hasNext();) {
508           JExpression expr = (JExpression) iter.next();
509           typeList.add(expr.getType());
510         }
511       }
512       Set JavaDoc/* <JMethod> */myOverriders = (Set JavaDoc) overriders.get(x);
513       if (myOverriders != null) {
514         for (Iterator JavaDoc iter = myOverriders.iterator(); iter.hasNext();) {
515           JMethod method = (JMethod) iter.next();
516           typeList.add(method.getType());
517         }
518       }
519
520       JReferenceType resultType = program.generalizeTypes(typeList);
521       resultType = program.strongerType(refType, resultType);
522       if (refType != resultType) {
523         x.setType(resultType);
524         myDidChange = true;
525       }
526     }
527
528     // @Override
529
public void endVisit(JParameter x, Context ctx) {
530       tighten(x);
531     }
532
533     // @Override
534
public boolean visit(JClassType x, Context ctx) {
535       // don't mess with classes used in code gen
536
if (program.specialTypes.contains(x)) {
537         return false;
538       }
539       return true;
540     }
541
542     public boolean visit(JsniMethod x, Context ctx) {
543       /*
544        * Explicitly NOT visiting native methods since we can't infer type
545        * information.
546        *
547        * TODO(later): can we figure out simple pass-through info?
548        */

549       return false;
550     }
551
552     /**
553      * Tighten based on assignment, and for parameters, callArgs as well.
554      */

555     private void tighten(JVariable x) {
556       if (!(x.getType() instanceof JReferenceType)) {
557         return;
558       }
559       JReferenceType refType = (JReferenceType) x.getType();
560
561       if (refType == typeNull) {
562         return;
563       }
564
565       // tighten based on non-instantiability
566
if (!program.typeOracle.isInstantiatedType(refType)) {
567         x.setType(typeNull);
568         myDidChange = true;
569         return;
570       }
571
572       // tighten based on assignment
573
List JavaDoc/* <JReferenceType> */typeList = new ArrayList JavaDoc/* <JReferenceType> */();
574
575       /*
576        * For non-parameters, always assume at least one null assignment; if
577        * there really aren't any other assignments, then this variable will get
578        * the null type. If there are, it won't hurt anything because null type
579        * will always lose.
580        *
581        * For parameters, don't perform any tightening if we can't find any
582        * actual assignments. The method should eventually get pruned.
583        */

584       if (!(x instanceof JParameter)) {
585         typeList.add(typeNull);
586       }
587
588       Set JavaDoc/* <JExpression> */myAssignments = (Set JavaDoc) assignments.get(x);
589       if (myAssignments != null) {
590         for (Iterator JavaDoc iter = myAssignments.iterator(); iter.hasNext();) {
591           JExpression expr = (JExpression) iter.next();
592           JType type = expr.getType();
593           if (!(type instanceof JReferenceType)) {
594             return; // something fishy is going on, just abort
595
}
596           typeList.add(type);
597         }
598       }
599
600       if (x instanceof JParameter) {
601         Set JavaDoc/* <JParameter> */myParams = (Set JavaDoc) paramUpRefs.get(x);
602         if (myParams != null) {
603           for (Iterator JavaDoc iter = myParams.iterator(); iter.hasNext();) {
604             JParameter param = (JParameter) iter.next();
605             typeList.add(param.getType());
606           }
607         }
608       }
609
610       if (typeList.isEmpty()) {
611         return;
612       }
613
614       JReferenceType resultType = program.generalizeTypes(typeList);
615       resultType = program.strongerType(refType, resultType);
616       if (refType != resultType) {
617         x.setType(resultType);
618         myDidChange = true;
619       }
620     }
621   }
622
623   public static boolean exec(JProgram program) {
624     return new TypeTightener(program).execImpl();
625   }
626
627   private static/* <T, V> */void add(Object JavaDoc target, Object JavaDoc value,
628       Map JavaDoc/* <T, Set<V>> */map) {
629     Set JavaDoc/* <V> */set = (Set JavaDoc) map.get(target);
630     if (set == null) {
631       set = new HashSet JavaDoc/* <V> */();
632       map.put(target, set);
633     }
634     set.add(value);
635   }
636
637   private final Map JavaDoc/* <JVariable, Set<JExpression>> */assignments = new IdentityHashMap JavaDoc();
638   private final Map JavaDoc/* <JReferenceType, Set<JClassType>> */implementors = new IdentityHashMap JavaDoc();
639   private final Map JavaDoc/* <JMethod, Set<JMethod>> */overriders = new IdentityHashMap JavaDoc();
640   private final Map JavaDoc/* <JParameter, Set<JParameter>> */paramUpRefs = new IdentityHashMap JavaDoc();
641   private final JProgram program;
642   private final Map JavaDoc/* <JMethod, Set<JExpression>> */returns = new IdentityHashMap JavaDoc();
643   private final JNullType typeNull;
644
645   private TypeTightener(JProgram program) {
646     this.program = program;
647     typeNull = program.getTypeNull();
648   }
649
650   private boolean execImpl() {
651     RecordVisitor recorder = new RecordVisitor();
652     recorder.accept(program);
653
654     /*
655      * We must iterate mutiple times because each way point we tighten creates
656      * more opportunities to do additional tightening for the things that depend
657      * on it.
658      */

659     boolean madeChanges = false;
660     while (true) {
661       TightenTypesVisitor tightener = new TightenTypesVisitor();
662       tightener.accept(program);
663       if (!tightener.didChange()) {
664         break;
665       }
666       madeChanges = true;
667
668       FixDanglingRefsVisitor fixer = new FixDanglingRefsVisitor();
669       fixer.accept(program);
670     }
671     return madeChanges;
672   }
673
674 }
675
Popular Tags