-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
AmbiguateProperties.java
740 lines (649 loc) · 25.1 KB
/
AmbiguateProperties.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Joiner;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.jscomp.graph.AdjacencyGraph;
import com.google.javascript.jscomp.graph.Annotation;
import com.google.javascript.jscomp.graph.GraphColoring;
import com.google.javascript.jscomp.graph.GraphColoring.GreedyGraphColoring;
import com.google.javascript.jscomp.graph.GraphNode;
import com.google.javascript.jscomp.graph.SubGraph;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.ObjectType;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Renames unrelated properties to the same name, using type information.
* This allows better compression as more properties can be given short names.
*
* <p>Properties are considered unrelated if they are never referenced from the
* same type or from a subtype of each others' types, thus this pass is only
* effective if type checking is enabled.
*
* Example:
* <code>
* Foo.fooprop = 0;
* Foo.fooprop2 = 0;
* Bar.barprop = 0;
* </code>
*
* becomes:
*
* <code>
* Foo.a = 0;
* Foo.b = 0;
* Bar.a = 0;
* </code>
*
*/
class AmbiguateProperties implements CompilerPass {
private static final Logger logger = Logger.getLogger(
AmbiguateProperties.class.getName());
private final AbstractCompiler compiler;
private final List<Node> stringNodesToRename = new ArrayList<>();
// Can't use these to start property names.
private final char[] reservedFirstCharacters;
// Can't use these at all in property names.
private final char[] reservedNonFirstCharacters;
/** Map from property name to Property object */
private final Map<String, Property> propertyMap = new LinkedHashMap<>();
/** Property names that don't get renamed */
private final ImmutableSet<String> externedNames;
/** Names to which properties shouldn't be renamed, to avoid name conflicts */
private final Set<String> quotedNames = new HashSet<>();
/** Map from original property name to new name. Only used by tests. */
private Map<String, String> renamingMap = null;
/**
* Sorts Property objects by their count, breaking ties alphabetically to
* ensure a deterministic total ordering.
*/
private static final Comparator<Property> FREQUENCY_COMPARATOR =
new Comparator<Property>() {
@Override
public int compare(Property p1, Property p2) {
if (p1.numOccurrences != p2.numOccurrences) {
return p2.numOccurrences - p1.numOccurrences;
}
return p1.oldName.compareTo(p2.oldName);
}
};
/** A map from JSType to a unique representative Integer. */
private final BiMap<JSType, Integer> intForType = HashBiMap.create();
/**
* A map from JSType to JSTypeBitSet representing the types related to the type.
*
* <p>A type is always related to itself.
*/
private final Map<JSType, JSTypeBitSet> relatedBitsets = new HashMap<>();
/** A set of types that invalidate properties from ambiguation. */
private final InvalidatingTypes invalidatingTypes;
AmbiguateProperties(
AbstractCompiler compiler,
char[] reservedFirstCharacters,
char[] reservedNonFirstCharacters) {
checkState(compiler.getLifeCycleStage().isNormalized());
this.compiler = compiler;
this.reservedFirstCharacters = reservedFirstCharacters;
this.reservedNonFirstCharacters = reservedNonFirstCharacters;
this.invalidatingTypes = new InvalidatingTypes.Builder(compiler.getTypeRegistry())
.addTypesInvalidForPropertyRenaming()
.addAllTypeMismatches(compiler.getTypeMismatches())
.addAllTypeMismatches(compiler.getImplicitInterfaceUses())
.build();
this.externedNames =
ImmutableSet.<String>builder()
.add("prototype")
.addAll(compiler.getExternProperties())
.build();
}
static AmbiguateProperties makePassForTesting(
AbstractCompiler compiler,
char[] reservedFirstCharacters,
char[] reservedNonFirstCharacters) {
AmbiguateProperties ap =
new AmbiguateProperties(compiler, reservedFirstCharacters, reservedNonFirstCharacters);
ap.renamingMap = new HashMap<>();
return ap;
}
Map<String, String> getRenamingMap() {
checkNotNull(renamingMap);
return renamingMap;
}
/** Returns an integer that uniquely identifies a JSType. */
private int getIntForType(JSType type) {
// Templatized types don't exist at runtime, so collapse to raw type
if (type != null && type.isGenericObjectType()) {
type = type.toMaybeObjectType().getRawType();
}
if (intForType.containsKey(type)) {
return intForType.get(type).intValue();
}
int newInt = intForType.size() + 1;
intForType.put(type, newInt);
return newInt;
}
@Override
public void process(Node externs, Node root) {
// Find all property references and record the types on which they occur.
// Populate stringNodesToRename, propertyMap, quotedNames.
NodeTraversal.traverse(compiler, root, new ProcessProperties());
ImmutableSet.Builder<String> reservedNames = ImmutableSet.<String>builder()
.addAll(externedNames)
.addAll(quotedNames);
int numRenamedPropertyNames = 0;
int numSkippedPropertyNames = 0;
ArrayList<PropertyGraphNode> nodes = new ArrayList<>(propertyMap.size());
for (Property prop : propertyMap.values()) {
if (prop.skipAmbiguating) {
++numSkippedPropertyNames;
reservedNames.add(prop.oldName);
} else {
++numRenamedPropertyNames;
nodes.add(new PropertyGraphNode(prop));
}
}
PropertyGraph graph = new PropertyGraph(nodes);
GraphColoring<Property, Void> coloring =
new GreedyGraphColoring<>(graph, FREQUENCY_COMPARATOR);
int numNewPropertyNames = coloring.color();
// Generate new names for the properties that will be renamed.
NameGenerator nameGen =
new DefaultNameGenerator(
reservedNames.build(), "", reservedFirstCharacters, reservedNonFirstCharacters);
String[] colorMap = new String[numNewPropertyNames];
for (int i = 0; i < numNewPropertyNames; ++i) {
colorMap[i] = nameGen.generateNextName();
}
// Translate the color of each Property instance to a name.
for (PropertyGraphNode node : graph.getNodes()) {
node.getValue().newName = colorMap[node.getAnnotation().hashCode()];
if (renamingMap != null) {
renamingMap.put(node.getValue().oldName, node.getValue().newName);
}
}
// Actually assign the new names to the relevant STRING nodes in the AST.
for (Node n : stringNodesToRename) {
String oldName = n.getString();
Property p = propertyMap.get(oldName);
if (p != null && p.newName != null) {
checkState(oldName.equals(p.oldName));
if (!p.newName.equals(oldName)) {
n.setString(p.newName);
compiler.reportChangeToEnclosingScope(n);
}
}
}
// We may have renamed getter / setter properties.
GatherGetterAndSetterProperties.update(compiler, externs, root);
if (logger.isLoggable(Level.FINE)) {
logger.fine("Collapsed " + numRenamedPropertyNames + " properties into "
+ numNewPropertyNames + " and skipped renaming "
+ numSkippedPropertyNames + " properties.");
}
}
private BitSet getRelatedTypesOnNonUnion(JSType type) {
// All of the types we encounter should have been added to the
// relatedBitsets via computeRelatedTypesForNonUnionType.
if (relatedBitsets.containsKey(type)) {
return relatedBitsets.get(type);
} else {
throw new RuntimeException("Related types should have been computed for"
+ " type: " + type + " but have not been.");
}
}
/**
* Adds subtypes - and implementors, in the case of interfaces - of the type to its JSTypeBitSet
* of related types.
*
* <p>The 'is related to' relationship is best understood graphically. Draw an arrow from each
* instance type to the prototype of each of its subclass. Draw an arrow from each prototype to
* its instance type. Draw an arrow from each interface to its implementors. A type is related to
* another if there is a directed path in the graph from the type to other. Thus, the 'is related
* to' relationship is reflexive and transitive.
*
* <p>Example with Foo extends Bar which extends Baz and Bar implements I:
*
* <pre>{@code
* Foo -> Bar.prototype -> Bar -> Baz.prototype -> Baz
* ^
* |
* I
* }</pre>
*
* <p>We also need to handle relationships between functions because of ES6 class-side inheritance
* although the top Function type itself is invalidating.
*/
@SuppressWarnings("ReferenceEquality")
private void computeRelatedTypesForNonUnionType(JSType type) {
// This method could be expanded to handle union types if necessary, but currently no union
// types are ever passed as input so the method doesn't have logic for union types
checkState(!type.isUnionType(), type);
if (relatedBitsets.containsKey(type)) {
// We only need to generate the bit set once.
return;
}
JSTypeBitSet related = new JSTypeBitSet(intForType.size());
relatedBitsets.put(type, related);
related.set(getIntForType(type));
// A prototype is related to its instance.
if (type.isFunctionPrototypeType()) {
FunctionType maybeCtor = type.toMaybeObjectType().getOwnerFunction();
if (maybeCtor.isConstructor() || maybeCtor.isInterface()) {
addRelatedInstance(maybeCtor, related);
}
return;
}
// A class/interface is related to its subclasses/implementors.
FunctionType constructor = type.toMaybeObjectType().getConstructor();
if (constructor != null) {
for (FunctionType subType : constructor.getDirectSubTypes()) {
addRelatedInstance(subType, related);
}
}
// We only specifically handle implicit prototypes of functions in the case of ES6 classes
// For regular functions, the implicit prototype being Function.prototype does not matter
// because the type `Function` is invalidating.
// This may cause unexpected behavior for code that manually sets a prototype, e.g.
// Object.setPrototypeOf(myFunction, prototypeObj);
// but code like that should not be used with --ambiguate_properties or type-based optimizations
FunctionType fnType = type.toMaybeFunctionType();
if (fnType != null) {
for (FunctionType subType : fnType.getDirectSubTypes()) {
// We record all subtypes of constructors, but don't care about old 'ES5-style' subtyping,
// just ES6-style. This is equivalent to saying that the subtype constructor's implicit
// prototype is the given type
if (fnType == subType.getImplicitPrototype()) {
addRelatedType(subType, related);
}
}
}
}
/**
* Adds the instance of the given constructor, its implicit prototype and all
* its related types to the given bit set.
*/
private void addRelatedInstance(FunctionType constructor, JSTypeBitSet related) {
checkArgument(constructor.hasInstanceType(),
"Constructor %s without instance type.", constructor);
ObjectType instanceType = constructor.getInstanceType();
addRelatedType(instanceType, related);
}
/** Adds the given type and all its related types to the given bit set. */
private void addRelatedType(JSType type, JSTypeBitSet related) {
computeRelatedTypesForNonUnionType(type);
related.or(relatedBitsets.get(type));
}
class PropertyGraph implements AdjacencyGraph<Property, Void> {
private final ArrayList<PropertyGraphNode> nodes;
PropertyGraph(ArrayList<PropertyGraphNode> nodes) {
this.nodes = nodes;
}
@Override
public List<PropertyGraphNode> getNodes() {
return nodes;
}
@Override
public int getNodeCount() {
return nodes.size();
}
@Override
public GraphNode<Property, Void> getNode(Property property) {
throw new RuntimeException("PropertyGraph#getNode is never called.");
}
@Override
public SubGraph<Property, Void> newSubGraph() {
return new PropertySubGraph();
}
@Override
public void clearNodeAnnotations() {
for (PropertyGraphNode node : nodes) {
node.setAnnotation(null);
}
}
@Override
public int getWeight(Property value) {
return value.numOccurrences;
}
}
/**
* A {@link SubGraph} that represents properties. The related types of
* the properties are used to efficiently calculate adjacency information.
*/
class PropertySubGraph implements SubGraph<Property, Void> {
/** Types related to properties referenced in this subgraph. */
JSTypeBitSet relatedTypes = new JSTypeBitSet(intForType.size());
/**
* Returns true if prop is in an independent set from all properties in this
* sub graph. That is, if none of its related types intersects with the
* related types for this sub graph.
*/
@Override
public boolean isIndependentOf(Property prop) {
return !relatedTypes.intersects(prop.relatedTypes);
}
/**
* Adds the node to the sub graph, adding all its related types to the
* related types for the sub graph.
*/
@Override
public void addNode(Property prop) {
relatedTypes.or(prop.relatedTypes);
}
}
static class PropertyGraphNode implements GraphNode<Property, Void> {
Property property;
protected Annotation annotation;
PropertyGraphNode(Property property) {
this.property = property;
}
@Override
public Property getValue() {
return property;
}
@Override
@SuppressWarnings("unchecked")
public <A extends Annotation> A getAnnotation() {
return (A) annotation;
}
@Override
public void setAnnotation(Annotation data) {
annotation = data;
}
}
private void reportInvalidRenameFunction(Node n, String functionName, String message) {
compiler.report(
JSError.make(
n, PropertyRenamingDiagnostics.INVALID_RENAME_FUNCTION, functionName, message));
}
private static final String WRONG_ARGUMENT_COUNT = " Must be called with 1 or 2 arguments.";
private static final String WANT_STRING_LITERAL = " The first argument must be a string literal.";
private static final String DO_NOT_WANT_PATH = " The first argument must not be a property path.";
/** Finds all property references, recording the types on which they occur. */
private class ProcessProperties extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getToken()) {
case GETPROP:
processGetProp(n);
return;
case CALL:
processCall(n);
return;
case OBJECTLIT:
case OBJECT_PATTERN:
processObjectLitOrPattern(n);
return;
case GETELEM:
processGetElem(n);
return;
case CLASS:
processClass(n);
return;
default:
// Nothing to do.
}
}
private void processGetProp(Node getProp) {
Node propNode = getProp.getSecondChild();
JSType type = getJSType(getProp.getFirstChild());
maybeMarkCandidate(propNode, type);
}
private void processCall(Node call) {
Node target = call.getFirstChild();
if (!target.isQualifiedName()) {
return;
}
String renameFunctionName = target.getOriginalQualifiedName();
if (renameFunctionName != null
&& compiler.getCodingConvention().isPropertyRenameFunction(renameFunctionName)) {
int childCount = call.getChildCount();
if (childCount != 2 && childCount != 3) {
reportInvalidRenameFunction(call, renameFunctionName, WRONG_ARGUMENT_COUNT);
return;
}
Node propName = call.getSecondChild();
if (!propName.isString()) {
reportInvalidRenameFunction(call, renameFunctionName, WANT_STRING_LITERAL);
return;
}
if (propName.getString().contains(".")) {
reportInvalidRenameFunction(call, renameFunctionName, DO_NOT_WANT_PATH);
return;
}
// Skip ambiguation for properties in renaming calls
// NOTE (lharker@) - I'm not sure if this behavior is necessary, or if we could safely
// ambiguate the property as long as we also updated the property renaming call
Property p = getProperty(propName.getString());
p.skipAmbiguating = true;
} else if (NodeUtil.isObjectDefinePropertiesDefinition(call)) {
Node typeObj = call.getSecondChild();
JSType type = getJSType(typeObj);
Node objectLiteral = typeObj.getNext();
if (!objectLiteral.isObjectLit()) {
return;
}
for (Node key : objectLiteral.children()) {
if (key.isQuotedString()) {
quotedNames.add(key.getString());
} else {
maybeMarkCandidate(key, type);
}
}
}
}
private void processObjectLitOrPattern(Node objectLit) {
// Object.defineProperties literals are handled at the CALL node.
if (objectLit.getParent().isCall()
&& NodeUtil.isObjectDefinePropertiesDefinition(objectLit.getParent())) {
return;
}
// The children of an OBJECTLIT node are keys, where the values
// are the children of the keys.
JSType type = getJSType(objectLit);
for (Node key = objectLit.getFirstChild(); key != null; key = key.getNext()) {
switch (key.getToken()) {
case COMPUTED_PROP:
if (key.getFirstChild().isString()) {
// If this quoted prop name is statically determinable, ensure we don't rename some
// other property in a way that could conflict with it.
//
// This is largely because we store quoted member functions as computed properties and
// want to be consistent with how other quoted properties invalidate property names.
quotedNames.add(key.getFirstChild().getString());
}
break;
case MEMBER_FUNCTION_DEF:
case GETTER_DEF:
case SETTER_DEF:
case STRING_KEY:
if (key.isQuotedString()) {
// If this quoted prop name is statically determinable, ensure we don't rename some
// other property in a way that could conflict with it
quotedNames.add(key.getString());
} else {
maybeMarkCandidate(key, type);
}
break;
case ITER_REST:
case OBJECT_REST:
case ITER_SPREAD:
case OBJECT_SPREAD:
break; // Nothing to do.
default:
throw new IllegalStateException(
"Unexpected child of " + objectLit.getToken() + ": " + key.toStringTree());
}
}
}
private void processGetElem(Node n) {
// If this is a quoted property access (e.g. x['myprop']), we need to
// ensure that we never rename some other property in a way that
// could conflict with this quoted name.
Node child = n.getLastChild();
if (child.isString()) {
quotedNames.add(child.getString());
}
}
private void processClass(Node classNode) {
JSType classConstructorType = getJSType(classNode);
JSType classPrototype =
classConstructorType.isFunctionType()
? classConstructorType.toMaybeFunctionType().getPrototype()
: compiler.getTypeRegistry().getNativeType(JSTypeNative.UNKNOWN_TYPE);
for (Node member : NodeUtil.getClassMembers(classNode).children()) {
if (member.isQuotedString()) {
// ignore get 'foo'() {} and prevent property name collisions
// Note that only getters/setters are represented as quoted strings, not 'foo'() {}
// see https://github.com/google/closure-compiler/issues/3071
quotedNames.add(member.getString());
continue;
} else if (member.isComputedProp()) {
// ignore ['foo']() {}
// for simple cases, we also prevent renaming collisions
if (member.getFirstChild().isString()) {
quotedNames.add(member.getFirstChild().getString());
}
continue;
} else if (NodeUtil.isEs6ConstructorMemberFunctionDef(member)) {
// don't rename `class C { constructor() {} }` !
// This only applies for ES6 classes, not generic properties called 'constructor', which
// is why it's handled in this method specifically.
continue;
}
JSType memberOwnerType = member.isStaticMember() ? classConstructorType : classPrototype;
// member could be a MEMBER_FUNCTION_DEF, GETTER_DEF, or SETTER_DEF
maybeMarkCandidate(member, memberOwnerType);
}
}
/**
* If a property node is eligible for renaming, stashes a reference to it
* and increments the property name's access count.
*
* @param n The STRING node for a property
*/
private void maybeMarkCandidate(Node n, JSType type) {
String name = n.getString();
if (!externedNames.contains(name)) {
stringNodesToRename.add(n);
recordProperty(name, type);
}
}
private Property recordProperty(String name, JSType type) {
Property prop = getProperty(name);
prop.addType(type);
return prop;
}
}
private Property getProperty(String name) {
Property prop = propertyMap.get(name);
if (prop == null) {
prop = new Property(name);
propertyMap.put(name, prop);
}
return prop;
}
/**
* This method gets the JSType from the Node argument and verifies that it is
* present.
*/
private JSType getJSType(Node n) {
JSType type = n.getJSType();
if (type == null) {
// TODO(bradfordcsmith): This branch indicates a compiler bug. It should throw an exception.
return compiler.getTypeRegistry().getNativeType(JSTypeNative.UNKNOWN_TYPE);
} else {
return type;
}
}
/** Encapsulates the information needed for renaming a property. */
private class Property {
final String oldName;
String newName;
int numOccurrences;
boolean skipAmbiguating;
JSTypeBitSet relatedTypes = new JSTypeBitSet(intForType.size());
Property(String name) {
this.oldName = name;
}
/** Add this type to this property, calculating */
void addType(JSType newType) {
if (skipAmbiguating) {
return;
}
++numOccurrences;
if (newType.isUnionType()) {
newType = newType.restrictByNotNullOrUndefined();
if (newType.isUnionType()) {
for (JSType alt : newType.getUnionMembers()) {
addNonUnionType(alt);
}
return;
}
}
addNonUnionType(newType);
}
private void addNonUnionType(JSType newType) {
if (skipAmbiguating || invalidatingTypes.isInvalidating(newType)) {
skipAmbiguating = true;
return;
}
if (!relatedTypes.get(getIntForType(newType))) {
computeRelatedTypesForNonUnionType(newType);
relatedTypes.or(getRelatedTypesOnNonUnion(newType));
}
}
}
// A BitSet that stores type info. Adds pretty-print routines.
private class JSTypeBitSet extends BitSet {
private static final long serialVersionUID = 1L;
private JSTypeBitSet(int size) {
super(size);
}
/**
* Pretty-printing, for diagnostic purposes.
*/
@Override
public String toString() {
int from = 0;
int current = 0;
List<String> types = new ArrayList<>();
while (-1 != (current = nextSetBit(from))) {
types.add(intForType.inverse().get(current).toString());
from = current + 1;
}
return Joiner.on(" && ").join(types);
}
}
}