diff --git a/src/com/google/javascript/jscomp/TypedScope.java b/src/com/google/javascript/jscomp/TypedScope.java index 351aaa47347..7bf3fc8beca 100644 --- a/src/com/google/javascript/jscomp/TypedScope.java +++ b/src/com/google/javascript/jscomp/TypedScope.java @@ -21,12 +21,10 @@ import com.google.common.collect.Iterables; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; -import com.google.javascript.rhino.QualifiedName; import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.ObjectType; import com.google.javascript.rhino.jstype.StaticTypedScope; import com.google.javascript.rhino.jstype.StaticTypedSlot; -import javax.annotation.Nullable; /** * TypedScope contains information about variables and their types. Scopes can be nested, a scope @@ -190,28 +188,4 @@ public JSDocInfo getJsdocOfTypeDeclaration(String typeName) { StaticTypedSlot slot = getSlot(typeName); return slot == null ? null : slot.getJSDocInfo(); } - - /** - * Looks up a given qualified name in the scope. if that fails, looks up the component properties - * off of any owner types that are in scope. - * - *

This is always more or equally expensive as calling getVar(String name), so should only be - * used when necessary. - */ - @Nullable - public JSType lookupQualifiedName(QualifiedName qname) { - TypedVar slot = getVar(qname.join()); - if (slot != null && !slot.isTypeInferred()) { - JSType type = slot.getType(); - if (type != null && !type.isUnknownType()) { - return type; - } - } else if (!qname.isSimple()) { - JSType type = lookupQualifiedName(qname.getOwner()); - if (type != null) { - return type.findPropertyType(qname.getComponent()); - } - } - return null; - } } diff --git a/src/com/google/javascript/rhino/jstype/JSTypeRegistry.java b/src/com/google/javascript/rhino/jstype/JSTypeRegistry.java index a8a712cf157..734b9dfe484 100644 --- a/src/com/google/javascript/rhino/jstype/JSTypeRegistry.java +++ b/src/com/google/javascript/rhino/jstype/JSTypeRegistry.java @@ -65,6 +65,7 @@ import com.google.javascript.rhino.JSTypeExpression; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.PMap; +import com.google.javascript.rhino.QualifiedName; import com.google.javascript.rhino.SimpleErrorReporter; import com.google.javascript.rhino.StaticScope; import com.google.javascript.rhino.StaticSlot; @@ -2023,21 +2024,21 @@ private JSType createFromTypeNodesInternal( case TYPEOF: { String name = n.getFirstChild().getString(); - StaticTypedSlot slot = scope.getSlot(name); - if (slot == null) { - reporter.warning("Not in scope: " + name, sourceName, n.getLineno(), n.getCharno()); - return getNativeType(UNKNOWN_TYPE); - } // TODO(sdh): require var to be const? - JSType type = slot.getType(); - if (type == null) { - reporter.warning("No type for: " + name, sourceName, n.getLineno(), n.getCharno()); + JSType type = scope.lookupQualifiedName(QualifiedName.of(name)); + if (type == null || type.isUnknownType()) { + reporter.warning( + "Missing type for `typeof` value. The value must be declared and const.", + sourceName, + n.getLineno(), + n.getCharno()); return getNativeType(UNKNOWN_TYPE); } if (type.isLiteralObject()) { + JSType scopeType = type; type = createNamedType(scope, "typeof " + name, sourceName, n.getLineno(), n.getCharno()); - ((NamedType) type).setReferencedType(slot.getType()); + ((NamedType) type).setReferencedType(scopeType); } return type; } diff --git a/src/com/google/javascript/rhino/jstype/StaticTypedScope.java b/src/com/google/javascript/rhino/jstype/StaticTypedScope.java index fd7153d83b0..bf18dcc0297 100644 --- a/src/com/google/javascript/rhino/jstype/StaticTypedScope.java +++ b/src/com/google/javascript/rhino/jstype/StaticTypedScope.java @@ -39,7 +39,9 @@ package com.google.javascript.rhino.jstype; +import com.google.javascript.rhino.QualifiedName; import com.google.javascript.rhino.StaticScope; +import javax.annotation.Nullable; /** * The {@code StaticTypedScope} interface must be implemented by any object that defines variables @@ -68,4 +70,28 @@ public interface StaticTypedScope extends StaticScope { /** Returns the expected type of {@code this} in the current scope. */ JSType getTypeOfThis(); + + /** + * Looks up a given qualified name in the scope. if that fails, looks up the component properties + * off of any owner types that are in scope. + * + *

This is always more or equally expensive as calling getSlot(String name), so should only be + * used when necessary. + */ + @Nullable + default JSType lookupQualifiedName(QualifiedName qname) { + StaticTypedSlot slot = getSlot(qname.join()); + if (slot != null && !slot.isTypeInferred()) { + JSType type = slot.getType(); + if (type != null && !type.isUnknownType()) { + return type; + } + } else if (!qname.isSimple()) { + JSType type = lookupQualifiedName(qname.getOwner()); + if (type != null) { + return type.findPropertyType(qname.getComponent()); + } + } + return null; + } } diff --git a/test/com/google/javascript/jscomp/TypeCheckTest.java b/test/com/google/javascript/jscomp/TypeCheckTest.java index 28ac7661c9e..bbf3c6facdd 100644 --- a/test/com/google/javascript/jscomp/TypeCheckTest.java +++ b/test/com/google/javascript/jscomp/TypeCheckTest.java @@ -22289,7 +22289,9 @@ public void testRefinedTypeInNestedShortCircuitingAndOr() { @Test public void testTypeofType_notInScope() { - testTypes("var /** typeof ns */ x;", "Parse error. Not in scope: ns"); + testTypes( + "var /** typeof ns */ x;", + "Parse error. Missing type for `typeof` value. The value must be declared and const."); } @Test @@ -22445,6 +22447,38 @@ public void testTypeofType_namespacedTypeNameResolves() { "required: ns1.Foo")); } + @Test + public void testTypeofType_unknownType() { + testTypes( + lines("var /** ? */ x;", "/** @type {typeof x} */ var y;"), + "Parse error. Missing type for `typeof` value. The value must be declared and const."); + } + + @Test + public void testTypeofType_namespacedTypeOnIndirectAccessNameResolves() { + testTypes( + lines( + "/** @const */ var ns1 = {};", + "/** @constructor */ ns1.Foo = function() {};", + "const ns2 = ns1;", + // Verify this works although we have never seen any assignments to `ns2.Foo` + "/** @const {typeof ns2.Foo} */ var Foo2 = /** @type {?} */ (x);", + "/** @type {null} */ var x = new Foo2();"), + lines( + "initializing variable", // + "found : ns1.Foo", + "required: null")); + } + + @Test + public void testTypeofType_namespacedTypeMissing() { + testTypes( + lines( + "/** @const */ var ns = {};", + "/** @const {typeof ns.Foo} */ var Foo = /** @type {?} */ (x);"), + "Parse error. Missing type for `typeof` value. The value must be declared and const."); + } + @Test public void testTypeofType_withGlobalDeclaredVariableWithHoistedFunction() { // TODO(sdh): fix this. This is another form of problems with partially constructed scopes. @@ -22457,7 +22491,8 @@ public void testTypeofType_withGlobalDeclaredVariableWithHoistedFunction() { "function g(/** typeof x */ a) {}", "x = 'str';", "g(null);"), - lines("Parse error. Not in scope: x")); + lines( + "Parse error. Missing type for `typeof` value. The value must be declared and const.")); } @Test @@ -22483,7 +22518,8 @@ public void testTypeofType_withGlobalInferredVariableWithFunctionExpression() { "var g = function (/** typeof x */ a) {}", "x = 'str';", "g(null);"), - lines("Parse error. No type for: x")); + lines( + "Parse error. Missing type for `typeof` value. The value must be declared and const.")); } @Test @@ -22499,7 +22535,8 @@ public void testTypeofType_withLocalInferredVariableInHoistedFunction() { " x = 'str';", " g(null);", "}"), - lines("Parse error. Not in scope: x")); + lines( + "Parse error. Missing type for `typeof` value. The value must be declared and const.")); } @Test @@ -22516,7 +22553,8 @@ public void testTypeofType_withLocalDeclaredVariableWithHoistedFunction() { " x = 'str';", " g(null);", "}"), - lines("Parse error. Not in scope: x")); + lines( + "Parse error. Missing type for `typeof` value. The value must be declared and const.")); } @Test @@ -22529,7 +22567,8 @@ public void testTypeofType_withLocalInferredVariableWithFunctionExpression() { " x = 'str';", " g(null);", "}"), - lines("Parse error. No type for: x")); + lines( + "Parse error. Missing type for `typeof` value. The value must be declared and const.")); } @Test @@ -22559,7 +22598,8 @@ public void testTypeofType_withLocalInferredVariable() { " var g = null", " x = 'str';", "}"), - lines("Parse error. No type for: x")); + lines( + "Parse error. Missing type for `typeof` value. The value must be declared and const.")); } @Test