Skip to content

Commit aa3f740

Browse files
lauraharkercopybara-github
authored andcommitted
Improve BanUnresolvedType conformance error message
The conformance error will contain the actual name of the forward declared type rather than the string "NoResolvedType" in most cases. There are some remaining cases where the error still says "NoResolvedType". Fixing those would require tweaking the "greatest common subtype" & "least common supertype" operations on NoResolvedTypes, which is probably tricky. PiperOrigin-RevId: 483981189
1 parent 56ae527 commit aa3f740

File tree

3 files changed

+74
-10
lines changed

3 files changed

+74
-10
lines changed

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1712,7 +1712,7 @@ protected ConformanceResult checkConformance(NodeTraversal t, Node n) {
17121712
if (n.isGetProp()) {
17131713
Node target = n.getFirstChild();
17141714
JSType type = target.getJSType();
1715-
JSType nonConformingPart = getNonConformingPart(type);
1715+
String nonConformingPart = getNonConformingPart(type);
17161716
if (nonConformingPart != null && !isTypeImmediatelyTightened(n)) {
17171717
return new ConformanceResult(
17181718
ConformanceLevel.VIOLATION,
@@ -1722,21 +1722,28 @@ protected ConformanceResult checkConformance(NodeTraversal t, Node n) {
17221722
return ConformanceResult.CONFORMANCE;
17231723
}
17241724

1725-
private static @Nullable JSType getNonConformingPart(JSType type) {
1725+
private static @Nullable String getNonConformingPart(JSType type) {
17261726
if (type == null) {
17271727
return null;
17281728
}
17291729
if (type.isUnionType()) {
17301730
// unwrap union types which might contain unresolved type name
17311731
// references for example {Foo|undefined}
17321732
for (JSType part : type.getUnionMembers()) {
1733-
JSType nonConformingPart = getNonConformingPart(part);
1733+
String nonConformingPart = getNonConformingPart(part);
17341734
if (nonConformingPart != null) {
17351735
return nonConformingPart;
17361736
}
17371737
}
17381738
} else if (type.isNoResolvedType()) {
1739-
return type;
1739+
ObjectType noResolvedType = type.toObjectType();
1740+
// Whenever possible, return the 'reference name' of the NoResolvedType (i.e. the exact
1741+
// forward declared name). Some NoResolvedTypes do not have reference names because they
1742+
// were created as the product of an operation on NoResolvedTypes, e.g. consider
1743+
// 'noResolvedTypeA.getGreatestSubtype(noResolvedTypeB)'
1744+
return noResolvedType.getReferenceName() != null
1745+
? noResolvedType.getReferenceName()
1746+
: "NoResolvedType";
17401747
}
17411748
return null;
17421749
}
@@ -1751,7 +1758,7 @@ public StrictBanUnresolvedType(AbstractCompiler compiler, Requirement requiremen
17511758

17521759
@Override
17531760
protected ConformanceResult checkConformance(NodeTraversal t, Node n) {
1754-
JSType nonConformingPart = BanUnresolvedType.getNonConformingPart(n.getJSType());
1761+
String nonConformingPart = BanUnresolvedType.getNonConformingPart(n.getJSType());
17551762
if (nonConformingPart != null && !isTypeImmediatelyTightened(n)) {
17561763
return new ConformanceResult(
17571764
ConformanceLevel.VIOLATION,

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,12 +1168,15 @@ static JSType getGreatestSubtype(JSType thisType, JSType thatType) {
11681168
}
11691169

11701170
/**
1171-
* When computing infima, we may get a situation like
1172-
* inf(Type1, Type2)
1173-
* where both types are unresolved, so they're technically
1174-
* subtypes of one another.
1171+
* When computing infima, we may get a situation like inf(Type1, Type2) where both types are
1172+
* unresolved, so they're technically subtypes of one another.
11751173
*
1176-
* If this happens, filter them down to NoResolvedType.
1174+
* <p>If this happens, filter them down to NoResolvedType.
1175+
*
1176+
* <p>TODO(lharker): this filtering sometimes leads to conformance errors with unhelpful error
1177+
* messages: "Unfulfilled forward declaration 'NoResolvedType'". Is there something else we could
1178+
* do to preserve both type names, e.g. a NoResolvedType with the string name "Type1|Type2"?
1179+
* Related issue: b/112425334.
11771180
*/
11781181
static JSType filterNoResolvedType(JSType type) {
11791182
if (type.isNoResolvedType()) {

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2726,6 +2726,25 @@ public void testCustomBanUnresolvedType() {
27262726
CheckConformance.CONFORMANCE_VIOLATION,
27272727
"Violation: BanUnresolvedType Message\nReference to type 'Foo' never resolved.");
27282728

2729+
testWarning(
2730+
"goog.forwardDeclare('Foo'); /** @type {Foo} */ var f = makeFoo(); f.foo();",
2731+
CheckConformance.CONFORMANCE_VIOLATION,
2732+
"Violation: BanUnresolvedType Message\nReference to type 'Foo' never resolved.");
2733+
2734+
test(
2735+
srcs(
2736+
lines(
2737+
"goog.forwardDeclare('Foo');",
2738+
"goog.forwardDeclare('Bar');",
2739+
"/** @param {?Foo|Bar} foobar */",
2740+
"function f(foobar) {",
2741+
" return foobar.m();",
2742+
"}")),
2743+
warning(CheckConformance.CONFORMANCE_VIOLATION)
2744+
.withMessage(
2745+
// TODO(lharker): instead of 'Foo' could we print something like 'Foo|Bar'?
2746+
"Violation: BanUnresolvedType Message\nReference to type 'Foo' never resolved."));
2747+
27292748
testNoWarning(
27302749
lines(
27312750
"/**",
@@ -2760,6 +2779,41 @@ public void testCustomStrictBanUnresolvedType() {
27602779
"goog.forwardDeclare('Foo'); /** @return {!Foo} */ var f;", //
27612780
"f();"),
27622781
CheckConformance.CONFORMANCE_VIOLATION);
2782+
2783+
test(
2784+
srcs(
2785+
lines(
2786+
"goog.forwardDeclare('Foo');",
2787+
"goog.forwardDeclare('Bar');",
2788+
"/**",
2789+
" * @param {?Foo} foo",
2790+
" * @param {?Bar} bar",
2791+
" */",
2792+
"function f(foo, bar) {",
2793+
" return foo || bar;",
2794+
"}")),
2795+
warning(CheckConformance.CONFORMANCE_VIOLATION)
2796+
.withMessage(
2797+
"Violation: StrictBanUnresolvedType Message\n"
2798+
+ "Reference to type 'Foo' never resolved."),
2799+
warning(CheckConformance.CONFORMANCE_VIOLATION)
2800+
.withMessage(
2801+
"Violation: StrictBanUnresolvedType Message\n"
2802+
+ "Reference to type 'Bar' never resolved."),
2803+
warning(CheckConformance.CONFORMANCE_VIOLATION)
2804+
.withMessage(
2805+
"Violation: StrictBanUnresolvedType Message\n"
2806+
+ "Reference to type 'Foo' never resolved."),
2807+
warning(CheckConformance.CONFORMANCE_VIOLATION)
2808+
.withMessage(
2809+
// TODO(lharker): instead of 'NoResolvedType', could we print something like
2810+
// 'Foo|Bar' ? We'd need to modify anything calling JSType.filterNoResolvedType.
2811+
"Violation: StrictBanUnresolvedType Message\n"
2812+
+ "Reference to type 'NoResolvedType' never resolved."),
2813+
warning(CheckConformance.CONFORMANCE_VIOLATION)
2814+
.withMessage(
2815+
"Violation: StrictBanUnresolvedType Message\n"
2816+
+ "Reference to type 'Bar' never resolved."));
27632817
}
27642818

27652819
@Test

0 commit comments

Comments
 (0)