Skip to content

Commit c957257

Browse files
lauraharkercopybara-github
authored andcommitted
Typecheck implementations of @abstract Symbol-keyed properties/methods
Also simplify @interface override checks to use the new getOwnPropertyKeys() helper. PiperOrigin-RevId: 790750515
1 parent 740b9de commit c957257

File tree

6 files changed

+112
-26
lines changed

6 files changed

+112
-26
lines changed

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2499,19 +2499,19 @@ private boolean couldBeAConstructor(JSType type) {
24992499
private void checkInterfaceConflictProperties(
25002500
Node n,
25012501
String functionName,
2502-
Map<String, ObjectType> properties,
2503-
Map<String, ObjectType> currentProperties,
2502+
Map<Property.Key, ObjectType> properties,
2503+
Map<Property.Key, ObjectType> currentProperties,
25042504
ObjectType interfaceType) {
25052505
ObjectType implicitProto = interfaceType.getImplicitPrototype();
2506-
Set<String> currentPropertyNames;
2506+
ImmutableSet<Property.Key> currentPropertyNames;
25072507
if (implicitProto == null) {
25082508
// This can be the case if interfaceType is proxy to a non-existent
25092509
// object (which is a bad type annotation, but shouldn't crash).
25102510
currentPropertyNames = ImmutableSet.of();
25112511
} else {
2512-
currentPropertyNames = implicitProto.getOwnPropertyNames();
2512+
currentPropertyNames = implicitProto.getOwnPropertyKeys();
25132513
}
2514-
for (String name : currentPropertyNames) {
2514+
for (Property.Key name : currentPropertyNames) {
25152515
ObjectType oType = properties.get(name);
25162516
currentProperties.put(name, interfaceType);
25172517
if (oType != null) {
@@ -2531,7 +2531,7 @@ private void checkInterfaceConflictProperties(
25312531
n,
25322532
INCOMPATIBLE_EXTENDED_PROPERTY_TYPE,
25332533
functionName,
2534-
name,
2534+
name.humanReadableName(),
25352535
oType.toString(),
25362536
interfaceType.toString()));
25372537
}
@@ -2695,8 +2695,8 @@ private void checkInterface(Node n, FunctionType functionType) {
26952695
// Check whether the extended interfaces have any conflicts
26962696
if (functionType.getExtendedInterfacesCount() > 1) {
26972697
// Only check when extending more than one interfaces
2698-
LinkedHashMap<String, ObjectType> properties = new LinkedHashMap<>();
2699-
LinkedHashMap<String, ObjectType> currentProperties = new LinkedHashMap<>();
2698+
LinkedHashMap<Property.Key, ObjectType> properties = new LinkedHashMap<>();
2699+
LinkedHashMap<Property.Key, ObjectType> currentProperties = new LinkedHashMap<>();
27002700
for (ObjectType interfaceType : functionType.getExtendedInterfaces()) {
27012701
currentProperties.clear();
27022702
checkInterfaceConflictProperties(

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

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,10 @@
5252
import com.google.javascript.rhino.jstype.JSType.SubtypingMode;
5353
import com.google.javascript.rhino.jstype.JSTypeNative;
5454
import com.google.javascript.rhino.jstype.JSTypeRegistry;
55-
import com.google.javascript.rhino.jstype.KnownSymbolType;
5655
import com.google.javascript.rhino.jstype.NamedType;
5756
import com.google.javascript.rhino.jstype.ObjectType;
5857
import com.google.javascript.rhino.jstype.Property;
5958
import com.google.javascript.rhino.jstype.Property.OwnedProperty;
60-
import com.google.javascript.rhino.jstype.Property.StringKey;
6159
import com.google.javascript.rhino.jstype.TemplateType;
6260
import com.google.javascript.rhino.jstype.TemplateTypeMap;
6361
import com.google.javascript.rhino.jstype.TemplateTypeReplacer;
@@ -950,20 +948,13 @@ void expectAllInterfaceProperties(Node n, FunctionType type) {
950948
private void expectInterfaceProperties(
951949
Node n, ObjectType instance, ObjectType ancestorInterface) {
952950
// Case: `/** @interface */ class Foo { constructor() { this.prop; } }`
953-
for (String prop : ancestorInterface.getOwnPropertyNames()) {
954-
expectInterfaceProperty(n, instance, ancestorInterface, new StringKey(prop));
955-
}
956-
for (KnownSymbolType symbol : ancestorInterface.getOwnPropertyKnownSymbols()) {
957-
expectInterfaceProperty(n, instance, ancestorInterface, new Property.SymbolKey(symbol));
951+
for (Property.Key prop : ancestorInterface.getOwnPropertyKeys()) {
952+
expectInterfaceProperty(n, instance, ancestorInterface, prop);
958953
}
959954
if (ancestorInterface.getImplicitPrototype() != null) {
960955
// Case: `/** @interface */ class Foo { prop() { } }`
961-
for (String prop : ancestorInterface.getImplicitPrototype().getOwnPropertyNames()) {
962-
expectInterfaceProperty(n, instance, ancestorInterface, new StringKey(prop));
963-
}
964-
for (KnownSymbolType symbol :
965-
ancestorInterface.getImplicitPrototype().getOwnPropertyKnownSymbols()) {
966-
expectInterfaceProperty(n, instance, ancestorInterface, new Property.SymbolKey(symbol));
956+
for (Property.Key prop : ancestorInterface.getImplicitPrototype().getOwnPropertyKeys()) {
957+
expectInterfaceProperty(n, instance, ancestorInterface, prop);
967958
}
968959
}
969960
}
@@ -1049,15 +1040,15 @@ void checkPropertyType(
10491040
void expectAbstractMethodsImplemented(Node n, FunctionType ctorType) {
10501041
checkArgument(ctorType.isConstructor());
10511042

1052-
Map<String, ObjectType> abstractMethodSuperTypeMap = new LinkedHashMap<>();
1043+
Map<Property.Key, ObjectType> abstractMethodSuperTypeMap = new LinkedHashMap<>();
10531044
FunctionType currSuperCtor = ctorType.getSuperClassConstructor();
10541045
if (currSuperCtor == null || !currSuperCtor.isAbstract()) {
10551046
return;
10561047
}
10571048

10581049
while (currSuperCtor != null && currSuperCtor.isAbstract()) {
10591050
ObjectType superType = currSuperCtor.getInstanceType();
1060-
for (String prop : currSuperCtor.getPrototype().getOwnPropertyNames()) {
1051+
for (Property.Key prop : currSuperCtor.getPrototype().getOwnPropertyKeys()) {
10611052
FunctionType maybeAbstractMethod = superType.findPropertyType(prop).toMaybeFunctionType();
10621053
if (maybeAbstractMethod != null
10631054
&& maybeAbstractMethod.isAbstract()
@@ -1070,7 +1061,7 @@ void expectAbstractMethodsImplemented(Node n, FunctionType ctorType) {
10701061

10711062
ObjectType instance = ctorType.getInstanceType();
10721063
for (var entry : abstractMethodSuperTypeMap.entrySet()) {
1073-
String method = entry.getKey();
1064+
Property.Key method = entry.getKey();
10741065
ObjectType superType = entry.getValue();
10751066
FunctionType abstractMethod = instance.findPropertyType(method).toMaybeFunctionType();
10761067
if (abstractMethod == null || abstractMethod.isAbstract()) {
@@ -1080,7 +1071,7 @@ void expectAbstractMethodsImplemented(Node n, FunctionType ctorType) {
10801071
JSError.make(
10811072
n,
10821073
ABSTRACT_METHOD_NOT_IMPLEMENTED,
1083-
method,
1074+
method.humanReadableName(),
10841075
superType.toString(),
10851076
instance.toString()));
10861077
}

src/com/google/javascript/rhino/jstype/ObjectType.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@
5454
import com.google.javascript.rhino.JSDocInfo;
5555
import com.google.javascript.rhino.Node;
5656
import com.google.javascript.rhino.jstype.Property.OwnedProperty;
57-
import java.util.Iterator;
5857
import java.util.Map;
5958
import java.util.Set;
6059
import org.jspecify.annotations.Nullable;
@@ -674,6 +673,22 @@ public Set<String> getOwnPropertyNames() {
674673
return getPropertyMap().getOwnPropertyNames();
675674
}
676675

676+
/**
677+
* Returns the names of all the properties directly on this type.
678+
*/
679+
public final ImmutableSet<Property.Key> getOwnPropertyKeys() {
680+
Set<String> ownPropertyNames = this.getOwnPropertyNames();
681+
Set<KnownSymbolType> ownSymbols = this.getOwnPropertyKnownSymbols();
682+
ImmutableSet.Builder<Property.Key> ownPropertyKeys = ImmutableSet.builder();
683+
for (String propertyName : ownPropertyNames) {
684+
ownPropertyKeys.add(new Property.StringKey(propertyName));
685+
}
686+
for (KnownSymbolType symbol : ownSymbols) {
687+
ownPropertyKeys.add(new Property.SymbolKey(symbol));
688+
}
689+
return ownPropertyKeys.build();
690+
}
691+
677692
/**
678693
* Checks whether the property's type is inferred.
679694
*/

src/com/google/javascript/rhino/testing/TypeSubject.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,10 @@ public void isConstructorFor(String name) {
349349
.isEqualTo(name);
350350
}
351351

352+
public void isAbstract() {
353+
check("isAbstract()").that(actualFunctionType().isAbstract()).isTrue();
354+
}
355+
352356
public void hasPrimitiveId(ClosurePrimitive id) {
353357
check("getClosurePrimitive()")
354358
.that(actualNonNull().toMaybeFunctionType().getClosurePrimitive())

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,21 @@ class C extends B {
453453
.run();
454454
}
455455

456+
@Test
457+
public void testAbstractMethodInEs6NotImplemented_symbolProp() {
458+
newTest()
459+
.addSource(
460+
"""
461+
/** @abstract */ class A {
462+
/** @abstract */ [Symbol.iterator]() {}
463+
}
464+
class B extends A {}
465+
""")
466+
.addDiagnostic("property Symbol.iterator on abstract class A is not implemented by type B")
467+
.includeDefaultExterns()
468+
.run();
469+
}
470+
456471
@Test
457472
public void testAbstractMethodHandling1() {
458473
newTest()
@@ -609,6 +624,35 @@ function Baz() {}
609624
.run();
610625
}
611626

627+
@Test
628+
public void testOverriddenReturnDoesntMatchOnAbstractClass_symbolProp() {
629+
newTest()
630+
.addSource(
631+
"""
632+
/** @interface */ function IFoo() {}
633+
/** @return {number} */ IFoo.prototype[Symbol.iterator] = function() {}
634+
/** @constructor */ function Foo() {}
635+
/** @return {string} */ Foo.prototype[Symbol.iterator] = function() {}
636+
/** @constructor @extends {Foo} */ function Bar() {}
637+
/**
638+
* @constructor
639+
* @abstract
640+
* @extends {Bar}
641+
* @implements {IFoo}
642+
*/
643+
function Baz() {}
644+
/** @return {string} */
645+
function test() { return (/** @type {Baz} */ (null))[Symbol.iterator](); }
646+
""")
647+
.addDiagnostic(
648+
"""
649+
mismatch of the Symbol.iterator property on type Baz and the type of the property it overrides from interface IFoo
650+
original: function(this:IFoo): number
651+
override: function(this:Foo): string
652+
""")
653+
.run();
654+
}
655+
612656
@Test
613657
public void testAbstractMethodCall1() {
614658
// Converted from Closure style "goog.base" super call

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import com.google.javascript.rhino.jstype.KnownSymbolType;
5858
import com.google.javascript.rhino.jstype.NamedType;
5959
import com.google.javascript.rhino.jstype.ObjectType;
60+
import com.google.javascript.rhino.jstype.Property;
6061
import java.util.ArrayDeque;
6162
import java.util.Deque;
6263
import java.util.HashMap;
@@ -3338,6 +3339,37 @@ class Foo {
33383339
.toStringIsEqualTo("function(this:Foo): symbol");
33393340
}
33403341

3342+
@Test
3343+
public void testClassWithWellKnownSymbolTypedMethod_abstract() {
3344+
testSame(
3345+
externs(
3346+
new TestExternsBuilder().addIterable().build(),
3347+
"""
3348+
/** @const {symbol} */
3349+
Symbol.dispose;
3350+
"""),
3351+
srcs(
3352+
"""
3353+
/** @abstract */
3354+
class Foo {
3355+
/**
3356+
* @param {string} x
3357+
* @param {number} y
3358+
* @abstract
3359+
*/
3360+
[Symbol.iterator](x, y) {}
3361+
}
3362+
"""));
3363+
3364+
FunctionType foo = (FunctionType) findNameType("Foo", globalScope);
3365+
KnownSymbolType symbolIteratorType =
3366+
(KnownSymbolType) findNameType("Symbol.iterator", globalScope);
3367+
assertType(foo.getInstanceType()).hasProperty(symbolIteratorType);
3368+
assertType(foo.getInstanceType().getPropertyType(new Property.SymbolKey(symbolIteratorType)))
3369+
.isFunctionTypeThat()
3370+
.isAbstract();
3371+
}
3372+
33413373
@Test
33423374
public void testClassDeclarationWithDucplicatePrototypeMethodAfterward() {
33433375
// Given

0 commit comments

Comments
 (0)