Skip to content

Commit f13691d

Browse files
nbeloglazovcopybara-github
authored andcommitted
Improve SymbolTable handling of inheritance (interfaces, records).
When looking up property on a symbol try using ObjectType.getPropertyNode() if available. That function handles interfaces/records with multiple parents. 1. Fixes inheritance in @records. 2. Supports multiple inheritance for interfaces. PiperOrigin-RevId: 556971231
1 parent 84e1928 commit f13691d

File tree

2 files changed

+117
-9
lines changed

2 files changed

+117
-9
lines changed

src/com/google/javascript/jscomp/SymbolTable.java

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
import com.google.javascript.rhino.jstype.SimpleSlot;
5454
import com.google.javascript.rhino.jstype.StaticTypedScope;
5555
import com.google.javascript.rhino.jstype.StaticTypedSlot;
56-
import com.google.javascript.rhino.jstype.UnionType;
5756
import java.util.ArrayList;
5857
import java.util.Collection;
5958
import java.util.Collections;
@@ -361,10 +360,9 @@ public List<Symbol> getAllSymbolsForType(JSType type) {
361360
return ImmutableList.of();
362361
}
363362

364-
UnionType unionType = type.toMaybeUnionType();
365-
if (unionType != null) {
363+
if (type.isUnionType()) {
366364
ImmutableList.Builder<Symbol> result = ImmutableList.builder();
367-
for (JSType alt : unionType.getAlternates()) {
365+
for (JSType alt : type.toMaybeUnionType().getAlternates()) {
368366
// Our type system never has nested unions.
369367
Symbol altSym = getSymbolForTypeHelper(alt, true);
370368
if (altSym != null) {
@@ -831,7 +829,7 @@ void fillNamespaceReferences() {
831829
if (namespace == null && root.scope.isGlobalScope()) {
832830
// Originally UNKNOWN_TYPE has been always used for namespace symbols even though
833831
// compiler does have type information attached to a node. Unclear why. Changing code to
834-
// propery type mostly works. It only fails on Foo.prototype cases for some reason.
832+
// property type mostly works. It only fails on Foo.prototype cases for some reason.
835833
// It's pretty rare case when Foo.prototype defined in global scope though so for now
836834
// just carve it out.
837835
JSType nodeType = currentNode.getJSType();
@@ -1890,9 +1888,25 @@ public void process(Node externs, Node root) {
18901888
}
18911889

18921890
private boolean maybeDefineReference(Node n, String propName, Symbol ownerSymbol) {
1893-
// getPropertyScope() will be null in some rare cases where there
1894-
// are no extern declarations for built-in types (like Function).
1895-
if (ownerSymbol != null && ownerSymbol.getPropertyScope() != null) {
1891+
if (ownerSymbol == null) {
1892+
return false;
1893+
}
1894+
// If current symbol is ObjectType - try using ObjectType.getPropertyNode to find a node.
1895+
// That function searches for property in all extended classes and interfaces, which supports
1896+
// multiple extended interfaces.
1897+
JSType owner = ownerSymbol.getType();
1898+
if (owner != null && owner.isObjectType()) {
1899+
Node propNode = owner.toObjectType().getPropertyNode(propName);
1900+
Symbol propSymbol = symbols.get(propNode, propName);
1901+
if (propSymbol != null) {
1902+
propSymbol.defineReferenceAt(n);
1903+
return true;
1904+
}
1905+
}
1906+
// Use PropertyScope to find property. PropertyScope doesn't support multiple parents so all
1907+
// cases where multiple parents can occur (e.g. class implementing 2+ interfaces) must be
1908+
// handled above.
1909+
if (ownerSymbol.getPropertyScope() != null) {
18961910
Symbol prop = ownerSymbol.getPropertyScope().getSlot(propName);
18971911
if (prop != null) {
18981912
prop.defineReferenceAt(n);

test/com/google/javascript/jscomp/SymbolTableTest.java

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,100 @@ public void testMethodReferences() {
750750
assertThat(table.getReferences(method)).hasSize(3);
751751
}
752752

753+
@Test
754+
public void testMethodReferencesNullableClass() {
755+
SymbolTable table =
756+
createSymbolTable(
757+
lines(
758+
"class DomHelper { method() {} }",
759+
"let /** ?DomHelper */ helper;",
760+
"helper.method();"));
761+
762+
Symbol method = getGlobalVar(table, "DomHelper.prototype.method");
763+
assertThat(table.getReferences(method)).hasSize(2);
764+
}
765+
766+
@Test
767+
public void testMethodReferencesNullableInterface() {
768+
SymbolTable table =
769+
createSymbolTable(
770+
lines(
771+
"/** @interface */ class DomHelper { method() {} }",
772+
"let /** ?DomHelper */ helper;",
773+
"helper.method();"));
774+
775+
Symbol method = getGlobalVar(table, "DomHelper.prototype.method");
776+
assertThat(table.getReferences(method)).hasSize(2);
777+
}
778+
779+
@Test
780+
public void testMethodReferencesNullableRecord() {
781+
SymbolTable table =
782+
createSymbolTable(
783+
lines(
784+
"/** @record */ function DomHelper() {}",
785+
"DomHelper.prototype.method = function() {};",
786+
"let /** ?DomHelper */ helper;",
787+
"helper.method();"));
788+
789+
Symbol method = getGlobalVar(table, "DomHelper.prototype.method");
790+
assertThat(table.getReferences(method)).hasSize(2);
791+
}
792+
793+
@Test
794+
public void testRecordInheritance() {
795+
SymbolTable table =
796+
createSymbolTable(
797+
lines(
798+
"/** @record */ function DomHelper() {}",
799+
"DomHelper.prototype.method = function() {};",
800+
"",
801+
"/** @record @extends {DomHelper} */ function DomHelper2() {}",
802+
"DomHelper2.prototype.method2 = function() {};",
803+
"",
804+
"let /** ?DomHelper2 */ helper;",
805+
"helper.method();"));
806+
807+
Symbol method = getGlobalVar(table, "DomHelper.prototype.method");
808+
assertThat(table.getReferences(method)).hasSize(2);
809+
}
810+
811+
@Test
812+
public void testUnionType() {
813+
SymbolTable table =
814+
createSymbolTable(
815+
lines(
816+
"class First { method() {} }",
817+
"class Second { method() {} }",
818+
"let /** !Second|!First */ obj;",
819+
"obj.method();"));
820+
821+
assertThat(table.getReferences(getGlobalVar(table, "First.prototype.method"))).hasSize(2);
822+
assertThat(table.getReferences(getGlobalVar(table, "Second.prototype.method"))).hasSize(2);
823+
}
824+
825+
@Test
826+
public void testMultipleInterfaceInheritance() {
827+
SymbolTable table =
828+
createSymbolTable(
829+
lines(
830+
"/** @interface */",
831+
"class First { doFirst() {} }",
832+
"",
833+
"/** @interface */",
834+
"class Second { doSecond() {} }",
835+
"",
836+
"/** @interface @extends {First} @extends {Second} */",
837+
"class Third {}",
838+
"",
839+
"let /** !Third */ obj;",
840+
"obj.doFirst();",
841+
"obj.doSecond();"));
842+
843+
assertThat(table.getReferences(getGlobalVar(table, "First.prototype.doFirst"))).hasSize(2);
844+
assertThat(table.getReferences(getGlobalVar(table, "Second.prototype.doSecond"))).hasSize(2);
845+
}
846+
753847
@Test
754848
public void testSuperClassMethodReferences() {
755849
SymbolTable table =
@@ -1737,7 +1831,7 @@ public void testDuplicatedWindowExternsMergedWithWindowPrototype() {
17371831
}
17381832
assertThat(refsPerFile).containsExactly("in1", 2, "externs1", 1);
17391833
}
1740-
1834+
17411835
private void assertSymmetricOrdering(Ordering<Symbol> ordering, Symbol first, Symbol second) {
17421836
assertThat(ordering.compare(first, first)).isEqualTo(0);
17431837
assertThat(ordering.compare(second, second)).isEqualTo(0);

0 commit comments

Comments
 (0)