/* * Copyright 2013 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 com.google.javascript.jscomp.newtypes.JSTypeCreatorFromJSDoc; /** * Tests new type inference behavior for syntax it understands without transpilation. * * @author blickly@google.com (Ben Lickly) * @author dimvar@google.com (Dimitris Vardoulakis) */ public final class NewTypeInferenceTest extends NewTypeInferenceTestBase { public void testExterns() { typeCheck( "/** @param {Array<string>} x */ function f(x) {}; f([5]);", NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testVarDefinitionsInExterns() { typeCheckCustomExterns( DEFAULT_EXTERNS + "var undecl = {};", "if (undecl) { undecl.x = 7 };"); typeCheckCustomExterns( DEFAULT_EXTERNS + "var undecl = {};", "function f() { if (undecl) { undecl.x = 7 }; }"); typeCheckCustomExterns(DEFAULT_EXTERNS + "var undecl;", "undecl(5);"); typeCheckCustomExterns( DEFAULT_EXTERNS + "/** @type {number} */ var num;", "num - 5;"); typeCheckCustomExterns( DEFAULT_EXTERNS + "var maybeStr; /** @type {string} */ var maybeStr;", "maybeStr - 5;", NewTypeInference.INVALID_OPERAND_TYPE); typeCheckCustomExterns( DEFAULT_EXTERNS + "/** @type {string} */ var str;", "str - 5;", NewTypeInference.INVALID_OPERAND_TYPE); // TODO(blickly): Warn if function in externs has body typeCheckCustomExterns( DEFAULT_EXTERNS + "function f() {/** @type {string} */ var invisible;}", "invisible - 5;"); // VarCheck.UNDEFINED_VAR_ERROR); typeCheckCustomExterns( DEFAULT_EXTERNS + "/** @type {number} */ var num;", "/** @type {undefined} */ var x = num;", NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheckCustomExterns( DEFAULT_EXTERNS + "var untypedNum;", LINE_JOINER.join( "function f(x) {", " x < untypedNum;", " untypedNum - 5;", "}", "f('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testThisInAtTypeFunction() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){};", "/** @type {number} */ Foo.prototype.n;", "/** @type {function(this:Foo)} */ function f() { this.n = 'str' };"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck( "/** @type {function(this:gibberish)} */ function foo() {}", GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "var /** function(this:Foo) */ x = function() {};")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @param {function(this:Foo)} x */", "function f(x) {}", "f(/** @type {function(this:Foo)} */ (function() {}));")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() { /** @type {number} */ this.prop = 1; }", "/** @type {function(this:Foo)} */", "function f() { this.prop = 'asdf'; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {}", "/** @param {function(this:Foo)} x */", "function f(x) {}", "f(/** @type {function(this:Bar)} */ (function() {}));"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function High() {}", "/** @constructor @extends {High} */", "function Low() {}", "function f(/** function(this:Low) */ low,", " /** function(this:High) */ high) {", " var fun = (1 < 2) ? low : high;", " var /** function(this:High) */ f2 = fun;", " var /** function(this:Low) */ f3 = fun;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function High() {}", "/** @constructor @extends {High} */", "function Low() {}", "function f(/** function(function(this:Low)) */ low,", " /** function(function(this:High)) */ high) {", " var fun = (1 < 2) ? low : high;", " var /** function(function(this:High)) */ f2 = fun;", " var /** function(function(this:Low)) */ f3 = fun;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " * @param {T} x", " */", "function Foo(x) {}", "/**", " * @template T", " * @param {function(this:Foo<T>)} fun", " */", "function f(fun) { return fun; }", "var /** function(this:Foo<string>) */ x =", " f(/** @type {function(this:Foo<number>)} */ (function() {}));"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testThisInFunctionJsdoc() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {};", "/** @type {number} */ Foo.prototype.n;", "/** @this {Foo} */", "function f() { this.n = 'str'; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck( "/** @this {gibberish} */ function foo() {}", GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() { /** @type {number} */ this.prop = 1; }", "/** @this {Foo} */", "function f() { this.prop = 'asdf'; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); } // TODO(dimvar): we must warn when a THIS fun isn't called as a method public void testDontCallMethodAsFunction() { typeCheck(LINE_JOINER.join( "/** @type{function(this: Object)} */", "function f() {}", "f();")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prototype.method = function() {};", "var f = (new Foo).method;", "f();")); } public void testNewInFunctionJsdoc() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "function h(/** function(new:Foo, ...number):number */ f) {", " (new f()) - 5;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " * @param {T} x", " */", "function Foo(x) {}", "/**", " * @template T", " * @param {function(new:Foo<T>)} fun", " */", "function f(fun) { return fun; }", "/** @type {function(new:Foo<number>)} */", "function f2() {}", "var /** function(new:Foo<string>) */ x = f(f2);"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f(x) {", " x();", " var /** !Foo */ y = new x();", " var /** function(new:Foo, number) */ z = x;", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @type {number} */", "Foo.num = 123;", "function f(/** function(new:Foo, string) */ x) {", " var /** string */ s = x.num;", "}"), NewTypeInference.INEXISTENT_PROPERTY); } public void testAlhpaRenamingDoesntChangeType() { typeCheck(LINE_JOINER.join( "/**", " * @param {U} x", " * @param {U} y", " * @template U", " */", "function f(x, y){}", "/**", " * @template T", " * @param {function(T, T): boolean} comp", " * @param {!Array<T>} arr", " */", "function g(comp, arr) {", " var compare = comp || f;", " compare(arr[0], arr[1]);", "}")); } public void testInvalidThisReference() { typeCheck("this.x = 5;", NewTypeInference.GLOBAL_THIS); typeCheck("function f(x){}; f(this);"); typeCheck("function f(){ return this; }"); typeCheck("function f() { this.p = 1; }", NewTypeInference.GLOBAL_THIS); typeCheck("function f() { return this.toString; }", NewTypeInference.GLOBAL_THIS); typeCheck("function f() { this['toString']; }", NewTypeInference.GLOBAL_THIS); typeCheck("(function() { this.toString; })();", NewTypeInference.GLOBAL_THIS); typeCheck(LINE_JOINER.join( "function g(x) {}", "g(function() { return this.toString; })")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(x) {}", "new Foo(function() { return this.toString; })")); } public void testUnusualThisReference() { typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @this {T}", " */", "function f(x) {", " this.p = 123;", "}"), NewTypeInference.ADDING_PROPERTY_TO_NON_OBJECT); typeCheck(LINE_JOINER.join( "/** @this {Object} */", "function f(pname) {", " var x = this[pname];", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/**", " * @template T", " * @this {T}", " * @return {T}", " */", "function f() { return this; }", "var /** !Foo */ x = f.call(new Foo);")); } public void testSuperClassWithUndeclaredProps() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Error() {};", "Error.prototype.sourceURL;", "/** @constructor @extends {Error} */ function SyntaxError() {}")); } public void testInheritMethodFromParent() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {};", "/** @param {string} x */ Foo.prototype.method = function(x) {};", "/** @constructor @extends {Foo} */ function Bar() {};", "(new Bar).method(4)"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testSubClassWithUndeclaredProps() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Super() {};", "/** @type {string} */ Super.prototype.str;", "/** @constructor @extends {Super} */ function Sub() {};", "Sub.prototype.str;")); } public void testUseBeforeDeclaration() { typeCheck(LINE_JOINER.join( "function f() { return 9; }", "var x = f();", "x - 7;")); } public void testDeclaredVariables() { typeCheck("var /** null */ obj = 5;", NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck( "var /** ?number */ n = true;", NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testEmptyBlockPropagation() { typeCheck( "var x = 5; { }; var /** string */ s = x", NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testForLoopInference() { typeCheck(LINE_JOINER.join( "var x = 5;", "for (;x < 10;) {", " x = 'str';", "}", "var /** (string|number) */ y = x;", "(function(/** string */ s){})(x);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "var x = 5;", "while (x < 10) {", " x = 'str';", "}", "(function(/** string */ s){})(x);", "var /** (string|number) */ y = x;"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "while (true || false) {", " var x = 'str';", "}", "var /** (string|undefined) */ y = x;", "(function(/** string */ s){})(x);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "for (var x = 5; x < 10; x++) {}", "(function(/** string */ s){})(x);", "var /** number */ y = x;"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testConditionalSpecialization() { typeCheck(LINE_JOINER.join( "var x, y = 5;", "if (true) {", " x = 5;", "} else {", " x = 'str';", "}", "if (x === 5) {", " y = x;", "}", "y - 5")); typeCheck(LINE_JOINER.join( "var x, y = 5;", "if (true) {", " x = 5;", "} else {", " x = null;", "}", "if (x !== null) {", " y = x;", "}", "y - 5")); typeCheck(LINE_JOINER.join( "var x, y;", "if (true) {", " x = 5;", "} else {", " x = null;", "}", "if (x === null) {", " y = 5;", "} else {", " y = x;", "}", "y - 5")); typeCheck(LINE_JOINER.join( "var numOrNull = true ? null : 1", "if (null === numOrNull) { var /** null */ n = numOrNull; }")); typeCheck(LINE_JOINER.join( "function f(/** !Object */ x) {", " if (!x.prop) {", " return;", " }", " return x.prop + 123;", "}")); } public void testUnspecializedStrictComparisons() { typeCheck( "var /** number */ n = (1 === 2);", NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testAndOrConditionalSpecialization() { typeCheck(LINE_JOINER.join( "var x, y = 5;", "if (true) {", " x = 5;", "} else if (true) {", " x = null;", "}", "if (x !== null && x !== undefined) {", " y = x;", "}", "y - 5")); typeCheck(LINE_JOINER.join( "var x, y;", "if (true) {", " x = 5;", "} else if (true) {", " x = null;", "}", "if (x === null || x === void 0) {", " y = 5;", "} else {", " y = x;", "}", "y - 5")); typeCheck(LINE_JOINER.join( "var x, y = 5;", "if (true) {", " x = 5;", "} else if (true) {", " x = null;", "}", "if (x === null || x === undefined) {", " y = x;", "}", "var /** (number|null|undefined) **/ z = y;", "(function(/** (number|null) */ x){})(y);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "var x, y;", "if (true) {", " x = 5;", "} else if (true) {", " x = null;", "}", "if (x !== null && x !== undefined) {", " y = 5;", "} else {", " y = x;", "}", "var /** (number|null|undefined) **/ z = y;", "(function(/** (number|null) */ x){})(y);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "var x, y = 5;", "if (true) {", " x = 5;", "} else {", " x = 'str';", "}", "if (x === 7 || x === 8) {", " y = x;", "}", "y - 5")); typeCheck(LINE_JOINER.join( "/** @constructor */ function C(){}", "var obj = new C;", "if (obj || false) { 123, obj.asdf; }"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "function f(/** (number|string) */ x) {", " (typeof x === 'number') && (x - 5);", "}")); typeCheck(LINE_JOINER.join( "function f(/** (number|string|null) */ x) {", " (x && (typeof x === 'number')) && (x - 5);", "}")); typeCheck(LINE_JOINER.join( "function f(/** (number|string|null) */ x) {", " (x && (typeof x === 'string')) && (x - 5);", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(/** (number|string|null) */ x) {", " typeof x === 'string' && x;", " x < 'asdf';", "}")); typeCheck(LINE_JOINER.join( "function f(/** (null|number) */ x, /** (null|number) */ y) {", " if (x == y) {", " return x - 1;", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(/** (null|number) */ x, /** (null|number) */ y) {", " if (x == y) {", " return y - 1;", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck("var /** boolean */ x = true || 123;"); typeCheck("var /** number */ x = undefined || 123;"); typeCheck("var /** null */ x = null && 123;"); typeCheck("var /** number */ x = { a: 1 } && 123;"); typeCheck(LINE_JOINER.join( "function f(/** Object|undefined */ opt_obj) {", " var x = opt_obj && 'asdf';", " if (opt_obj && x in opt_obj) {}", "}"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testLoopConditionSpecialization() { typeCheck(LINE_JOINER.join( "var x = true ? null : 'str';", "while (x !== null) {}", "var /** null */ y = x;")); typeCheck(LINE_JOINER.join( "var x = true ? null : 'str';", "for (;x !== null;) {}", "var /** null */ y = x;")); typeCheck(LINE_JOINER.join( "for (var x = true ? null : 'str'; x === null;) {}", "var /** string */ y = x;")); typeCheck(LINE_JOINER.join( "var x;", "for (x = true ? null : 'str'; x === null;) {}", "var /** string */ y = x;")); typeCheck(LINE_JOINER.join( "var x = true ? null : 'str';", "do {} while (x === null);", "var /** string */ y = x;")); } public void testVarDecls() { typeCheck( "/** @type {number} */ var x, y;", GlobalTypeInfo.ONE_TYPE_FOR_MANY_VARS); typeCheck( "var /** number */ x = 5, /** string */ y = 6;", NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck( "var /** number */ x = 'str', /** string */ y = 'str2';", NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testBadInitialization() { typeCheck( "/** @type {string} */ var s = 123;", NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testBadAssignment() { typeCheck( "/** @type {string} */ var s; s = 123;", NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testBadArithmetic() { typeCheck("123 - 'str';", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck("123 * 'str';", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck("123 / 'str';", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck("123 % 'str';", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck( "var y = 123; var x = 'str'; var z = x - y;", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck( "var y = 123; var x; var z = x - y;", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck("+true;"); // This is considered an explicit coercion typeCheck("true + 5;", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck("5 + true;", NewTypeInference.INVALID_OPERAND_TYPE); } public void testTypeAfterIF() { typeCheck( "var x = true ? 1 : 'str'; x - 1;", NewTypeInference.INVALID_OPERAND_TYPE); } public void testSimpleBwdPropagation() { typeCheck(LINE_JOINER.join( "function f(x) { x - 5; }", "f(123);", "f('asdf')"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x) { x++; }", "f(123);", "f('asdf')"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(y) { var x = y; x - 5; }", "f(123);", "f('asdf')"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(y) { var x; x = y; x - 5; }", "f(123);", "f('asdf')"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x) { x + 5; }", "f(123);", "f('asdf')")); } public void testSimpleReturn() { typeCheck(LINE_JOINER.join( "function f(x) {}", "var /** undefined */ x = f();", "var /** number */ y = f();"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(x) { return; }", "var /** undefined */ x = f();", "var /** number */ y = f();"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(x) { return 123; }", "var /** undefined */ x = f();", "var /** number */ y = f();"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(x) { if (x) {return 123;} else {return 'asdf';} }", "var /** (string|number) */ x = f();")); typeCheck(LINE_JOINER.join( "function f(x) { if (x) {return 123;} }", "var /** (undefined|number) */ x = f();")); typeCheck(LINE_JOINER.join( "function f(x) { var y = x; y - 5; return x; }", "var /** undefined */ x = f(1);", "var /** number */ y = f(2);"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testComparisons() { typeCheck( "1 < 0; 'a' < 'b'; true < false; null < null; undefined < undefined;"); typeCheck( "/** @param {{ p1: ?, p2: ? }} x */ function f(x) { x.p1 < x.p2; }"); typeCheck("function f(x, y) { x < y; }"); typeCheck( "var x = 1; var y = true ? 1 : 'str'; x < y;"); typeCheck( "var x = 'str'; var y = 1; x < y;", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " var y = 1;", " x < y;", " return x;", "}", "var /** undefined */ x = f(1);", "var /** number */ y = f(2);"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(x) {", " var y = x, z = 7;", " y < z;", "}")); typeCheck("new Date(1) > new Date(0);"); } public void testFunctionJsdoc() { typeCheck(LINE_JOINER.join( "/** @param {number} n */", "function f(n) { n < 5; }")); typeCheck(LINE_JOINER.join( "/** @param {string} n */", "function f(n) { n < 5; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @return {string} */", "function f() { return 1; }"), NewTypeInference.RETURN_NONDECLARED_TYPE); typeCheck(LINE_JOINER.join( "/** @return {string} */", "function f() { return; }"), NewTypeInference.RETURN_NONDECLARED_TYPE); typeCheck(LINE_JOINER.join( "/** @return {string} */", "function f(s) { return s; }", "f(123);", "f('asdf')"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @return {number} */", "function f() {}"), NewTypeInference.MISSING_RETURN_STATEMENT); typeCheck(LINE_JOINER.join( "/** @return {(undefined|number)} */", "function f() { if (true) { return 'str'; } }"), NewTypeInference.RETURN_NONDECLARED_TYPE); typeCheck(LINE_JOINER.join( "/** @param {function(number)} fun */", "function f(fun) {}", "f(function (/** string */ s) {});"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck( "/** @param {number} n */ function f(/** number */ n) {}", JSTypeCreatorFromJSDoc.TWO_JSDOCS); typeCheck("/** @constructor */ var Foo = function() {}; new Foo;"); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @param {number} x */ Foo.prototype.method = function(x) {};", "(new Foo).method('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "Foo.prototype.method = /** @param {number} x */ function(x) {};", "(new Foo).method('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "Foo.prototype.method = function(/** number */ x) {};", "(new Foo).method('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck( "/** @type {function(number)} */ function f(x) {}; f('asdf');", NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck( "/** @type {number} */ function f() {}", JSTypeCreatorFromJSDoc.FUNCTION_WITH_NONFUNC_JSDOC); typeCheck(LINE_JOINER.join( "/** @type {function():number} */", "function /** number */ f() { return 1; }")); typeCheck(LINE_JOINER.join( "function f(/** function(number) */ fnum, floose, cond) {", " var y;", " if (cond) {", " y = fnum;", " } else {", " floose();", " y = floose;", " }", " return y;", "}")); typeCheck(LINE_JOINER.join( "/** @param {function(): *} x */ function g(x) {}", "/** @param {function(number): string} x */ function f(x) {", " g(x);", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck( "var x = {}; x.a = function(/** string */ x) {}; x.a(123);", NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck("/** @param {function(...)} x */ function f(x) {}"); typeCheck(LINE_JOINER.join( "/**", " * @interface", " */", "function A() {};", "/** @return {number} */", "A.prototype.foo = function() {};")); typeCheck( "/** @param {number} x */ function f(y) {}", GlobalTypeInfo.INEXISTENT_PARAM); } public void testFunctionSubtyping() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {}", "function f(/** function(new:Foo) */ x) {}", "f(Bar);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f(/** function(new:Foo) */ x) {}", "f(function() {});"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor @extends {Foo} */", "function Bar() {}", "function f(/** function(new:Foo) */ x) {}", "f(Bar);")); } public void testFunctionJoin() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/**", " * @param {function(new:Foo, (number|string))} x ", " * @param {function(new:Foo, number)} y ", " */", "function f(x, y) {", " var z = 1 < 2 ? x : y;", " return new z(123);", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {}", "/**", " * @param {function(new:Foo)} x ", " * @param {function(new:Bar)} y ", " */", "function f(x, y) {", " var z = 1 < 2 ? x : y;", " return new z();", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f(/** function(new:Foo) */ x, /** function() */ y) {", " var z = 1 < 2 ? x : y;", " return new z();", "}"), NewTypeInference.NOT_A_CONSTRUCTOR); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f(/** function(new:Foo) */ x, /** function() */ y) {", " var z = 1 < 2 ? x : y;", " return z();", "}")); } public void testFunctionMeet() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/**", " * @param {function(new:Foo, (number|string))} x ", " * @param {function(new:Foo, number)} y ", " */", "function f(x, y) { if (x === y) { return x; } }")); } public void testRecordWithoutTypesJsdoc() { typeCheck(LINE_JOINER.join( "function f(/** {a, b} */ x) {}", "f({c: 123});"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testBackwardForwardPathologicalCase() { typeCheck(LINE_JOINER.join( "function f(x) { var y = 5; y < x; }", "f(123);", "f('asdf')"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testTopInitialization() { typeCheck("function f(x) { var y = x; y < 5; }"); typeCheck("function f(x) { x < 5; }"); typeCheck( "function f(x) { x - 5; x < 'str'; }", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " var y = x; y - 5; y < 'str';", "}"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testSimpleCalls() { typeCheck("function f() {}; f(5);", NewTypeInference.WRONG_ARGUMENT_COUNT); typeCheck("function f(x) { x-5; }; f();", NewTypeInference.WRONG_ARGUMENT_COUNT); typeCheck(LINE_JOINER.join( "/** @return {number} */ function f() { return 1; }", "var /** string */ s = f();"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck( "function f(/** number */ x) {}; f(true);", NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(/** boolean */ x) {}", "function g() { f(123); }"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(/** void */ x) {}", "function g() { f(123); }"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(/** boolean */ x) {}", "function g(x) {", " var /** string */ s = x;", " f(x < 7);", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(/** number */ x) {}", "function g(x, y) {", " y < x;", " f(x);", " var /** string */ s = y;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testObjectType() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "function takesObj(/** Object */ x) {}", "takesObj(new Foo);")); typeCheck(LINE_JOINER.join( "function takesObj(/** Object */ x) {}", "takesObj(null);")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "function /** Object */ returnsObj() { return {}; }", "function takesFoo(/** Foo */ x) {}", "takesFoo(returnsObj());"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck("Object.prototype.hasOwnProperty.call({}, 'asdf');"); } public void testCallsWithComplexOperator() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @constructor */ function Bar() {}", "function fun(cond, /** !Foo */ f, /** !Bar */ g) {", " (cond ? f : g)();", "}"), NewTypeInference.NOT_CALLABLE); } public void testDeferredChecks() { typeCheck(LINE_JOINER.join( "function f() { return 'str'; }", "function g() { f() - 5; }"), NewTypeInference.INVALID_INFERRED_RETURN_TYPE); typeCheck(LINE_JOINER.join( "function f(x) { x - 5; }", "f(5 < 6);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x, y) { x - y; }", "f(5);"), NewTypeInference.WRONG_ARGUMENT_COUNT); typeCheck(LINE_JOINER.join( "function f() { return 'str'; }", "function g() { var x = f(); x - 7; }"), NewTypeInference.INVALID_INFERRED_RETURN_TYPE); typeCheck(LINE_JOINER.join( "function f(/** number */ x, y) { return x-y; }", "f(5, 'str');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @return {number} */ function f(x) { return x; }", "f('str');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(/** number */ x) { return x; }", "function g(x) {", " var /** string */ s = f(x);", "};"), NewTypeInference.INVALID_INFERRED_RETURN_TYPE); typeCheck(LINE_JOINER.join( "function f() { new Foo('asdf'); }", "/** @constructor */ function Foo(x) { x - 5; }"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Arr() {}", "/**", " * @template T", " * @param {...T} var_args", " */", "Arr.prototype.push = function(var_args) {};", "function f(x) {}", "var renameByParts = function(parts) {", " var mapped = new Arr();", " mapped.push(f(parts));", "};")); // Here we don't want a deferred check and an INVALID_INFERRED_RETURN_TYPE // warning b/c the return type is declared. typeCheck(LINE_JOINER.join( "/** @return {string} */ function foo(){ return 'str'; }", "function g() { foo() - 123; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f() {", " function x() {};", " function g() { x(1); }", " g();", "}"), NewTypeInference.WRONG_ARGUMENT_COUNT); // We used to erroneously create a deferred check for the call to f // (and crash as a result), because we had a bug where the top-level // function was not being shadowed by the formal parameter. typeCheck(LINE_JOINER.join( "function f() { return 123; }", "var outer = 123;", "function g(/** function(number) */ f) {", " f(123) < 'str';", " return outer;", "}")); // TODO(dimvar): Do deferred checks for known functions that are properties. // typeCheck(LINE_JOINER.join( // "/** @const */ var ns = {};", // "ns.f = function(x) { return x - 1; };", // "function g() { ns.f('asdf'); }"), // NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testShadowing() { typeCheck(LINE_JOINER.join( "var /** number */ x = 5;", "function f() {", " var /** string */ x = 'str';", " return x - 5;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "var /** number */ x = 5;", "function f() {", " /** @typedef {string} */ var x;", " return x - 5;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "var /** number */ x = 5;", "function f() {", " /** @enum {string} */ var x = { FOO : 'str' };", " return x - 5;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); // Types that are only present in types, and not in code do not cause shadowing in code typeCheck(LINE_JOINER.join( "var /** number */ X = 5;", "/** @template X */", "function f() {", " return X - 5;", "}")); typeCheck(LINE_JOINER.join( "var /** string */ X = 'str';", "/** @template X */", "function f() {", " return X - 5;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testTypedefIsUndefined() { typeCheck(LINE_JOINER.join( "function f() {", " /** @typedef {string} */ var x;", " /** @type {undefined} */ var y = x;", "}")); } public void testFunctionsInsideFunctions() { typeCheck(LINE_JOINER.join( "(function() {", " function f() {}; f(5);", "})();"), NewTypeInference.WRONG_ARGUMENT_COUNT); typeCheck(LINE_JOINER.join( "(function() {", " function f() { return 'str'; }", " function g() { f() - 5; }", "})();"), NewTypeInference.INVALID_INFERRED_RETURN_TYPE); typeCheck(LINE_JOINER.join( "var /** number */ x;", "function f() { x = 'str'; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "var x;", "function f() { x - 5; x < 'str'; }"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testCrossScopeWarnings() { typeCheck(LINE_JOINER.join( "function f() {", " x < 'str';", "}", "var x = 5;", "f()"), NewTypeInference.CROSS_SCOPE_GOTCHA); typeCheck(LINE_JOINER.join( "var x;", "function f() {", " return x - 1;", "}", "f()"), NewTypeInference.CROSS_SCOPE_GOTCHA); typeCheck(LINE_JOINER.join( "function f(y) {", " var x;", " y(function() { return x - 1; });", "}")); typeCheck(LINE_JOINER.join( "function f() {", " x = 'str';", "}", "var x = 5;", "f()")); typeCheck(LINE_JOINER.join( "function f() {", " var x;", " function g() { x = 123; }", " g();", " return x - 1;", "}")); // Missing the warning because x is used in g, even though g doesn't change // its type. typeCheck(LINE_JOINER.join( "function f(x) {", " function g() { return x; }", " var /** number */ n = x;", " g();", " var /** string */ s = x;", "}")); // CROSS_SCOPE_GOTCHA is only for undeclared variables typeCheck(LINE_JOINER.join( "/** @type {string} */ var s;", "function f() {", " s = 123;", "}", "f();"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(g) {", " var x;", " g(function() { return x - 123; });", " return /** @type {number} */ (x) - 1;", "}")); typeCheck(LINE_JOINER.join( "function f(x) {", " function g(y) {", " y(function() { return x - 1; });", " }", "}")); // Spurious warning because we only know the type of x at the beginning of // f, which is string. This is a contrived example though; not sure how // important it is in practice to record postconditions in FunctionType. typeCheck(LINE_JOINER.join( "function g(x) {", " function f() {", " var /** string */ s = x;", " x = 5;", " }", " f();", " x - 5;", "}"), NewTypeInference.CROSS_SCOPE_GOTCHA, NewTypeInference.INVALID_OPERAND_TYPE); // Spurious warning, for the same reason as the previous test. // This test is trickier, so it avoids the CROSS_SCOPE_GOTCHA warning. typeCheck(LINE_JOINER.join( "function g(x) {", " function f() {", " var /** string */ s = x;", " x = 5;", " }", " var z = x;", " f();", " x - 5;", " var /** string */ y = z;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @type {string} */", "Foo.prototype.prefix;", "Foo.prototype.method = function() {", " var x = this;", " return function() {", " if (x.prefix.length) {", " return 123;", " }", " };", "};")); typeCheck(LINE_JOINER.join( "function f(x) {", " function g(condition) {", " if (condition) {", " return 'early';", " }", " return function() { return x.toString; };", " }", "}")); // TODO(dimvar): it'd be nice to catch this warning typeCheck(LINE_JOINER.join( "function f(x) {", " function g(condition) {", " if (condition) {", " return 'early';", " }", " (function() { return x - 1; })();", " }", " g(false);", " var /** string */ s = x;", "}")); typeCheck(LINE_JOINER.join( "var x;", "function f(cond) {", " if (cond) {", " return;", " }", " return function g() {", " return function w() { x };", " };", "}")); // TODO(dimvar): we can't do this yet; requires more info in the summary // typeCheck(LINE_JOINER.join( // "/** @constructor */", // "function Foo() {", // " /** @type{?Object} */ this.prop = null;", // "}", // "Foo.prototype.initProp = function() { this.prop = {}; };", // "var obj = new Foo();", // "if (obj.prop == null) {", // " obj.initProp();", // " obj.prop.a = 123;", // "}")); } public void testTrickyUnknownBehavior() { typeCheck(LINE_JOINER.join( "function f(/** function() */ x, cond) {", " var y = cond ? x() : 5;", " y < 'str';", "}")); typeCheck(LINE_JOINER.join( "/** @param {function() : ?} x */ function f(x, cond) {", " var y = cond ? x() : 5;", " y < 'str';", "}")); typeCheck(LINE_JOINER.join( "function f(/** function() */ x) {", " x() < 'str';", "}")); typeCheck(LINE_JOINER.join( "Object.prototype.y;", "function g() { return {}; }", "function f() {", " var /** ? */ x = g();", " return x.y;", "}")); typeCheck(LINE_JOINER.join( "function g() { return {}; }", "function f() {", " var /** ? */ x = g()", " x.y = 5;", "}")); typeCheck(LINE_JOINER.join( "Object.prototype.y1;", "function g(x) { return x; }", "function f(z) {", " var /** ? */ x = g(z);", " x.y2 = 123;", // specializing to a loose object here " return x.y1 - 5;", "}")); } public void testDeclaredFunctionTypesInFormals() { typeCheck(LINE_JOINER.join( "function f(/** function():number */ x) {", " var /** string */ s = x();", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** function(number) */ x) {", " x(true);", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function g(x, y, /** function(number) */ f) {", " y < x;", " f(x);", " var /** string */ s = y;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " var y = x(); y - 5; y < 'str';", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @param {function():?} x */ function f(x) {", " var y = x(); y - 5; y < 'str';", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck( "function f(/** ? */ x) { x < 'asdf'; x - 5; }", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @param {function(number): string} x */ function g(x) {}", "/** @param {function(number): string} x */ function f(x) {", " g(x);", "}")); typeCheck(LINE_JOINER.join( "/** @param {function(number): *} x */ function g(x) {}", "/** @param {function(*): string} x */ function f(x) {", " g(x);", "}")); typeCheck(LINE_JOINER.join( "/** @param {function(*): string} x */ function g(x) {}", "/** @param {function(number): string} x */ function f(x) {", " g(x);", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @param {function(number): string} x */ function g(x) {}", "/** @param {function(number): *} x */ function f(x) {", " g(x);", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testSpecializedFunctions() { typeCheck(LINE_JOINER.join( "function f(/** function(string) : number */ x) {", " if (x('str') === 5) {", " x(5);", " }", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(/** function(string) : string */ x) {", " if (x('str') === 5) {", " x(5);", " }", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE, NewTypeInference.INCOMPATIBLE_STRICT_COMPARISON); typeCheck(LINE_JOINER.join( "function f(/** function(string) */ x, y) {", " y(1);", " if (x === y) {", " x(5);", " }", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " if (x === null) {", " return 5;", " } else {", " return x - 43;", " }", "}", "f('str');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @const */ var goog = {};", "/** @type {!Function} */ goog.abstractMethod = function(){};", "/** @constructor */ function Foo(){};", "/** @return {!Foo} */ Foo.prototype.clone = goog.abstractMethod;", "/** @constructor @extends {Foo} */", "function Bar() {}", "/** @return {!Bar} */ Bar.prototype.clone = goog.abstractMethod;")); typeCheck(LINE_JOINER.join( "/** @const */ var goog = {};", "/** @type {!Function} */ goog.abstractMethod = function(){};", "/** @constructor */ function Foo(){};", "/** @return {!Foo} */ Foo.prototype.clone = goog.abstractMethod;", "/** @constructor @extends {Foo} */", "function Bar() {}", "/** @return {!Bar} */ Bar.prototype.clone = goog.abstractMethod;", "var /** null */ n = (new Bar).clone();"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */", "function Foo() {}", "/** @type {function(number)} */", "Foo.prototype.m = goog.nullFunction;", "/** @enum {function(string)} */", "var e = {", " A: goog.nullFunction", "};")); typeCheck(LINE_JOINER.join( "function f() {}", "/** @type {function(number)} */", "var g = f;", "/** @type {function(string)} */", "var h = f;")); } public void testDifficultObjectSpecialization() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function X() { this.p = 1; }", "/** @constructor */", "function Y() { this.p = 2; }", "/** @param {(!X|!Y)} a */", "function fn(a) {", " a.p;", " /** @type {!X} */ (a);", "}")); // Currently, two types that have a common subtype specialize to bottom // instead of to the common subtype. If we change that, then this test will // have no warnings. typeCheck(LINE_JOINER.join( "/** @interface */", "function High1() {}", "/** @interface */", "function High2() {}", "/**", " * @constructor", " * @implements {High1}", " * @implements {High2}", " */", "function Low() {}", "function f(x) {", " var /** !High1 */ v1 = x;", " var /** !High2 */ v2 = x;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); // Currently, two types that have a common subtype specialize to bottom // instead of to the common subtype. If we change that, then this test will // have no warnings, and the type of x will be !Low. // (We must normalize the output of specialize to avoid getting (!Med|!Low)) typeCheck(LINE_JOINER.join( "/** @interface */", "function High1() {}", "/** @interface */", "function High2() {}", "/** @interface */", "function High3() {}", "/**", " * @interface", " * @extends {High1}", " * @extends {High2}", " */", "function Mid() {}", "/**", " * @interface", " * @extends {Mid}", " * @extends {High3}", " */", "function Low() {}", "function f(x) {", " var /** !High1 */ v1 = x;", " var /** (!High2|!High3) */ v2 = x;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "Object.prototype.prop2;", "/** @constructor */", "function Foo() {}", "/** @param {!Foo} x */", "function f(x) {", " if (x.prop1) {", " x.prop1.prop2 += 1234;", " }", "}")); typeCheck(LINE_JOINER.join( "Object.prototype.prop3;", "/** @constructor */", "function Foo() {}", "/** @param {!Foo} x */", "function f(x) {", " if (x.prop1 && x.prop1.prop2) {", " x.prop1.prop3 += 1234;", " }", "}")); } public void testLooseConstructors() { typeCheck(LINE_JOINER.join( "function f(ctor) {", " new ctor(1);", "}")); typeCheck(LINE_JOINER.join( "function f(ctor) {", " new ctor(1);", "}", "/** @constructor */ function Foo(/** string */ y) {}", "f(Foo);"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testLooseFunctions() { typeCheck(LINE_JOINER.join( "function f(x) {", " x(1);", "}")); typeCheck(LINE_JOINER.join( "function f(x) {", " x(1);", "}", "function g(/** string */ y) {}", "f(g);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " x(1);", "}", "function g(/** number */ y) {}", "f(g);")); typeCheck(LINE_JOINER.join( "function f(x) {", " x(1);", "}", "function g(/** (number|string) */ y) {}", "f(g);")); typeCheck(LINE_JOINER.join( "function f(x) {", " 5 - x(1);", "}", "/** @return {string} */", "function g(/** number */ y) { return ''; }", "f(g);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " 5 - x(1);", "}", "/** @return {(number|string)} */", "function g(/** number */ y) { return 5; }", "f(g);")); typeCheck(LINE_JOINER.join( "function f(x, y) {", " x(5);", " y(5);", " return x(y);", "}")); typeCheck(LINE_JOINER.join( "function f(x) {", " x();", " return x;", "}", "function g() {}", "function h() { f(g) - 5; }"), NewTypeInference.INVALID_INFERRED_RETURN_TYPE); typeCheck(LINE_JOINER.join( "function f(x, cond) {", " x();", " return cond ? 5 : x;", "}", "function g() {}", "function h() { f(g, true) - 5; }"), NewTypeInference.INVALID_INFERRED_RETURN_TYPE); // A loose function is a loose subtype of a non-loose function. // Traditional function subtyping would warn here. typeCheck(LINE_JOINER.join( "function f(x) {", " x(5);", " return x;", "}", "function g(x) {}", "function h() {", " var /** function((number|string)) */ fun = f(g);", "}")); typeCheck(LINE_JOINER.join( "function g(/** string */ x) {}", "function f(x, y) {", " y - 5;", " x(y);", " y + y;", "}", "f(g, 5)"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @return {string} */", "function g(/** number */ x) { return 'str'; }", "/** @return {number} */", "function f(x) {", " var y = 5;", " var z = x(y);", " return z;", "}", "f(g)"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @return {number} */", "function g(/** number */ y) { return 6; }", "function f(x, cond) {", " if (cond) {", " 5 - x(1);", " } else {", " x('str') < 'str';", " }", "}", "f(g, true)")); typeCheck(LINE_JOINER.join( "function f(g, cond) {", " if (cond) {", " g(5, cond);", " }", "}")); typeCheck(LINE_JOINER.join( "/**", " * @param {function (number)|Function} x", " */", "function f(x) {};", "f(function () {});")); typeCheck(LINE_JOINER.join( "function f(x) {", " x(true, 'asdf');", " x(false);", "}")); typeCheck(LINE_JOINER.join( "function f(x) {", " var y = x();", " var /** (number|string) */ w = y;", " var /** number */ z = y;", "}")); typeCheck(LINE_JOINER.join( "var foo;", "(function() { foo(); })();", "foo();"), NewTypeInference.NULLABLE_DEREFERENCE); } public void testBackwardForwardPathologicalCase2() { typeCheck(LINE_JOINER.join( "function f(/** number */ x, /** string */ y, z) {", " var w = z;", " x < z;", " w < y;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testNotCallable() { typeCheck(LINE_JOINER.join( "/** @param {number} x */ function f(x) {", " x(7);", "}"), NewTypeInference.NOT_CALLABLE); } public void testSimpleLocallyDefinedFunction() { typeCheck(LINE_JOINER.join( "function f() { return 'str'; }", "var x = f();", "x - 7;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f() { return 'str'; }", "f() - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "(function() {", " function f() { return 'str'; }", " f() - 5;", "})();"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "(function() {", " function f() { return 'str'; }", " f() - 5;", "})();"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testIdentityFunction() { typeCheck(LINE_JOINER.join( "function f(x) { return x; }", "5 - f(1);")); } public void testReturnTypeInferred() { typeCheck(LINE_JOINER.join( "function f() {", " var x = g();", " var /** string */ s = x;", " x - 5;", "};", "function g() { return 'str'};"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testGetpropOnNonObjects() { typeCheck("(null).foo;", NewTypeInference.PROPERTY_ACCESS_ON_NONOBJECT); typeCheck(LINE_JOINER.join( "var /** undefined */ n;", "n.foo;"), NewTypeInference.PROPERTY_ACCESS_ON_NONOBJECT); typeCheck("var x = {}; x.foo.bar = 1;", NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "var /** undefined */ n;", "n.foo = 5;"), NewTypeInference.ADDING_PROPERTY_TO_NON_OBJECT); typeCheck(LINE_JOINER.join( "/** @param {*} x */", "function f(x) {", " if (x.prop) {", " var /** { prop: ? } */ y = x;", " }", "}")); // TODO(blickly): Currently, this warning is not good, referring to props of // BOTTOM. Ideally, we could warn about accessing a prop on undefined. typeCheck(LINE_JOINER.join( "/** @param {undefined} x */", "function f(x) {", " if (x.prop) {}", "}"), NewTypeInference.PROPERTY_ACCESS_ON_NONOBJECT); typeCheck("null[123];", NewTypeInference.PROPERTY_ACCESS_ON_NONOBJECT); typeCheck( "function f(/** !Object */ x) { if (x[123]) { return 1; } }"); typeCheck( "function f(/** undefined */ x) { if (x[123]) { return 1; } }", NewTypeInference.PROPERTY_ACCESS_ON_NONOBJECT); typeCheck(LINE_JOINER.join( "function f(/** (number|null) */ n) {", " n.foo;", "}"), NewTypeInference.NULLABLE_DEREFERENCE); typeCheck(LINE_JOINER.join( "function f(/** (number|null|undefined) */ n) {", " n.foo;", "}"), NewTypeInference.NULLABLE_DEREFERENCE); typeCheck(LINE_JOINER.join( "function f(/** (!Object|number|null|undefined) */ n) {", " n.foo;", "}"), NewTypeInference.NULLABLE_DEREFERENCE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){}", "Foo.prototype.prop;", "function f(/** (!Foo|undefined) */ n) {", " n.prop;", "}"), NewTypeInference.NULLABLE_DEREFERENCE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){}", "/** @type {string} */ Foo.prototype.prop1;", "function g(/** Foo */ f) {", " f.prop1.prop2 = 'str';", "};"), NewTypeInference.ADDING_PROPERTY_TO_NON_OBJECT, NewTypeInference.NULLABLE_DEREFERENCE); } public void testNonexistentProperty() { typeCheck(LINE_JOINER.join( "/** @param {{ a: number }} obj */", "function f(obj) {", " 123, obj.b;", " obj.b = 'str';", "}"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck("({}).p < 'asdf';", NewTypeInference.INEXISTENT_PROPERTY); typeCheck( "(/** @type {?} */ (null)).prop - 123;", NewTypeInference.INEXISTENT_PROPERTY); typeCheck( LINE_JOINER.join( "Object.prototype.prop;", "(/** @type {?} */ (null)).prop += 123;")); typeCheck("var x = {}; var y = x.a;", NewTypeInference.INEXISTENT_PROPERTY); typeCheck("var x = {}; var y = x['a'];"); typeCheck("var x = {}; x.y - 3; x.y = 5;", NewTypeInference.INEXISTENT_PROPERTY); } public void testNullableDereference() { typeCheck( "function f(/** ?{ p : number } */ o) { return o.p; }", NewTypeInference.NULLABLE_DEREFERENCE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() { /** @const */ this.p = 5; }", "function g(/** ?Foo */ f) { return f.p; }"), NewTypeInference.NULLABLE_DEREFERENCE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "Foo.prototype.p = function(){};", "function g(/** ?Foo */ f) { f.p(); }"), NewTypeInference.NULLABLE_DEREFERENCE); typeCheck( "var f = 5 ? function() {} : null; f();", NewTypeInference.NULLABLE_DEREFERENCE); typeCheck( "var f = 5 ? function(/** number */ n) {} : null; f('str');", NewTypeInference.NULLABLE_DEREFERENCE, NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(/** ?{ p : number } */ o) {", " goog.asserts.assert(o);", " return o.p;", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(/** ?{ p : number } */ o) {", " goog.asserts.assertObject(o);", " return o.p;", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(/** ?Array<string> */ a) {", " goog.asserts.assertArray(a);", " return a.length;", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */ function Foo() {}", "Foo.prototype.p = function(){};", "function g(/** ?Foo */ f) {", " goog.asserts.assertInstanceof(f, Foo);", " f.p();", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */ function Foo() {}", "/** @constructor */ function Bar() {}", "function g(/** !Bar */ o) {", " goog.asserts.assertInstanceof(o, Foo);", "}"), NewTypeInference.ASSERT_FALSE); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */ function Foo() {}", "function g(/** !Foo */ o) {", " goog.asserts.assertInstanceof(o, 42);", "}"), NewTypeInference.UNKNOWN_ASSERTION_TYPE); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */ function Foo() {}", "function Bar() {}", "function g(/** !Foo */ o) {", " goog.asserts.assertInstanceof(o, Bar);", "}"), NewTypeInference.UNKNOWN_ASSERTION_TYPE); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */ function Foo() {}", "/** @interface */ function Bar() {}", "function g(/** !Foo */ o) {", " goog.asserts.assertInstanceof(o, Bar);", "}"), NewTypeInference.UNKNOWN_ASSERTION_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @param {Foo} x */", "function f(x) {}", "f(new Foo);")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){ this.prop = 123; }", "function f(/** Foo */ obj) { obj.prop; }"), NewTypeInference.NULLABLE_DEREFERENCE); typeCheck(LINE_JOINER.join( "/** @interface */", "function I() {}", "I.prototype.method = function() {};", "/** @param {I} x */", "function foo(x) { x.method(); }"), NewTypeInference.NULLABLE_DEREFERENCE); } public void testAsserts() { typeCheck( LINE_JOINER.join( CLOSURE_BASE, "function f(/** ({ p : string }|null|undefined) */ o) {", " goog.asserts.assert(o);", " o.p - 5;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */ function Foo() {}", "function f(/** (Array<string>|Foo) */ o) {", " goog.asserts.assert(o instanceof Array);", " var /** string */ s = o.length;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */ function Foo() {}", "Foo.prototype.p = function(/** number */ x){};", "function f(/** (function(new:Foo)) */ ctor,", " /** ?Foo */ o) {", " goog.asserts.assertInstanceof(o, ctor);", " o.p('str');", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/**", " * @template T", " * @param {T} x", " */", "function f(x) {", " var y = x;", " goog.asserts.assertInstanceof(y, Foo);", "}")); typeCheck(LINE_JOINER.join( "function f(/** { p: (number|null) } */ x) {", " goog.asserts.assertNumber(x.p) - 1;", "}")); typeCheck(LINE_JOINER.join( "function f(/** function():(number|null) */ x) {", " goog.asserts.assertNumber(x()) - 1;", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {}", "/** @return {?Foo} */", "Bar.prototype.method = function() { return null; };", "var /** !Foo */ x = goog.asserts.assertInstanceOf((new Bar).method(), Foo);")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f() {", " var /** string */ s;", " s = goog.asserts.assertString(/** @type {number|string} */ (123));", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f() {", " var /** string */ s;", " s = goog.asserts.assertString(123);", "}"), NewTypeInference.ASSERT_FALSE); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "goog.forwardDeclare('ns.Foo');", "function f(x) {", " return goog.asserts.assertInstanceof(x, ns.Foo);", "}"), NewTypeInference.UNKNOWN_ASSERTION_TYPE); typeCheck("goog.asserts.assert(false, 'this code should not run');"); } public void testDontInferBottom() { typeCheck( // Ensure we don't infer bottom for x here "function f(x) { var /** string */ s; (s = x) - 5; } f(9);", NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testDontInferBottomReturn() { typeCheck( // Technically, BOTTOM is correct here, but since using dead code is error prone, // we'd rather infer f to return TOP (and get a warning). "function f() { throw ''; } f() - 5;", NewTypeInference.INVALID_OPERAND_TYPE); } public void testAssignToInvalidObject() { typeCheck( "n.foo = 5; var n;", // VariableReferenceCheck.EARLY_REFERENCE, NewTypeInference.ADDING_PROPERTY_TO_NON_OBJECT); } public void testAssignmentDoesntFlowWrongInit() { typeCheck(LINE_JOINER.join( "function f(/** number */ n) {", " n = 'typo';", " n - 5;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @param {{ n: number }} x */ function f(x) {", " x.n = 'typo';", " x.n - 5;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testPossiblyNonexistentProperties() { typeCheck(LINE_JOINER.join( "/** @param {{ n: number }} x */ function f(x) {", " if (x.p) {", " return x.p;", " }", "}")); typeCheck(LINE_JOINER.join( "/** @param {{ p : string }} x */ function reqHasPropP(x){}", "/** @param {{ n: number }} x */ function f(x, cond) {", " if (cond) {", " x.p = 'str';", " }", " reqHasPropP(x);", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @param {{ n: number }} x */ function f(x, cond) {", " if (cond) { x.p = 'str'; }", " if (x.p) {", " x.p - 5;", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(/** { n : number } */ x) {", " x.s = 'str';", " return x.inexistentProp;", "}"), NewTypeInference.INEXISTENT_PROPERTY); } public void testDeclaredRecordTypes() { typeCheck(LINE_JOINER.join( "/** @param {{ p: number }} x */ function f(x) {", " return x.p - 3;", "}")); typeCheck(LINE_JOINER.join( "/** @param {{ p: string }} x */ function f(x) {", " return x.p - 3;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @param {{ 'p': string }} x */ function f(x) {", " return x.p - 3;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @param {{ p: number }} x */ function f(x) {", " return x.q;", "}"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @param {{ p: string }} obj */ function f(obj, x, y) {", " x < y;", " x - 5;", " obj.p < y;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @param {{ p: number }} x */ function f(x) {", " x.p = 3;", "}")); typeCheck(LINE_JOINER.join( "/** @param {{ p: number }} x */ function f(x) {", " x.p = 'str';", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @param {{ p: number }} x */ function f(x) {", " x.q = 'str';", "}")); typeCheck(LINE_JOINER.join( "/** @param {{ p: number }} x */ function f(x) {", " x.q = 'str';", "}", "/** @param {{ p: number }} x */ function g(x) {", " f(x);", "}")); typeCheck(LINE_JOINER.join( "/** @param {{ p: number }} x */ function f(x) {", " x.q = 'str';", " return x.q;", "}", "/** @param {{ p: number }} x */ function g(x) {", " f(x) - 5;", "}"), NewTypeInference.INVALID_INFERRED_RETURN_TYPE); typeCheck(LINE_JOINER.join( "/** @param {{ p: number }} x */ function f(x) {", " x.q = 'str';", " x.q = 7;", "}")); typeCheck(LINE_JOINER.join( "function f(/** { prop: number} */ obj) {", " obj.prop = 'asdf';", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** { prop: number} */ obj, cond) {", " if (cond) { obj.prop = 123; } else { obj.prop = 234; }", " obj.prop = 'asdf';", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** {p: number} */ x, /** {p: (number|null)} */ y) {", " var z;", " if (true) { z = x; } else { z = y; }", "}")); typeCheck(LINE_JOINER.join( "var /** { a: number } */ obj1 = { a: 321};", "var /** { a: number, b: number } */ obj2 = obj1;"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testSimpleObjectLiterals() { typeCheck(LINE_JOINER.join( "/** @param {{ p: number }} obj */", "function f(obj) {", " obj = { p: 123 };", "}")); typeCheck(LINE_JOINER.join( "/** @param {{ p: number, p2: string }} obj */", "function f(obj) {", " obj = { p: 123 };", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @param {{ p: number }} obj */", "function f(obj) {", " obj = { p: 'str' };", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "var obj;", "obj = { p: 123 };", "obj.p < 'str';"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @param {{ p: number }} obj */", "function f(obj, x) {", " obj = { p: x };", " x < 'str';", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @param {{ p: number }} obj */", "function f(obj, x) {", " obj = { p: 123, q: x };", " obj.q - 5;", " x < 'str';", "}"), NewTypeInference.INVALID_OPERAND_TYPE); // An example of how record types can hide away the extra properties and // allow type misuse. typeCheck(LINE_JOINER.join( "/** @param {{ p: number }} obj */", "function f(obj) {", " obj.q = 123;", "}", "/** @param {{ p: number, q: string }} obj */", "function g(obj) { f(obj); }")); typeCheck(LINE_JOINER.join( "/** @param {{ p: number }} obj */", "function f(obj) {}", "var obj = {p: 5};", "if (true) {", " obj.q = 123;", "}", "f(obj);")); typeCheck( "function f(/** number */ n) {}; f({});", NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testInferPreciseTypeWithDeclaredUnknown() { typeCheck( "var /** ? */ x = 'str'; x - 123;", NewTypeInference.INVALID_OPERAND_TYPE); } public void testSimpleLooseObjects() { typeCheck("function f(obj) { obj.x = 1; obj.x - 5; }"); typeCheck( "function f(obj) { obj.x = 'str'; obj.x - 5; }", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "Object.prototype.p;", "function f(obj) {", " var /** number */ x = obj.p;", " obj.p < 'str';", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "Object.prototype.p", "function f(obj) {", " var /** @type {{ p: number }} */ x = obj;", " obj.p < 'str';", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(obj) {", " obj.x = 1;", " return obj.x;", "}", "f({x: 'str'});")); typeCheck(LINE_JOINER.join( "function f(obj) {", " obj.x - 1;", "}", "f({x: 'str'});"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(obj, cond) {", " if (cond) {", " obj.x = 'str';", " }", " obj.x - 5;", "}")); typeCheck(LINE_JOINER.join( "function f(obj) {", " obj.x - 1;", " return obj;", "}", "var /** string */ s = (f({x: 5})).x;"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testNestedLooseObjects() { typeCheck(LINE_JOINER.join( "function f(obj) {", " obj.a.b = 123;", "}")); typeCheck(LINE_JOINER.join( "Object.prototype.a;", "function f(obj) {", " obj.a.b = 123;", " obj.a.b < 'str';", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(obj, cond) {", " (cond ? obj : obj).x - 1;", " return obj.x;", "}", "f({x: 'str'}, true);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(obj) {", " obj.a.b - 123;", "}", "f({a: {b: 'str'}})"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(obj) {", " obj.a.b = 123;", "}", "f({a: {b: 'str'}})")); typeCheck(LINE_JOINER.join( "function f(obj) {", " var o;", " (o = obj).x - 1;", " return o.x;", "}", "f({x: 'str'});"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(obj) {", " ({x: obj.foo}).x - 1;", "}", "f({foo: 'str'});"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " ({p: x++}).p = 'str';", "}", "f('str');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " ({p: 'str'}).p = x++;", "}", "f('str');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x, y, z) {", " ({p: (y = x++), q: 'str'}).p = z = y;", " z < 'str';", "}"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testLooseObjectSubtyping() { typeCheck(LINE_JOINER.join( "Object.prototype.prop;", "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {}", "function f(obj) { obj.prop - 5; }", "var /** !Foo */ x = new Foo;", "f(x);", "var /** !Bar */ y = x;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "Object.prototype.prop;", "/** @constructor */ function Foo() {}", "function f(obj) { obj.prop - 5; }", "f(new Foo);")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() { /** @type {string} */ this.prop = 'str'; }", "function f(obj) { obj.prop - 5; }", "f(new Foo);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() { /** @type {number} */ this.prop = 1; }", "function g(obj) { var /** string */ s = obj.prop; return obj; }", "var /** !Foo */ x = g({ prop: '' });"), NewTypeInference.MISTYPED_ASSIGN_RHS); // Infer obj.a as loose, don't warn at the call to f. typeCheck(LINE_JOINER.join( "Object.prototype.a;", "Object.prototype.num;", "Object.prototype.str;", "function f(obj) { obj.a.num - 5; }", "function g(obj) {", " obj.a.str < 'str';", " f(obj);", "}")); // A loose object is a subtype of Array even if it has a dotted property typeCheck(LINE_JOINER.join( "function f(/** Array<?> */ x) {}", "function g(obj) {", " obj.x = 123;", " f(obj);", "}")); typeCheck(LINE_JOINER.join( "function f(g) {", " if (g.randomName) {", " } else {", " return g();", " }", "}")); typeCheck(LINE_JOINER.join( "function f(x) {", " if (x.a) {} else {}", "}", "f({ b: 123 }); ")); // TODO(dimvar): We could warn about this since x is callable and we're // passing a non-function, but we don't catch it for now. typeCheck(LINE_JOINER.join( "function f(x) {", " if (x.randomName) {", " } else {", " return x();", " }", "}", "f({ abc: 123 }); ")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function UnrestrictedClass() {}", "function f(x) {", " x.someprop = 123;", "}", "function g(x) {", " return x.someprop - 1;", "}", "/** @type {function(!UnrestrictedClass)} */", "var z = g;")); } public void testUnionOfRecords() { // The previous type inference doesn't warn because it keeps records // separate in unions. // We treat {x:number}|{y:number} as {x:number=, y:number=} typeCheck(LINE_JOINER.join( "/** @param {({x:number}|{y:number})} obj */", "function f(obj) {}", "f({x: 5, y: 'asdf'});"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testUnionOfFunctionAndNumber() { typeCheck("var x = function(/** number */ y){};"); // typeCheck("var x = function(/** number */ y){}; var x = 5", // VariableReferenceCheck.REDECLARED_VARIABLE); typeCheck( "var x = function(/** number */ y){}; x('str');", NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck( "var x = true ? function(/** number */ y){} : 5; x('str');", NewTypeInference.NOT_CALLABLE); } public void testAnonymousNominalType() { typeCheck(LINE_JOINER.join( "function f() { return {}; }", "/** @constructor */", "f().Foo = function() {};"), GlobalTypeInfo.ANONYMOUS_NOMINAL_TYPE); typeCheck(LINE_JOINER.join( "Object.prototype.Foo;", "var x = {};", "function f() { return x; }", "/** @constructor */", "f().Foo = function() {};", "new (f().Foo)();"), GlobalTypeInfo.ANONYMOUS_NOMINAL_TYPE); } public void testFoo() { typeCheck( "/** @constructor */ function Foo() {}; Foo();", NewTypeInference.CONSTRUCTOR_NOT_CALLABLE); typeCheck( "function Foo() {}; new Foo();", NewTypeInference.NOT_A_CONSTRUCTOR); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {};", "function reqFoo(/** Foo */ f) {};", "reqFoo(new Foo());")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {};", "/** @constructor */ function Bar() {};", "function reqFoo(/** Foo */ f) {};", "reqFoo(new Bar());"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {};", "function reqFoo(/** Foo */ f) {};", "function g() {", " /** @constructor */ function Foo() {};", " reqFoo(new Foo());", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @param {number} x */", "Foo.prototype.method = function(x) {};", "/** @param {!Foo} x */", "function f(x) { x.method('asdf'); }"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testComma() { typeCheck( "var x; var /** string */ s = (x = 1, x);", NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(x) {", " var y = x;", " y < (123, 'asdf');", "}", "f(123);"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testTypeof() { typeCheck("(typeof 'asdf') < 123;", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " var y = x;", " y < (typeof 123);", "}", "f(123);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " if (typeof x === 'string') {", " x - 5;", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " if (typeof x != 'function') {", " x - 5;", " }", "}")); typeCheck(LINE_JOINER.join( "function f(x) {", " if (typeof x == 'string') {", " x - 5;", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " if ('string' === typeof x) {", " x - 5;", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " if (typeof x === 'number') {", " x < 'asdf';", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " if (typeof x === 'boolean') {", " x - 5;", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " if (typeof x === 'undefined') {", " x - 5;", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " if (typeof x === 'function') {", " x - 5;", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @param {*} x */", "function f(x) {", " if (typeof x === 'function') {", " x();", " }", "}")); typeCheck(LINE_JOINER.join( "function f(x) {", " if (typeof x === 'object') {", " x - 5;", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " if (!(typeof x == 'number')) {", " x.prop;", " }", "}")); typeCheck(LINE_JOINER.join( "function f(x) {", " if (!(typeof x == 'undefined')) {", " x - 5;", " }", "}")); typeCheck(LINE_JOINER.join( "/** @param {*} x */", "function f(x) {", " if (!(typeof x == 'undefined')) {", " var /** undefined */ y = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @param {*} x */", "function f(x) {", " if (typeof x !== 'undefined') {", " var /** undefined */ y = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @param {*} x */", "function f(x) {", " if (typeof x == 'undefined') {} else {", " var /** undefined */ y = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** (number|undefined) */ x) {", " if (typeof x !== 'undefined') {", " x - 5;", " }", "}")); typeCheck(LINE_JOINER.join( "function f() {", " return (typeof 123 == 'number' ||", " typeof 123 == 'string' ||", " typeof 123 == 'boolean' ||", " typeof 123 == 'undefined' ||", " typeof 123 == 'function' ||", " typeof 123 == 'object' ||", " typeof 123 == 'unknown');", "}")); typeCheck( "function f(){ if (typeof 123 == 'numbr') return 321; }", NewTypeInference.UNKNOWN_TYPEOF_VALUE); typeCheck( "switch (typeof 123) { case 'foo': }", NewTypeInference.UNKNOWN_TYPEOF_VALUE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @param {(number|null|Foo)} x */", "function f(x) {", " if (!(typeof x === 'object')) {", " var /** number */ n = x;", " }", "}")); typeCheck(LINE_JOINER.join( "/** @param {(number|function(number):number)} x */", "function f(x) {", " if (!(typeof x === 'function')) {", " var /** number */ n = x;", " }", "}")); typeCheck(LINE_JOINER.join( "function f(/** {prop: {prop2: (string|number)}} */ x) {", " if (typeof x.prop.prop2 === 'string') {", " } else if (typeof x.prop.prop2 === 'number') {", " }", "}")); typeCheck(LINE_JOINER.join( "function f(x) {", " if (typeof x === 'symbol') {", // We don't know what symbols are yet. " var /** null */ n = x;", " }", "}")); } public void testAssignWithOp() { typeCheck(LINE_JOINER.join( "function f(x) {", " var y = x, z = 0;", " y < (z -= 123);", "}", "f('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " var y = x, z = { prop: 0 };", " y < (z.prop -= 123);", "}", "f('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " var z = { prop: 0 };", " x < z.prop;", " z.prop -= 123;", "}", "f('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck("var x = 0; x *= 'asdf';", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck( "var /** string */ x = 'asdf'; x *= 123;", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck("var x; x *= 123;", NewTypeInference.INVALID_OPERAND_TYPE); } public void testClassConstructor() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {", " /** @type {number} */ this.n = 5;", "};", "(new Foo()).n - 5;")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {", " /** @type {number} */ this.n = 5;", "};", "(new Foo()).n = 'str';"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {", " /** @type {number} */ this.n;", "};", "(new Foo()).n = 'str';"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f() { (new Foo()).n = 'str'; }", "/** @constructor */ function Foo() {", " /** @type {number} */ this.n = 5;", "};"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f() { var x = new Foo(); x.n = 'str'; }", "/** @constructor */ function Foo() {", " /** @type {number} */ this.n = 5;", "};"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f() { var x = new Foo(); return x.n - 5; }", "/** @constructor */ function Foo() {", " this.n = 5;", "};")); typeCheck(LINE_JOINER.join( "function f() { var x = new Foo(); x.s = 'str'; x.s < x.n; }", "/** @constructor */ function Foo() {", " /** @type {number} */ this.n = 5;", "};"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {", " /** @type {number} */ this.n = 5;", "};", "function reqFoo(/** Foo */ x) {};", "reqFoo({ n : 20 });"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f() { var x = new Foo(); x.n - 5; x.n < 'str'; }", "/** @constructor */ function Foo() {", " this.n = 5;", "};"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testPropertyDeclarations() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {number} */ this.x = 'abc';", " /** @type {string} */ this.x = 'def';", "}"), GlobalTypeInfo.REDECLARED_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {number} */ this.x = 5;", " /** @type {number} */ this.x = 7;", "}"), GlobalTypeInfo.REDECLARED_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " this.x = 5;", " /** @type {number} */ this.x = 7;", "}", "function g() { (new Foo()).x < 'str'; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {number} */ this.x = 7;", " this.x = 5;", "}", "function g() { (new Foo()).x < 'str'; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {number} */ this.x = 7;", " this.x < 'str';", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {?} */ this.x = 1;", " /** @type {?} */ this.x = 1;", "}"), GlobalTypeInfo.REDECLARED_PROPERTY); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "ns.prop = function() {};", "function f() {", " ns.prop = function() {};", "}")); // When initializing a namespace property to a function without jsdoc, // do we consider that a definition of a function namespace? // If so, we warn here, if not, it would need a jsdoc to be a definition. // The first option seems more acceptable than the second. typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "ns.prop = function() {};", "function f() {", " ns.prop = 234;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " this.prop = function(x, y) {};", "}", "(new Foo).prop = 123;")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {?function(number)} */", " this.prop = function(x) {};", "}", "(new Foo).prop = null;")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " this.prop = function(/** number */ x) {};", "}", "(new Foo).prop = null;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {?function(number)} */", " this.prop = function(x) {};", "}", "(new Foo).prop = 5;"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testPrototypePropertyAssignments() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @type {string} */ Foo.prototype.x = 'str';", "function g() { (new Foo()).x - 5; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "Foo.prototype.x = 'str';", "function g() { var f = new Foo(); f.x - 5; f.x < 'str'; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @type {function(string)} s */", "Foo.prototype.bar = function(s) {};", "function g() { (new Foo()).bar(5); }"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {};", "Foo.prototype.bar = function(s) {", " /** @type {string} */ this.x = 'str';", "};", "(new Foo()).x - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "(function() { Foo.prototype.prop = 123; })();"), GlobalTypeInfo.CTOR_IN_DIFFERENT_SCOPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function F() {}", "F.prototype.bar = function() {};", "F.prototype.bar = function() {};"), GlobalTypeInfo.REDECLARED_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor */ function F() {}", "/** @return {void} */ F.prototype.bar = function() {};", "F.prototype.bar = function() {};"), GlobalTypeInfo.REDECLARED_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor */ function C(){}", "C.prototype.foo = {};", "C.prototype.method = function() { this.foo.bar = 123; }")); // TODO(dimvar): I think we can fix the next one with better deferred checks // for prototype methods. Look into it. // typeCheck(LINE_JOINER.join( // "/** @constructor */ function Foo() {};", // "Foo.prototype.bar = function(s) { s < 'asdf'; };", // "function g() { (new Foo()).bar(5); }"), // NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {};", "/** @param {string} s */ Foo.prototype.bar = function(s) {};", "function g() { (new Foo()).bar(5); }"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {};", "Foo.prototype.bar = function(/** string */ s) {};", "function g() { (new Foo()).bar(5); }"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f() {}", "function g() { f.prototype.prop = 123; }")); typeCheck(LINE_JOINER.join( "/** @param {!Function} f */", "function foo(f) { f.prototype.bar = function(x) {}; }")); } public void testPrototypeAssignment() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prototype = { a: 1, b: 2 };", "var x = (new Foo).a;")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prototype = { a: 1, b: 2 - 'asdf' };", "var x = (new Foo).a;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prototype = { a: 1, /** @const */ b: 2 };", "(new Foo).b = 3;"), NewTypeInference.CONST_PROPERTY_REASSIGNED); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prototype = { method: function(/** number */ x) {} };", "(new Foo).method('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prototype = { method: function(/** number */ x) {} };", "/** @constructor @extends {Foo} */", "function Bar() {}", "(new Bar).method('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(ns) {", " return ns.prototype = {", " method: function(x, y) {}", " };", "}")); } public void testAssignmentsToPrototype() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor @extends {Foo} */", "function Bar() {}", "Bar.prototype = new Foo;", "Bar.prototype.method1 = function() {};")); } public void testConflictingPropertyDefinitions() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() { this.x = 'str1'; };", "/** @type {string} */ Foo.prototype.x = 'str2';", "(new Foo).x - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @type {string} */ Foo.prototype.x = 'str1';", "Foo.prototype.x = 'str2';", "(new Foo).x - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "Foo.prototype.x = 'str2';", "/** @type {string} */ Foo.prototype.x = 'str1';", "(new Foo).x - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() { /** @type {string} */ this.x = 'str1'; };", "Foo.prototype.x = 'str2';", "(new Foo).x - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() { this.x = 5; };", "/** @type {string} */ Foo.prototype.x = 'str';"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() { /** @type {string} */ this.x = 'str1'; };", "Foo.prototype.x = 5;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() { /** @type {string} */ this.x = 'str'; };", "/** @type {number} */ Foo.prototype.x = 'str';"), GlobalTypeInfo.REDECLARED_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @type {number} */ Foo.prototype.x = 1;", "/** @type {number} */ Foo.prototype.x = 2;"), GlobalTypeInfo.REDECLARED_PROPERTY); } public void testPrototypeAliasing() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "Foo.prototype.x = 'str';", "var fp = Foo.prototype;", "fp.x - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testInstanceof() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "function takesFoos(/** Foo */ afoo) {}", "function f(/** (number|Foo) */ x) {", " takesFoos(x);", " if (x instanceof Foo) { takesFoos(x); }", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck( "({} instanceof function(){});", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "(123 instanceof Foo);"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "({} instanceof (true || Foo))"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "function takesFoos(/** Foo */ afoo) {}", "function f(/** (number|Foo) */ x) {", " if (x instanceof Foo) { takesFoos(x); }", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "function f(/** (number|!Foo) */ x) {", " if (x instanceof Foo) {} else { x - 5; }", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "function f(/** (number|!Foo) */ x) {", " if (!(x instanceof Foo)) { x - 5; }", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @constructor */ function Bar() {}", "function takesFoos(/** Foo */ afoo) {}", "function f(/** Foo */ x) {", " if (x instanceof Bar) {} else { takesFoos(x); }", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "function takesFoos(/** Foo */ afoo) {}", "/** @param {*} x */ function f(x) {", " takesFoos(x);", " if (x instanceof Foo) { takesFoos(x); }", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "var x = new Foo();", "x.bar = 'asdf';", "if (x instanceof Foo) { x.bar - 5; }"), NewTypeInference.INVALID_OPERAND_TYPE); // typeCheck( // "function f(x) { if (x instanceof UndefinedClass) {} }", // VarCheck.UNDEFINED_VAR_ERROR); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() { this.prop = 123; }", "function f(x) { x = 123; if (x instanceof Foo) { x.prop; } }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @constructor @extends {Foo} */ function Bar() {}", "/** @param {(number|!Bar)} x */", "function f(x) {", " if (!(x instanceof Foo)) {", " var /** number */ n = x;", " }", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @enum {!Foo} */", "var E = { ONE: new Foo };", "/** @param {(number|E)} x */", "function f(x) {", " if (!(x instanceof Foo)) {", " var /** number */ n = x;", " }", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/**", " * @template T", " * @param {T} x", " */", "function f(x) {", " if (x instanceof Foo) {", " var /** !Foo */ y = x;", " }", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {}", "/**", " * @template T", " * @param {T} x", " */", "function f(x) {", " if (x instanceof Foo) {", " var /** !Bar */ z = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {function(new:T)} x", " */", "function f(x, y) {", " if (y instanceof x) {}", "}")); typeCheck(LINE_JOINER.join( "/**", " * @param {*} value", " * @param {function(new: T, ...)} type", " * @template T", " */", "function assertInstanceof(value, type) {}", "/** @const */ var ctor = unresolvedGlobalVar;", "function f(obj) {", " if (obj instanceof ctor) {", " return assertInstanceof(obj, ctor);", " }", "}"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); typeCheck(LINE_JOINER.join( "function f(x, y) {", " x(123);", " if (y instanceof x) {", " return y;", " }", "}")); } public void testFunctionsExtendFunction() { typeCheck(LINE_JOINER.join( "function f(x) {", " if (x instanceof Function) { x(); }", "}")); typeCheck(LINE_JOINER.join( "function f(x) {", " if (x instanceof Function) { x(1); x('str') }", "}")); typeCheck(LINE_JOINER.join( "function f(/** (null|function()) */ x) {", " if (x instanceof Function) { x(); }", "}")); typeCheck(LINE_JOINER.join( "function f(/** (null|function()) */ x) {", " if (x instanceof Function) {} else { x(); }", "}"), NewTypeInference.NOT_CALLABLE); typeCheck("(function(){}).call(null);"); typeCheck(LINE_JOINER.join( "function greet(name) {}", "greet.call(null, 'bob');", "greet.apply(null, ['bob']);")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){}", "Foo.prototype.greet = function(name){};", "Foo.prototype.greet.call(new Foo, 'bob');")); typeCheck(LINE_JOINER.join( "Function.prototype.method = function(/** string */ x){};", "(function(){}).method(5);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(value) {", " if (value instanceof Function) {} else if (value instanceof Object) {", " return value.displayName || value.name || '';", " }", "};")); } public void testObjectsAreNotClassy() { typeCheck(LINE_JOINER.join( "Object.prototype.x;", "function g(obj) {", " if (!(obj instanceof Object)) { throw -1; }", " return obj.x - 5;", "}", "g(new Object);")); } public void testFunctionWithProps() { typeCheck(LINE_JOINER.join( "function f() {}", "f.x = 'asdf';", "f.x - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testConstructorProperties() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @type {number} */ Foo.n = 1", "/** @type {number} */ Foo.n = 1"), GlobalTypeInfo.REDECLARED_PROPERTY); typeCheck(LINE_JOINER.join( "function g() { Foo.bar - 5; }", "/** @constructor */ function Foo() {}", "Foo.bar = 42;")); typeCheck(LINE_JOINER.join( "function g() { Foo.bar - 5; }", "/** @constructor */ function Foo() {}", "/** @type {string} */ Foo.bar = 'str';"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function g() { return (new Foo).bar; }", "/** @constructor */ function Foo() {}", "/** @type {string} */ Foo.bar = 'str';"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @type {string} */ Foo.prop = 'asdf';", "var x = Foo;", "x.prop - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function g() { Foo.prototype.baz = (new Foo).bar + Foo.bar; }", "/** @constructor */ function Foo() {}", "/** @type {number} */ Foo.prototype.bar = 5", "/** @type {string} */ Foo.bar = 'str';"), GlobalTypeInfo.CTOR_IN_DIFFERENT_SCOPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @type {number} */ Foo.n = 1;", "Foo.n = 1;")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @type {number} */ Foo.n;", "Foo.n = '';"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testTypeTighteningHeuristic() { typeCheck( "/** @param {*} x */ function f(x) { var /** ? */ y = x; x - 5; }", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(/** ? */ x) {", " if (!(typeof x == 'number')) {", " x < 'asdf';", " }", "}")); typeCheck(LINE_JOINER.join( "function f(/** { prop: ? } */ x) {", " var /** (number|string) */ y = x.prop;", " x.prop < 5;", "}")); typeCheck(LINE_JOINER.join( "function f(/** (number|string) */ x, /** (number|string) */ y) {", " var z;", " if (1 < 2) {", " z = x;", " } else {", " z = y;", " }", " z - 5;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testDeclaredPropertyIndirectly() { typeCheck(LINE_JOINER.join( "function f(/** { n: number } */ obj) {", " var o2 = obj;", " o2.n = 'asdf';", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testNonRequiredArguments() { typeCheck(LINE_JOINER.join( "function f(f1, /** function(string=) */ f2, cond) {", " var y;", " if (cond) {", " f1();", " y = f1;", " } else {", " y = f2;", " }", " return y;", "}")); typeCheck(LINE_JOINER.join( "function f(/** ...number */ fnum) {}", "f(); f(1, 2, 3); f(1, 2, 'asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck( "function f(/** number= */ x, /** number */ y) {}", JSTypeCreatorFromJSDoc.WRONG_PARAMETER_ORDER); typeCheck(LINE_JOINER.join( "function f(/** number= */ x) {}", "f(); f('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(/** number= */ x) {}", "f(1, 2);"), NewTypeInference.WRONG_ARGUMENT_COUNT); typeCheck(LINE_JOINER.join( "/** @param {function(...number)} fnum */", "function f(fnum) {", " fnum(); fnum(1, 2, 3, 'asdf');", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @param {function(number=, number)} g */", "function f(g) {}"), JSTypeCreatorFromJSDoc.WRONG_PARAMETER_ORDER); typeCheck(LINE_JOINER.join( "/** @param {number=} x */", "function f(x) {}", "f(); f('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @param {number=} x */", "function f(x) {}", "f(1, 2);"), NewTypeInference.WRONG_ARGUMENT_COUNT); typeCheck( "/** @type {number|function()} */ function f(x) {}", GlobalTypeInfo.WRONG_PARAMETER_COUNT); typeCheck( "/** @type {number|function(number)} */ function f() {}", GlobalTypeInfo.WRONG_PARAMETER_COUNT); typeCheck( "/** @type {function(number)} */ function f(/** number */ x) {}"); typeCheck(LINE_JOINER.join( "/**", " * @param {number=} x", " * @param {number} y", " */", "function f(x, y) {}"), JSTypeCreatorFromJSDoc.WRONG_PARAMETER_ORDER); typeCheck(LINE_JOINER.join( "/** @type {function(number=)} */ function f(x) {}", "f(); f('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck( "/** @type {function(number=, number)} */ function f(x, y) {}", JSTypeCreatorFromJSDoc.WRONG_PARAMETER_ORDER); typeCheck( "function /** number */ f() { return 'asdf'; }", NewTypeInference.RETURN_NONDECLARED_TYPE); typeCheck( "/** @return {number} */ function /** number */ f() { return 1; }", JSTypeCreatorFromJSDoc.TWO_JSDOCS); typeCheck( "/** @type {function(): number} */ function /** number */ f() { return 1; }"); typeCheck(LINE_JOINER.join( "/** @type {function(...number)} */ function f() {}", "f(); f(1, 2, 3); f(1, 2, 'asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @param {...number} var_args */ function f(var_args) {}", "f(); f(1, 2, 3); f(1, 2, 'asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck( "/** @type {function(...number)} */ function f(x) {}"); typeCheck(LINE_JOINER.join( "/**", " * @param {...number} var_args", " * @param {number=} x", " */", "function f(var_args, x) {}"), JSTypeCreatorFromJSDoc.WRONG_PARAMETER_ORDER); typeCheck(LINE_JOINER.join( "/** @type {function(number=, ...number)} */", "function f(x) {}", "f('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(/** function(number=) */ fnum,", " /** function(string=) */ fstr, cond) {", " var y;", " if (cond) {", " y = fnum;", " } else {", " y = fstr;", " }", " y();", " y(123);", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(/** function(...number) */ fnum,", " /** function(...string) */ fstr, cond) {", " var y;", " if (cond) {", " y = fnum;", " } else {", " y = fstr;", " }", " y();", " y(123);", "}"), NewTypeInference.NOT_CALLABLE, NewTypeInference.NOT_CALLABLE); typeCheck(LINE_JOINER.join( "function f(", " /** function() */ f1, /** function(string=) */ f2, cond) {", " var y;", " if (cond) {", " y = f1;", " } else {", " y = f2;", " }", " y(123);", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @param {function(string): *} x */ function g(x) {}", "/** @param {function(...number): string} x */ function f(x) {", " g(x);", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @param {number=} x", " * @param {number=} y", " */", "function f(x, y) {}", "f(undefined, 123);", "f('str')"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(/** function(...) */ fun) {}", "f(function() {});")); // The restarg formal doesn't have to be called var_args. // It shouldn't be used in the body of the function. // typeCheck( // "/** @param {...number} x */ function f(x) { x - 5; }", // VarCheck.UNDEFINED_VAR_ERROR); typeCheck( "/** @param {number=} x */ function f(x) { x - 5; }", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck( "/** @param {number=} x */ function f(x) { if (x) { x - 5; } }"); typeCheck(LINE_JOINER.join( "function f(/** function(...number) */ x) {}", "f(function() {});")); typeCheck(LINE_JOINER.join( "function f(/** function() */ x) {}", "f(/** @type {function(...number)} */ (function(nums) {}));")); typeCheck(LINE_JOINER.join( "function f(/** function(string=) */ x) {}", "f(/** @type {function(...number)} */ (function(nums) {}));"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(/** function(...number) */ x) {}", "f(/** @type {function(string=)} */ (function(x) {}));"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @param {number} opt_num */ function f(opt_num) {}", "f();")); typeCheck( "function f(opt_num, x) {}", JSTypeCreatorFromJSDoc.WRONG_PARAMETER_ORDER); typeCheck("function f(var_args) {} f(1, 2, 3);"); typeCheck( "function f(var_args, x) {}", JSTypeCreatorFromJSDoc.WRONG_PARAMETER_ORDER); } public void testInferredOptionalFormals() { typeCheck("function f(x) {} f();"); typeCheck("function f(/** number */ x, y) { x-5; } f(123);"); typeCheck(LINE_JOINER.join( "function f(x) {", " if (x !== undefined) {", " return x-5;", " } else {", " return 0;", " }", "}", "f() - 1;", "f('str');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @return {function(number=)} */", "function f() {", " return function(x) {};", "}", "f()();", "f()('str');"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testSimpleClassInheritance() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Parent() {}", "/** @constructor @extends{Parent} */", "function Child() {}", "Child.prototype = new Parent();")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Parent() {", " /** @type {string} */ this.prop = 'asdf';", "}", "/** @constructor @extends{Parent} */", "function Child() {}", "Child.prototype = new Parent();", "(new Child()).prop - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Parent() {", " /** @type {string} */ this.prop = 'asdf';", "}", "/** @constructor @extends{Parent} */", "function Child() {}", "Child.prototype = new Parent();", "(new Child()).prop = 5;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Parent() {}", "/** @type {string} */ Parent.prototype.prop = 'asdf';", "/** @constructor @extends{Parent} */", "function Child() {}", "Child.prototype = new Parent();", "(new Child()).prop - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Parent() {}", "/** @type {string} */ Parent.prototype.prop = 'asdf';", "/** @constructor @extends{Parent} */", "function Child() {", " /** @type {number} */ this.prop = 5;", "}", "Child.prototype = new Parent();"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Parent() {}", "/** @type {string} */ Parent.prototype.prop = 'asdf';", "/** @constructor @extends{Parent} */", "function Child() {}", "Child.prototype = new Parent();", "/** @type {number} */ Child.prototype.prop = 5;"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Parent() {}", "/** @extends {Parent} */ function Child() {}"), JSTypeCreatorFromJSDoc.EXTENDS_NOT_ON_CTOR_OR_INTERF); typeCheck( "/** @constructor @extends{number} */ function Foo() {}", JSTypeCreatorFromJSDoc.EXTENDS_NON_OBJECT); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @implements {string}", " */", "function Foo() {}"), JSTypeCreatorFromJSDoc.IMPLEMENTS_NON_INTERFACE); typeCheck(LINE_JOINER.join( "/**", " * @interface", " * @extends {number}", " */", "function Foo() {}"), JSTypeCreatorFromJSDoc.EXTENDS_NON_INTERFACE); typeCheck(LINE_JOINER.join( "/** @interface */ function Foo() {}", "/** @implements {Foo} */ function bar() {}"), JSTypeCreatorFromJSDoc.IMPLEMENTS_WITHOUT_CONSTRUCTOR); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prototype.method = function(x) { x - 1; };", "/** @constructor @extends {Foo} */", "function Bar() {}", "Bar.prototype.method = function(x, y) { x - y; };", "Bar.prototype.method2 = function(x, y) {};", "Bar.prototype.method = Bar.prototype.method2;"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "/** @type {string} */", "Foo.prototype.prop;", "/**", " * @constructor", " * @implements {Foo}", " */", "function Bar() {", " /** @type {?string} */", " this.prop = null;", "}", "Bar.prototype.method = function() {", " this.prop = null;", "};"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); } public void testInheritingTheParentClassInterfaces() { typeCheck(LINE_JOINER.join( "/** @interface */", "function High() {}", "/** @type {number} */", "High.prototype.p;", "/** @constructor @implements {High} */", "function Mid() {}", "Mid.prototype.p = 123;", // Low has p from Mid, no warning here "/** @constructor @extends {Mid} */", "function Low() {}")); typeCheck(LINE_JOINER.join( "/** @interface */", "function High() {}", "/** @constructor @implements {High} */", "function Mid() {}", "/** @constructor @extends {Mid} */", "function Low() {}", "var /** !High */ x = new Low();")); typeCheck(LINE_JOINER.join( "/**", " * @interface", " * @template T", " */", "function High() {}", "/**", " * @constructor", " * @template T", " * @implements {High<T>}", " */", "function Mid() {}", "/** @constructor @extends {Mid<number>} */", "function Low() {}", "var /** !High<string> */ x = new Low;"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testInheritanceSubtyping() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Parent() {}", "/** @constructor @extends{Parent} */ function Child() {}", "(function(/** Parent */ x) {})(new Child);")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Parent() {}", "/** @constructor @extends{Parent} */ function Child() {}", "(function(/** Child */ x) {})(new Parent);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Parent() {}", "/** @constructor @extends{Parent} */ function Child() {}", "/** @constructor */", "function Foo() { /** @type {Parent} */ this.x = new Child(); }", "/** @type {Child} */ Foo.prototype.y = new Parent();"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @interface */", "function High() {}", "/** @constructor @implements {High} */", "function Low() {}", "var /** !High */ x = new Low")); typeCheck(LINE_JOINER.join( "/** @interface */", "function High() {}", "/** @interface @extends {High}*/", "function Low() {}", "function f(/** !High */ h, /** !Low */ l) { h = l; }")); typeCheck(LINE_JOINER.join( "/** @interface */", "function High() {}", "/** @interface @extends {High}*/", "function Low() {}", "/** @constructor @implements {Low} */", "function Foo() {}", "var /** !High */ x = new Foo;")); typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "/** @interface */", "function High() {}", "/** @interface @extends {High} */", "function Med() {}", "/**", " * @interface", " * @extends {Med}", " * @extends {Foo}", " */", "function Low() {}", "function f(/** !High */ x, /** !Low */ y) { x = y }")); typeCheck(LINE_JOINER.join( "/**", " * @interface", " * @template T", " */", "function Foo() {}", "function f(/** !Foo<number> */ x, /** !Foo<string> */ y) { x = y; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @interface", " * @template T", " */", "function Foo() {}", "/**", " * @constructor", " * @implements {Foo<number>}", " */", "function Bar() {}", "function f(/** !Foo<string> */ x, /** Bar */ y) { x = y; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @interface", " * @template T", " */", "function Foo() {}", "/**", " * @constructor", " * @template T", " * @implements {Foo<T>}", " */", "function Bar() {}", "function f(/** !Foo<string> */ x, /** !Bar<number> */ y) { x = y; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @interface", " * @template T", " */", "function Foo() {}", "/**", " * @constructor", " * @template T", " * @implements {Foo<T>}", " */", "function Bar() {}", "function f(/** !Foo<string> */ x, /** !Bar<string> */ y) {", " x = y;", "}")); typeCheck(LINE_JOINER.join( "/**", " * @interface", " * @template T", " */", "function Foo() {}", "/**", " * @constructor", " * @template T", " * @implements {Foo<T>}", " */", "function Bar() {}", "/**", " * @template T", " * @param {!Foo<T>} x", " * @param {!Bar<number>} y", " */", "function f(x, y) { x = y; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); // When getting a method signature from the parent, the receiver type is // still the child's type. typeCheck(LINE_JOINER.join( "/** @interface */", "function High() {}", "/** @param {number} x */", "High.prototype.method = function (x) {};", "/** @constructor @implements {High} */", "function Low() {}", "Low.prototype.method = function (x) {", " var /** !Low */ y = this;", "};")); } public void testInheritanceImplicitObjectSubtyping() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @override */ Foo.prototype.toString = function(){ return ''; };")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @override */ Foo.prototype.toString = function(){ return 5; };"), NewTypeInference.RETURN_NONDECLARED_TYPE); } public void testRecordtypeSubtyping() { typeCheck(LINE_JOINER.join( "/** @interface */ function I() {}", "/** @type {number} */ I.prototype.prop;", "function f(/** !I */ x) {", " var /** { prop: number } */ y = x;", "}")); } public void testWarnAboutOverridesNotVisibleDuringGlobalTypeInfo() { typeCheck(LINE_JOINER.join( "/** @constructor @extends {Parent} */ function Child() {}", "/** @type {string} */ Child.prototype.y = 'str';", "/** @constructor */ function Grandparent() {}", "/** @type {number} */ Grandparent.prototype.y = 9;", "/** @constructor @extends {Grandparent} */ function Parent() {}"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); } public void testMethodPropertyOverride() { typeCheck(LINE_JOINER.join( "/** @interface */ function Parent() {}", "/** @type {number} */ Parent.prototype.y;", "/** @constructor @implements {Parent} */ function Child() {}", "/** @param {string} x */ Child.prototype.y = function(x) {};"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); typeCheck(LINE_JOINER.join( "/** @interface */ function Parent() {}", "/** @param {string} x */ Parent.prototype.y = function(x) {};", "/** @constructor @implements {Parent} */ function Child() {}", "/** @type {number} */ Child.prototype.y = 9;"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Parent() {}", "/** @type {number} */ Parent.prototype.y = 9;", "/** @constructor @extends {Parent} */ function Child() {}", "/** @param {string} x */ Child.prototype.y = function(x) {};"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Parent() {}", "/** @param {string} x */ Parent.prototype.y = function(x) {};", "/** @constructor @extends {Parent} */ function Child() {}", "/** @type {number} */ Child.prototype.y = 9;"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prototype.f = function(/** number */ x, /** number */ y) {};", "/** @constructor @extends {Foo} */", "function Bar() {}", "/** @override */", "Bar.prototype.f = function(x) {};")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @type {function()|undefined} */", "Foo.prototype.method;", "/**", " * @constructor", " * @extends {Foo}", " */", "function Baz() {}", "/** @export */", "Baz.prototype.method = function() {};")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @type {function(number=)} */", "Foo.prototype.method;", "/**", " * @constructor", " * @extends {Foo}", " */", "function Baz() {}", "/** @export */", "Baz.prototype.method = function(x) {};", "(new Baz).method();")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @type {function(number)|undefined} */", "Foo.prototype.method;", "/**", " * @constructor", " * @extends {Foo}", " */", "function Baz() {}", "/** @param {number} x */", "Baz.prototype.method = function(x) {};")); } public void testMultipleObjects() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @constructor */ function Bar() {}", "/** @param {(Foo|Bar)} x */ function reqFooBar(x) {}", "function f(cond) {", " reqFooBar(cond ? new Foo : new Bar);", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @constructor */ function Bar() {}", "/** @param {Foo} x */ function reqFoo(x) {}", "function f(cond) {", " reqFoo(cond ? new Foo : new Bar);", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @constructor */ function Bar() {}", "/** @param {(Foo|Bar)} x */ function g(x) {", " if (x instanceof Foo) {", " var /** Foo */ y = x;", " } else {", " var /** Bar */ z = x;", " }", " var /** Foo */ w = x;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() { /** @type {string} */ this.s = 'str'; }", "/** @param {(!Foo|{n:number, s:string})} x */ function g(x) {", " if (x instanceof Foo) {", " } else {", " x.s - 5;", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @type {number} */ Foo.prototype.n = 5;", "/** @param {{n : number}} x */ function reqRecord(x) {}", "function f() {", " reqRecord(new Foo);", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @type {number} */ Foo.prototype.n = 5;", "/** @param {{n : string}} x */ function reqRecord(x) {}", "function f() {", " reqRecord(new Foo);", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @param {{n : number}|!Foo} x */", "function f(x) {", " x.n - 5;", "}"), NewTypeInference.POSSIBLY_INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @param {{n : number}|!Foo} x */", "function f(x) {", " x.abc - 5;", "}"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @constructor */ function Bar() {}", "/** @param {!Bar|!Foo} x */", "function f(x) {", " x.abc = 'str';", " if (x instanceof Foo) {", " x.abc - 5;", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testMultipleFunctionsInUnion() { typeCheck(LINE_JOINER.join( "/** @param {function():string | function():number} x", " * @return {string|number} */", "function f(x) {", " return x();", "}")); typeCheck(LINE_JOINER.join( "/** @param {function(string)|function(number)} x", " * @param {string|number} y */", "function f(x, y) {", " x(y);", "}"), JSTypeCreatorFromJSDoc.UNION_IS_UNINHABITABLE); typeCheck(LINE_JOINER.join( "/**", " * @template S, T", " * @param {function(S):void | function(T):void} fun", " */", "function f(fun) {}"), JSTypeCreatorFromJSDoc.UNION_IS_UNINHABITABLE); } public void testPrototypeOnNonCtorFunction() { typeCheck("function Foo() {}; Foo.prototype.y = 5;"); typeCheck(LINE_JOINER.join( "function f(/** Function */ x) {", " var y = x != null ? x.prototype : null;", "}")); } public void testInvalidTypeReference() { typeCheck( "/** @type {gibberish} */ var x;", GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME); typeCheck( "/** @param {gibberish} x */ function f(x){};", GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME); typeCheck( "function f(/** gibberish */ x){};", GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME); typeCheck(LINE_JOINER.join( "/** @returns {gibberish} */", "function f(x) { return x; };"), GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME); typeCheck( "/** @interface @extends {gibberish} */ function Foo(){};", GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME, JSTypeCreatorFromJSDoc.EXTENDS_NON_INTERFACE); typeCheck( "/** @constructor @implements {gibberish} */ function Foo(){};", GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME, JSTypeCreatorFromJSDoc.IMPLEMENTS_NON_INTERFACE); typeCheck( "/** @constructor @extends {gibberish} */ function Foo() {};", GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME, JSTypeCreatorFromJSDoc.EXTENDS_NON_OBJECT); } public void testCircularDependencies() { typeCheck(LINE_JOINER.join( "/** @constructor @extends {Bar}*/ function Foo() {}", "/** @constructor */ function Bar() {}")); typeCheck(LINE_JOINER.join( "/** @param {Foo} x */ function f(x) {}", "/** @constructor */ function Foo() {}")); typeCheck(LINE_JOINER.join( "f(new Bar)", "/** @param {Foo} x */ function f(x) {}", "/** @constructor */ function Foo() {}", "/** @constructor */ function Bar() {}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor @param {Foo} x */ function Bar(x) {}", "/** @constructor @param {Bar} x */ function Foo(x) {}", "new Bar(new Foo(null));")); typeCheck(LINE_JOINER.join( "/** @constructor @param {Foo} x */ function Bar(x) {}", "/** @constructor @param {Bar} x */ function Foo(x) {}", "new Bar(new Foo(undefined));"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor @extends {Bar} */ function Foo() {}", "/** @constructor @extends {Foo} */ function Bar() {}"), JSTypeCreatorFromJSDoc.INHERITANCE_CYCLE); typeCheck(LINE_JOINER.join( "/** @interface @extends {Bar} */ function Foo() {}", "/** @interface @extends {Foo} */ function Bar() {}"), JSTypeCreatorFromJSDoc.INHERITANCE_CYCLE); typeCheck( "/** @constructor @extends {Foo} */ function Foo() {}", JSTypeCreatorFromJSDoc.INHERITANCE_CYCLE); } public void testInvalidInitOfInterfaceProps() throws Exception { typeCheck(LINE_JOINER.join( "/** @interface */ function T() {};", "T.prototype.x = function() { return 'foo'; }"), GlobalTypeInfo.INTERFACE_METHOD_NOT_EMPTY); typeCheck(LINE_JOINER.join( "/** @interface */ function I() {};", "/** @type {number} */", "I.prototype.n = 123;"), GlobalTypeInfo.INVALID_INTERFACE_PROP_INITIALIZER); } public void testInterfaceSingleInheritance() { typeCheck(LINE_JOINER.join( "/** @interface */ function I() {}", "/** @type {string} */ I.prototype.prop;", "/** @constructor @implements{I} */ function C() {}"), GlobalTypeInfo.INTERFACE_METHOD_NOT_IMPLEMENTED); typeCheck(LINE_JOINER.join( "/** @interface */ function I() {}", "/** @param {number} x */", "I.prototype.method = function(x) {};", "/** @constructor @implements{I} */ function C() {}"), GlobalTypeInfo.INTERFACE_METHOD_NOT_IMPLEMENTED); typeCheck(LINE_JOINER.join( "/** @interface */ function IParent() {}", "/** @type {number} */ IParent.prototype.prop;", "/** @interface @extends{IParent} */ function IChild() {}", "/** @constructor @implements{IChild} */", "function C() { this.prop = 5; }", "(new C).prop < 'adsf';"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */ function IParent() {}", "/** @type {number} */ IParent.prototype.prop;", "/** @interface @extends{IParent} */ function IChild() {}", "/** @constructor @implements{IChild} */", "function C() { this.prop = 'str'; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Parent() { /** @type {number} */ this.prop = 123; }", "/** @constructor @extends {Parent} */ function Child() {}", "(new Child).prop = 321;")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Parent() { /** @type {number} */ this.prop = 123; }", "/** @constructor @extends {Parent} */ function Child() {}", "(new Child).prop = 'str';"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @interface */ function I() {}", "/** @param {number} x */", "I.prototype.method = function(x, y) {};", "/** @constructor @implements{I} */ function C() {}", "/** @param {string} y */", "C.prototype.method = function(x, y) {};", "(new C).method(5, 6);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */ function I() {}", "/** @param {number} x */", "I.prototype.method = function(x, y) {};", "/** @constructor @implements{I} */ function C() {}", "/** @param {string} y */", "C.prototype.method = function(x, y) {};", "(new C).method('asdf', 'fgr');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */ function I() {}", "/** @param {number} x */", "I.prototype.method = function(x) {};", "/** @constructor @implements{I} */ function C() {}", "C.prototype.method = function(x) {};", "(new C).method('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */ function I1() {}", "/** @param {number} x */ I1.prototype.method = function(x, y) {};", "/** @interface */ function I2() {}", "/** @param {string} y */ I2.prototype.method = function(x, y) {};", "/** @constructor @implements{I1} @implements{I2} */ function C(){}", "C.prototype.method = function(x, y) {};", "(new C).method('asdf', 'fgr');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */ function I1() {}", "/** @param {number} x */ I1.prototype.method = function(x, y) {};", "/** @interface */ function I2() {}", "/** @param {string} y */ I2.prototype.method = function(x, y) {};", "/** @constructor @implements{I1} @implements{I2} */ function C(){}", "C.prototype.method = function(x, y) {};", "(new C).method(1, 2);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */ function I1() {}", "/** @param {number} x */ I1.prototype.method = function(x) {};", "/** @interface */ function I2() {}", "/** @param {string} x */ I2.prototype.method = function(x) {};", "/** @constructor @implements{I1} @implements{I2} */ function C(){}", // Type of C.method is @param {(string|number)} "C.prototype.method = function(x) {};")); typeCheck(LINE_JOINER.join( "/** @interface */ function I1() {}", "/** @param {number} x */ I1.prototype.method = function(x) {};", "/** @interface */ function I2() {}", "/** @param {string} x */ I2.prototype.method = function(x) {};", "/** @constructor @implements{I1} @implements{I2} */ function C(){}", // Type of C.method is @param {(string|number)} "C.prototype.method = function(x) {};", "(new C).method(true);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */ function I() {}", "/** @param {number} x */ I.prototype.method = function(x) {};", "/** @constructor */ function S() {}", "/** @param {string} x */ S.prototype.method = function(x) {};", "/** @constructor @implements{I} @extends{S} */ function C(){}", // Type of C.method is @param {(string|number)} "C.prototype.method = function(x) {};", "(new C).method(true);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "Foo.prototype.prop;", "/**", " * @constructor", " * @implements {Foo}", " */", "function Bar() {}"), GlobalTypeInfo.INTERFACE_METHOD_NOT_IMPLEMENTED); } public void testInterfaceMultipleInheritanceNoCrash() { typeCheck(LINE_JOINER.join( "/** @interface */", "function I1() {}", "I1.prototype.method = function(x) {};", "/** @interface */", "function I2() {}", "I2.prototype.method = function(x) {};", "/**", " * @interface", " * @extends {I1}", " * @extends {I2}", " */", "function I3() {}", "/** @constructor @implements {I3} */", "function Foo() {}", "Foo.prototype.method = function(x) {};")); } public void testInterfaceArgument() { typeCheck(LINE_JOINER.join( "/** @interface */", "function I() {}", "/** @param {number} x */", "I.prototype.method = function(x) {};", "/** @param {!I} x */", "function foo(x) { x.method('asdf'); }"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */", "function IParent() {}", "/** @param {number} x */", "IParent.prototype.method = function(x) {};", "/** @interface @extends {IParent} */", "function IChild() {}", "/** @param {!IChild} x */", "function foo(x) { x.method('asdf'); }"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testExtendedInterfacePropertiesCompatibility() { typeCheck(LINE_JOINER.join( "/** @interface */function Int0() {};", "/** @interface */function Int1() {};", "/** @type {number} */", "Int0.prototype.foo;", "/** @type {string} */", "Int1.prototype.foo;", "/** @interface \n @extends {Int0} \n @extends {Int1} */", "function Int2() {};"), GlobalTypeInfo.SUPER_INTERFACES_HAVE_INCOMPATIBLE_PROPERTIES); typeCheck(LINE_JOINER.join( "/** @interface */", "function Parent1() {}", "/**", " * @template T", " * @param {T} x", " * @return {number}", " */", "Parent1.prototype.method = function(x) {};", "/** @interface */", "function Parent2() {}", "/**", " * @template T", " * @param {T} x", " * @return {string}", " */", "Parent2.prototype.method = function(x) {};", "/** @interface @extends {Parent1} @extends {Parent2} */", "function Child() {}"), GlobalTypeInfo.SUPER_INTERFACES_HAVE_INCOMPATIBLE_PROPERTIES); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @constructor */ function Bar() {}", "/** @interface */", "function Parent1() {}", "/** @type {!Foo} */", "Parent1.prototype.obj;", "/** @interface */", "function Parent2() {}", "/** @type {!Bar} */", "Parent2.prototype.obj;", "/** @interface @extends {Parent1} @extends {Parent2} */", "function Child() {}"), GlobalTypeInfo.SUPER_INTERFACES_HAVE_INCOMPATIBLE_PROPERTIES); } public void testTwoLevelExtendedInterface() { typeCheck(LINE_JOINER.join( "/** @interface */function Int0() {};", "/** @type {function()} */", "Int0.prototype.foo;", "/** @interface @extends {Int0} */function Int1() {};", "/** @constructor \n @implements {Int1} */", "function Ctor() {};"), GlobalTypeInfo.INTERFACE_METHOD_NOT_IMPLEMENTED); } public void testConstructorExtensions() { typeCheck(LINE_JOINER.join( "/** @constructor */ function I() {}", "/** @param {number} x */", "I.prototype.method = function(x) {};", "/** @constructor @extends{I} */ function C() {}", "C.prototype.method = function(x) {};", "(new C).method('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function I() {}", "/** @param {number} x */", "I.prototype.method = function(x, y) {};", "/** @constructor @extends{I} */ function C() {}", "/** @param {string} y */", "C.prototype.method = function(x, y) {};", "(new C).method('asdf', 'fgr');"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testInterfaceAndConstructorInvalidConstructions() { typeCheck(LINE_JOINER.join( "/** @constructor @extends {Bar} */", "function Foo() {}", "/** @interface */", "function Bar() {}"), JSTypeCreatorFromJSDoc.CONFLICTING_EXTENDED_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor @implements {Bar} */", "function Foo() {}", "/** @constructor */", "function Bar() {}"), JSTypeCreatorFromJSDoc.IMPLEMENTS_NON_INTERFACE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @interface @implements {Foo} */", "function Bar() {}"), JSTypeCreatorFromJSDoc.IMPLEMENTS_NON_INTERFACE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @interface @extends {Foo} */", "function Bar() {}"), JSTypeCreatorFromJSDoc.EXTENDS_NON_INTERFACE); } public void testNot() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @constructor */", "function Bar() { /** @type {string} */ this.prop = 'asdf'; }", "function f(/** (!Foo|!Bar) */ obj) {", " if (!(obj instanceof Foo)) {", " obj.prop - 5;", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(cond) {", " var x = cond ? null : 123;", " if (!(x === null)) { x - 5; }", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){ this.prop = 123; }", "function f(/** Foo */ obj) {", " if (!obj) { obj.prop; }", "}"), NewTypeInference.PROPERTY_ACCESS_ON_NONOBJECT); } public void testGetElem() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function C(){ /** @type {number} */ this.prop = 1; }", "(new C)['prop'] < 'asdf';"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x, y) {", " x < y;", " ({})[y - 5];", "}", "f('asdf', 123);"), NewTypeInference.INVALID_ARGUMENT_TYPE); // We don't see the warning here b/c the formal param x is assigned to a // string, and we use x's type at the end of the function to create the // summary. typeCheck(LINE_JOINER.join( "function f(x, y) {", " x < y;", " ({})[y - 5];", " x = 'asdf';", "}", "f('asdf', 123);")); typeCheck(LINE_JOINER.join( "function f(x, y) {", " ({})[y - 5];", " x < y;", "}", "f('asdf', 123);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " x['prop'] = 'str';", " return x['prop'] - 5;", "}", "f({});"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck("function f(/** ? */ o) { return o[0].toString; }"); // TODO(blickly): The fact that this has no warnings is somewhat unpleasant. typeCheck(LINE_JOINER.join( "function f(x) {", " x['prop'] = 7;", " var p = 'prop';", " x[p] = 'str';", " return x['prop'] - 5;", "}", "f({});")); // Used to spuriously warn b/c we can't specialize the receiver of a // computed access to a useful type. typeCheck(LINE_JOINER.join( "Object.prototype.dispose;", "function f(/** !Object */ x, id) {", " var serviceHolder = x[id];", " if (typeof serviceHolder[0].dispose != 'undefined') {}", "}")); } public void testNamespaces() { typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @constructor */ ns.C = function() {};", "ns.C();"), NewTypeInference.CONSTRUCTOR_NOT_CALLABLE); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @param {number} x */ ns.f = function(x) {};", "ns.f('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @constructor */ ns.C = function(){}", "ns.C.prototype.method = function(/** string */ x){};", "(new ns.C).method(5);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @const */ ns.ns2 = {};", "/** @constructor */ ns.ns2.C = function() {};", "ns.ns2.C();"), NewTypeInference.CONSTRUCTOR_NOT_CALLABLE); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @const */ ns.ns2 = {};", "/** @constructor */ ns.ns2.C = function() {};", "ns.ns2.C.prototype.method = function(/** string */ x){};", "(new ns.ns2.C).method(11);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function C1(){}", "/** @constructor */ C1.C2 = function(){}", "C1.C2.prototype.method = function(/** string */ x){};", "(new C1.C2).method(1);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function C1(){};", "/** @constructor */ C1.prototype.C2 = function(){};", "(new C1).C2();"), NewTypeInference.CONSTRUCTOR_NOT_CALLABLE); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @type {number} */ ns.N = 5;", "ns.N();"), NewTypeInference.NOT_CALLABLE); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @type {number} */ ns.foo = 123;", "/** @type {string} */ ns.foo = '';"), GlobalTypeInfo.REDECLARED_PROPERTY); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @type {number} */ ns.foo;", "/** @type {string} */ ns.foo;"), GlobalTypeInfo.REDECLARED_PROPERTY); // We warn for duplicate declarations even if they are the same type. typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @type {number} */ ns.foo;", "/** @type {number} */ ns.foo;"), GlobalTypeInfo.REDECLARED_PROPERTY); // Without the @const, we don't consider it a namespace and don't warn. typeCheck(LINE_JOINER.join( "var ns = {};", "/** @type {number} */ ns.foo = 123;", "/** @type {string} */ ns.foo = '';")); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "ns.x = 5;", "/** @type {string} */", "ns.x = 'str';"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "ns.prop = 1;", "function f() { var /** string */ s = ns.prop; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/**", " * @constructor", " * @param {number} x", " */", "ns.Foo = function (x) {};", "function f() {", " return new ns.Foo('asdf');", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); // Happens when providing 'a.b.c' and explicitly defining a.b. typeCheck(LINE_JOINER.join( "function f() {", "/** @const */", "var a = {};", "a.b = {};", "/** @const */", "a.b.c = {};", "/** @constructor */", "a.b = function() {};", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testNamespacesInExterns() { typeCheckCustomExterns( DEFAULT_EXTERNS + LINE_JOINER.join( "/** @const */ var ns = {};", "/** @type {number} */ ns.num;"), "var /** number */ n = ns.num;"); typeCheckCustomExterns( DEFAULT_EXTERNS + LINE_JOINER.join( "/** @const */ var ns = {};", "/** @type {number} */ ns.num;"), "var /** string */ s = ns.num;", NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testSimpleInferNamespaces() { typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @const */ ns.numprop = 123;", "/** @const */ var x = ns;", "function f() { var /** string */ s = x.numprop; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @enum {number} */ var e = { FOO : 5 };", "/** @const */ e.numprop = 123;", "/** @const */ var x = e;", "function f() { var /** string */ s = x.numprop; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @type {number} */ ns.n = 5;", "/** @const */ var x = ns.n;")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @const */", "var Bar = Foo;", "function g() { Bar(); }"), NewTypeInference.CONSTRUCTOR_NOT_CALLABLE); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @constructor */", "ns.Foo = function() {};", "/** @type {string} */", "ns.Foo.prop = 'asdf';", "/** @const */ var Foo = ns.Foo;", "function g() { Foo.prop - 5; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @constructor */", "ns.Foo = function() {};", "/** @const */ var Foo = ns.Foo;", "function g() { Foo(); }"), NewTypeInference.CONSTRUCTOR_NOT_CALLABLE); typeCheck(LINE_JOINER.join( "function /** string */ f(/** string */ x) { return x; }", "/** @const */", "var g = f;", "function h() { g('asdf') - 1; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @type {number} */ ns.n = 5;", "/** @const */ var x = ns.n;", "/** @type {string} */ ns.s = 'str';")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @type {number} */ Foo.n = 5;", "/** @const */ var x = Foo.n;", "/** @type {string} */ Foo.s = 'str';")); } public void testDontWarnAboutInferringDeclaredFunctionTypes() { typeCheck(LINE_JOINER.join( "/** @const */ var ns ={};", "/** @const @return {void} */", "ns.f = function() {};")); typeCheck(LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "/** @const @return {void} */", "Foo.f = function() {};")); typeCheckCustomExterns(DEFAULT_EXTERNS + "/** @const @return {void} */ var f;", ""); } public void testDontInferUndeclaredFunctionReturn() { typeCheck(LINE_JOINER.join( "function f() {}", "/** @const */ var x = f();")); typeCheck(LINE_JOINER.join( "function f() {}", "/** @const */ var x = f();", "function g() { x; }"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "ns.f = function() {}", "/** @const */ var x = f();", "function g() { x; }"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); } public void testNestedNamespaces() { // In the previous type inference, ns.subns did not need a // @const annotation, but we require it. typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @const */", "ns.subns = {};", "/** @type {string} */", "ns.subns.n = 'str';", "function f() { ns.subns.n - 5; }"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testNonnamespaceLooksLikeANamespace() { typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @type {Object} */", "ns.obj = null;", "function setObj() {", " ns.obj = {};", "}")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @type {Object} */", "ns.obj = null;", "function setObj() {", " ns.obj = {};", " ns.obj.str = 'str';", "}")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @type {Object} */", "ns.obj = null;", "ns.obj = {};", "ns.obj.x = 'str';", "ns.obj.x - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @type {Object} */", "ns.obj = null;", "ns.obj = { x : 1, y : 5};", "ns.obj.x = 'str';")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @type {Object} */", "ns.obj = null;", "ns.obj = { x : 1, y : 5};", "ns.obj.x = 'str';", "ns.obj.x - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testNamespacedObjectsDontCrash() { typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @constructor */", "ns.Foo = function() {", " ns.Foo.obj.value = ns.Foo.VALUE;", "};", "ns.Foo.obj = {};", "ns.Foo.VALUE = 128;")); } public void testRedeclaredNamespaces() { // TODO(blickly): Consider a warning if RHS doesn't contain || typeCheck(LINE_JOINER.join( "/** @const */ var ns = ns || {}", "/** @const */ var ns = ns || {}")); typeCheck(LINE_JOINER.join( "/** @const */ var ns = ns || {}", "ns.subns = ns.subns || {}", "ns.subns = ns.subns || {}")); } public void testReferenceToNonexistentNamespace() { // typeCheck( // "/** @constructor */ ns.Foo = function(){};", // VarCheck.UNDEFINED_VAR_ERROR); // typeCheck( // "ns.subns = {};", // VarCheck.UNDEFINED_VAR_ERROR); // typeCheck( // "/** @enum {number} */ ns.NUM = { N : 1 };", // VarCheck.UNDEFINED_VAR_ERROR); // typeCheck( // "/** @typedef {number} */ ns.NUM;", // VarCheck.UNDEFINED_VAR_ERROR); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @constructor */ ns.subns.Foo = function(){};"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "ns.subns.subsubns = {};"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @enum {number} */ ns.subns.NUM = { N : 1 };"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @typedef {number} */ ns.subns.NUM;"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){}", "Foo.subns.subsubns = {};"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){}", "/** @constructor */ Foo.subns.Bar = function(){};"), NewTypeInference.INEXISTENT_PROPERTY); } public void testThrow() { typeCheck("throw 123;"); typeCheck("var msg = 'hello'; throw msg;"); typeCheck(LINE_JOINER.join( "function f(cond, x, y) {", " if (cond) {", " x < y;", " throw 123;", " } else {", " x < 2;", " }", "}")); typeCheck(LINE_JOINER.join( "function f() { }", "function g() {", " throw f();", "}")); typeCheck("throw (1 - 'asdf');", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x) { throw x - 1; }", "f('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testQnameInJsdoc() { typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @constructor */ ns.C = function() {};", "/** @param {!ns.C} x */ function f(x) {", " 123, x.prop;", "}"), NewTypeInference.INEXISTENT_PROPERTY); } public void testIncrementDecrements() { typeCheck(LINE_JOINER.join( "/** @const */ var ns = { x : 5 };", "ns.x++; ++ns.x; ns.x--; --ns.x;")); typeCheck( "function f(ns) { --ns.x; }; f({x : 'str'})", NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testAndOr() { typeCheck("function f(x, y, z) { return x || y && z;}"); typeCheck(LINE_JOINER.join( "function f(/** number */ x, /** string */ y) {", " var /** number */ n = x || y;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** number */ x, /** string */ y) {", " var /** number */ n = y || x;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function /** number */ f(/** ?number */ x) {", " return x || 42;", "}")); typeCheck(LINE_JOINER.join( "function /** (number|string) */ f(/** ?number */ x) {", " return x || 'str';", "}")); } public void testAndOrShortCircuiting() { typeCheck(LINE_JOINER.join( "/** @return {string|null} */", "function g() {", " /** @type {!Array<string|undefined>} */", " var a = [];", " return a[123] || null;", "}")); typeCheck(LINE_JOINER.join( "/** @return {number|undefined} */", "function g() {", " /** @type {!Array<!Object|undefined>} */", " var a = [];", " return a[123] && 42;", "}")); } public void testNonStringComparisons() { typeCheck(LINE_JOINER.join( "function f(x) {", " if (null == x) {", " var /** (null|undefined) */ y = x;", " }", "}")); typeCheck(LINE_JOINER.join( "function f(x) {", " if (x == null) {", " var /** (null|undefined) */ y = x;", " }", "}")); typeCheck(LINE_JOINER.join( "function f(x) {", " if (null == x) {", " var /** null */ y = x;", " var /** undefined */ z = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @param {*} x */", "function f(x) {", " if (5 == x) {", " var /** (null|undefined) */ y = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @param {*} x */", "function f(x) {", " if (x == 5) {", " var /** (null|undefined) */ y = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @param {*} x */", "function f(x) {", " if (null == x) {", " } else {", " var /** (null|undefined) */ y = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @param {*} x */", "function f(x) {", " if (x == null) {", " } else {", " var /** (null|undefined) */ y = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(x) {", " if (null != x) {", " } else {", " var /** (null|undefined) */ y = x;", " }", "}")); typeCheck(LINE_JOINER.join( "function f(x) {", " if (x != null) {", " } else {", " var /** (null|undefined) */ y = x;", " }", "}")); typeCheck(LINE_JOINER.join( "/** @param {*} x */", "function f(x) {", " if (5 != x) {", " } else {", " var /** (null|undefined) */ y = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @param {*} x */", "function f(x) {", " if (x != 5) {", " } else {", " var /** (null|undefined) */ y = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @param {*} x */", "function f(x) {", " if (null != x) {", " var /** (null|undefined) */ y = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @param {*} x */", "function f(x) {", " if (x != null) {", " var /** (null|undefined) */ y = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testAnalyzeLoopsBwd() { typeCheck("for(;;);"); typeCheck(LINE_JOINER.join( "function f(x) {", " for (; x - 5 > 0; ) {}", " x = undefined;", "}", "f(true);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " while (x - 5 > 0) {}", " x = undefined;", "}", "f(true);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " if (x - 5 > 0) {}", " x = undefined;", "}", "f(true);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " do {} while (x - 5 > 0);", " x = undefined;", "}", "f(true);"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testDontLoosenNominalTypes() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() { this.prop = 123; }", "function f(x) { if (x instanceof Foo) { var y = x.prop; } }")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() { this.prop = 123; }", "/** @constructor */ function Bar() { this.prop = 123; }", "function f(cond, x) {", " x = cond ? new Foo : new Bar;", " var y = x.prop;", "}")); } public void testFunctionsWithAbnormalExit() { typeCheck("function f(x) { x = 1; throw x; }"); // TODO(dimvar): to fix these, we must collect all THROWs w/out an out-edge // and use the envs from them in the summary calculation. (Rare case.) // typeCheck(LINE_JOINER.join( // "function f(x) {", // " var y = 1;", // " x < y;", // " throw 123;", // "}", // "f('asdf');"), // NewTypeInference.INVALID_ARGUMENT_TYPE); // typeCheck(LINE_JOINER.join( // "function f(x, cond) {", // " if (cond) {", // " var y = 1;", // " x < y;", // " throw 123;", // " }", // "}", // "f('asdf', 'whatever');"), // NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testAssignAdd() { // Without a type annotation, we can't find the type error here. typeCheck(LINE_JOINER.join( "function f(x, y) {", " x < y;", " var /** number */ z = 5;", " z += y;", "}", "f('asdf', 5);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x, y) {", " x < y;", " var z = 5;", " z += y;", "}", "f('asdf', 5);")); typeCheck( "var s = 'asdf'; (s += 'asdf') - 5;", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck("var s = 'asdf'; s += 5;"); typeCheck("var b = true; b += 5;", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck( "var n = 123; n += 'asdf';", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck( "var s = 'asdf'; s += true;", NewTypeInference.INVALID_OPERAND_TYPE); } public void testTypeCoercions() { typeCheck(LINE_JOINER.join( "function f(/** * */ x) {", " var /** string */ s = !x;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** * */ x) {", " var /** string */ s = +x;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** * */ x) {", " var /** string */ s = '' + x;", "}")); typeCheck(LINE_JOINER.join( "function f(/** * */ x) {", " var /** number */ s = '' + x;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testSwitch() { typeCheck( "switch (1) { case 1: break; case 2: break; default: break; }"); typeCheck(LINE_JOINER.join( "switch (1) {", " case 1:", " 1 - 'asdf';", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "switch (1) {", " default:", " 1 - 'asdf';", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "switch (1 - 'asdf') {", " case 1:", " break;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "switch (1) {", " case (1 - 'asdf'):", " break;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f(/** Foo */ x) {", " switch (x) {", " case null:", " break;", " default:", " var /** !Foo */ y = x;", " }", "}")); typeCheck(LINE_JOINER.join( "function f(x) {", " switch (x) {", " case 123:", " x - 5;", " }", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f(/** Foo */ x) {", " switch (x) {", " case null:", " default:", " var /** !Foo */ y = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(x) {", " switch (x) {", " case null:", " x - 5;", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " switch (x) {", " case null:", " var /** undefined */ y = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); // Tests for fall-through typeCheck(LINE_JOINER.join( "function f(x) {", " switch (x) {", " case 1: x - 5;", " case 'asdf': x < 123; x < 'asdf'; break;", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " switch (x) {", " case 1: x - 5;", " case 'asdf': break;", " }", "}")); typeCheck(LINE_JOINER.join( "function g(/** number */ x) { return 5; }", "function f() {", " switch (3) { case g('asdf'): return 123; }", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(/** number */ x, /** string */ y) {", " switch (y) { case x: ; }", "}"), NewTypeInference.INCOMPATIBLE_STRICT_COMPARISON); } public void testForIn() { typeCheck(LINE_JOINER.join( "function f(/** string */ y) {", " for (var x in { a: 1, b: 2 }) { y = x; }", " x = 234;", " return 123;", "}")); typeCheck(LINE_JOINER.join( "function f(y) {", " var z = x + 234;", " for (var x in { a: 1, b: 2 }) {}", " return 123;", "}"), // VariableReferenceCheck.EARLY_REFERENCE, NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(/** number */ y) {", " for (var x in { a: 1, b: 2 }) { y = x; }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck( "function f(/** Object? */ o) { for (var x in o); }", NewTypeInference.NULLABLE_DEREFERENCE); typeCheck("for (var x in 123) ;", NewTypeInference.FORIN_EXPECTS_OBJECT); typeCheck( "var /** number */ x = 5; for (x in {a : 1});", NewTypeInference.FORIN_EXPECTS_STRING_KEY); typeCheck(LINE_JOINER.join( "function f(/** undefined */ y) {", " var x;", " for (x in { a: 1, b: 2 }) { y = x; }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testTryCatch() { // typeCheck( // "try { e; } catch (e) {}", // VariableReferenceCheck.EARLY_REFERENCE); // typeCheck( // "e; try {} catch (e) {}", // VariableReferenceCheck.EARLY_REFERENCE); typeCheck("try {} catch (e) { e; }"); typeCheck( "try {} catch (e) { 1 - 'asdf'; }", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck( "try { throw 123; } catch (e) { 1 - 'asdf'; }", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck( "try { throw 123; } catch (e) {} finally { 1 - 'asdf'; }", NewTypeInference.INVALID_OPERAND_TYPE); // Outside of the catch block, e is unknown, like any other global variable. typeCheck(LINE_JOINER.join( "try {", " throw new Error();", "} catch (e) {}", "var /** number */ n = e;")); // // For this to pass, we must model local scopes properly. // typeCheck(LINE_JOINER.join( // "var /** string */ e = 'str';", // "try {", // " throw new Error();", // "} catch (e) {}", // "e - 3;"), // NewTypeInference.INVALID_OPERAND_TYPE); // typeCheck( // "var /** string */ e = 'asdf'; try {} catch (e) {} e - 5;", // VariableReferenceCheck.REDECLARED_VARIABLE); typeCheck(LINE_JOINER.join( "function f() {", " try {", " } catch (e) {", " return e.stack;", " }", "}")); typeCheck(LINE_JOINER.join( "function f() {", " try {", " throw new Error();", " } catch (e) {", " var /** Error */ x = e;", " }", "}")); typeCheck(LINE_JOINER.join( "function f() {", " try {", " throw new Error();", " } catch (e) {", " var /** number */ x = e;", " var /** string */ y = e;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheckCustomExterns( DEFAULT_EXTERNS + "function f() {}", "try {} catch (f) {}"); } public void testIn() { typeCheck("(true in {});", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck("('asdf' in 123);", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck( "var /** number */ n = ('asdf' in {});", NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** { a: number } */ obj) {", " if ('p' in obj) {", " return obj.p;", " }", "}", "f({ a: 123 });")); typeCheck(LINE_JOINER.join( "function f(/** { a: number } */ obj) {", " if (!('p' in obj)) {", " return obj.p;", " }", "}"), NewTypeInference.INEXISTENT_PROPERTY); } public void testDelprop() { typeCheck("delete ({ prop: 123 }).prop;"); typeCheck( "var /** number */ x = delete ({ prop: 123 }).prop;", NewTypeInference.MISTYPED_ASSIGN_RHS); // We don't detect the missing property typeCheck("var obj = { a: 1, b: 2 }; delete obj.a; obj.a;"); } public void testArrayLit() { typeCheck("[1, 2, 3 - 'asdf']", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x, y) {", " x < y;", " [y - 5];", "}", "f('asdf', 123);"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testArrayAccesses() { typeCheck( "var a = [1,2,3]; a['str'];", NewTypeInference.INVALID_INDEX_TYPE); typeCheck(LINE_JOINER.join( "function f(/** !Array<number> */ arr, i) {", " arr[i];", "}", "f([1, 2, 3], 'str');"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testRegExpLit() { typeCheck("/abc/"); } public void testDifficultLvalues() { typeCheck(LINE_JOINER.join( "function f() { return {}; }", "f().x = 123;")); typeCheck(LINE_JOINER.join( "function f() { return {}; }", "f().ns = {};")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() { /** @type {number} */ this.a = 123; }", "/** @return {!Foo} */", "function retFoo() { return new Foo(); }", "function f(cond) {", " (retFoo()).a = 'asdf';", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "(new Foo).x += 123;"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() { /** @type {number} */ this.a = 123; }", "function f(cond, /** !Foo */ foo1) {", " var /** { a: number } */ x = { a: 321 };", " (cond ? foo1 : x).a = 'asdf';", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck( "function f(obj) { obj[1 - 'str'] = 3; }", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck( "function f(/** undefined */ n, pname) { n[pname] = 3; }", NewTypeInference.ADDING_PROPERTY_TO_NON_OBJECT); } public void testQuestionableUnionJsDoc() { // 'string|?' is the same as '?' typeCheck("/** @type {string|?} */ var x;"); typeCheck(LINE_JOINER.join( "goog.forwardDeclare('a');", "/** @type {a|number} */", "var x;")); typeCheck(LINE_JOINER.join( "", "/**", " * @return {T|S}", " * @template T, S", " */", "function f(){};")); typeCheck("/** @param {(?)} x */ function f(x) {}"); } public void testGenericsJsdocParsing() { typeCheck("/** @template T\n@param {T} x */ function f(x) {}"); typeCheck(LINE_JOINER.join( "/** @template T\n @param {T} x\n @return {T} */", "function f(x) { return x; };")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @constructor", " * @param {T} x", " * @extends {Bar<T>} // error, Bar is not templatized ", " */", "function Foo(x) {}", "/** @constructor */", "function Bar() {}"), JSTypeCreatorFromJSDoc.INVALID_GENERICS_INSTANTIATION); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @constructor", " * @param {T} x", " */", "function Foo(x) {}", "/** @param {Foo<number, string>} x */", "function f(x) {}"), JSTypeCreatorFromJSDoc.INVALID_GENERICS_INSTANTIATION); typeCheck("/** @type {Array<number>} */ var x;"); typeCheck("/** @type {Object<number>} */ var x;"); typeCheck(LINE_JOINER.join( "function f(/** !Object<string, string> */ x) {", " return x['dont-warn-about-inexistent-property'];", "}")); typeCheck( "/** @template T\n@param {!T} x */ function f(x) {}", JSTypeCreatorFromJSDoc.CANNOT_MAKE_TYPEVAR_NON_NULL); } public void testInvalidGenericsInstantiation() { typeCheck(LINE_JOINER.join( "/** @type {number<string>} */", "var x;"), JSTypeCreatorFromJSDoc.INVALID_GENERICS_INSTANTIATION); typeCheck(LINE_JOINER.join( "/** @type {!Function<string>} */", "var x;"), JSTypeCreatorFromJSDoc.INVALID_GENERICS_INSTANTIATION); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T<number>} x", " */", "function f(x) {}"), JSTypeCreatorFromJSDoc.INVALID_GENERICS_INSTANTIATION); typeCheck(LINE_JOINER.join( "/** @typedef{{prop:number}} */", "var MyType;", "/** @type {MyType<string>} */", "var x;"), JSTypeCreatorFromJSDoc.INVALID_GENERICS_INSTANTIATION); typeCheck(LINE_JOINER.join( "/** @enum */", "var MyType = { A: 1 };", "/** @type {MyType<string>} */", "var x;"), JSTypeCreatorFromJSDoc.INVALID_GENERICS_INSTANTIATION); // Don't warn for a forward-declared type; the same file can be included // in compilations that define the type as a generic type. typeCheck(LINE_JOINER.join( FORWARD_DECLARATION_DEFINITIONS, "goog.forwardDeclare('Bar');", "/** @type {Bar<string>} */", "var x;")); } public void testPolymorphicFunctionInstantiation() { typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "function id(x) { return x; }", "id('str') - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "f(123, 'asdf');"), NewTypeInference.NOT_UNIQUE_INSTANTIATION); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {(T|null)} x", " * @return {(T|number)}", " */", "function f(x) { return x === null ? 123 : x; }", "/** @return {(null|undefined)} */ function g() { return null; }", "var /** (number|undefined) */ y = f(g());")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {(T|number)} x", " */", "function f(x) {}", "/** @return {*} */ function g() { return 1; }", "f(g());")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T=} x", " * @return {T}", " */", "function f(x) {", " return /** @type {?} */ (x);", "}", "/** @param {*} x */", "function g(x) { f(x); }")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "function id(x) { return x; }", "/** @return {*} */ function g() { return 1; }", "id(g()) - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T, U", " * @param {T} x", " * @param {U} y", " * @return {U}", " */", "function f(x, y) { return y; }", "f(10, 'asdf') - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function g(x) {", " /**", " * @template T", " * @param {T} x", " * @param {T} y", " */", " function f(x, y) {}", " f(x, 5);", "}", "g('asdf');")); typeCheck(LINE_JOINER.join( "function g(/** ? */ x) {", " /**", " * @template T", " * @param {(T|number)} x", " */", " function f(x) {}", " f(x)", "}")); // TODO(blickly): Catching the INVALID_ARGUMENT_TYPE here requires // return-type unification. typeCheck(LINE_JOINER.join( "function g(x) {", " /**", " * @template T", " * @param {T} x", " * @return {T}", " */", " function f(x) { return x; }", " f(x) - 5;", " x = 'asdf';", "}", "g('asdf');")); // Empty instantiations typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {(T|number)} x", " */", "function f(x) {}", "f(123);")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {(T|null)} x", " * @param {(T|number)} y", " */", "function f(x, y) {}", "f(null, 'str');")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){};", "/**", " * @template T", " * @param {(T|Foo)} x", " * @param {(T|number)} y", " */", "function f(x, y) {}", "f(new Foo(), 'str');")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {function(T):T} f", " * @param {T} x", " */", "function apply(f, x) { return f(x); }", "/** @type {string} */", "var out;", "var result = apply(function(x){ out = x; return x; }, 0);"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @template T */", "function f(/** T */ x, /** T */ y) {}", "f(1, 'str');"), NewTypeInference.NOT_UNIQUE_INSTANTIATION); typeCheck(LINE_JOINER.join( "/** @template T */", "function /** T */ f(/** T */ x) { return x; }", "f('str') - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " */", "function f(x) {", " /** @constructor */", " function Foo() {", " /** @type {T} */", " this.prop = x;", " }", " return (new Foo()).prop;", "}", "f('asdf') - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {*} x", " */", "function f(x) {}", "f(123);")); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() {}", "/**", " * @template U", " * @param {function(U)} x", " */", "Foo.prototype.f = function(x) { this.f(x); };")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {function(T)} x", " */", "function f(x) {}", "function g(x) {}", "f(g);")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {function(T=)} x", " */", "function f(x) {}", "function g(/** (number|undefined) */ x) {}", "f(g);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {function(...T)} x", " */", "function f(x) {}", "function g() {}", "f(g);")); typeCheck(LINE_JOINER.join( "/**", " * @param {!Array<T>} arr", " * @param {?function(this:S, T, number, ?) : boolean} f", " * @param {S=} opt_obj", " * @return {T|null}", " * @template T,S", " */", "function gaf(arr, f, opt_obj) {", " return null;", "};", "/** @type {number|null} */", "var x = gaf([1, 2, 3], function(x, y, z) { return true; });")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {function(T):boolean} x", " */", "function f(x) {}", "f(function(x) { return 'asdf'; });"), NewTypeInference.RETURN_NONDECLARED_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {function(T)} y", " */", "function f(x, y) {}", "f(123, function(x) { var /** string */ s = x; });"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " */", "function f(x) {}", "var y = null;", "if (!y) {", "} else {", " f(y)", "}")); // Instantiating with ? causes the other types to be forgotten typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {T} y", " * @param {T} z", " * @return {T}", " */", "function f(x, y, z) { return x; }", "var /** null */ n = f(/** @type {?} */ (null), 1, 'asdf');")); // Instantiating with ? causes the other types to be forgotten typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {T} y", " * @param {T} z", " * @return {T}", " */", "function f(x, y, z) { return x; }", "var /** null */ n = f(1, 'asdf', /** @type {?} */ (null));")); typeCheck(LINE_JOINER.join( "/** @template T */", "function f(x) {", " var y = x;", " y();", " return y;", "}", "f(function() {});")); typeCheck(LINE_JOINER.join( "/**", " * @param {T} elem", " * @return {!Array<T>}", " * @template T", " */", "function singleton(elem) {", " return [elem];", "}", "/**", " * @param {function(P): R} fun", " * @param {P} arg", " * @return {R}", " * @template P, R", " */", "function apply(fun, arg) {", " return fun(arg);", "}", // We infer that apply returns Array<?>. // Inferring Array<string> is harder; it's not clear how to do it in a general way. "var /** number */ x = apply(singleton, 'x')[0];")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "function f(x) { return x; }", "/**", " * @template U", " * @param {U} x", " * @return {U}", " */", "function g(x) { return x; }", "/**", " * @template R", " * @param {function(R):R} x", " * @param {function(R):R} y", " */", "function h(x, y) {}", "h(f, g);")); } public void testGenericReturnType() { typeCheck(LINE_JOINER.join( "/** @return {T|string} @template T */", "function f() { return 'str'; }")); } public void testUnificationWithGenericUnion() { typeCheck(LINE_JOINER.join( "/** @constructor @template T */ function Foo(){}", "/**", " * @template T", " * @param {!Array<T>|!Foo<T>} arr", " * @return {T}", " */", "function get(arr) {", " return arr[0];", "}", "var /** null */ x = get([5]);"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {Array<T>} arr", " * @return {T|undefined}", " */", "function get(arr) {", " if (arr === null || arr.length === 0) {", " return undefined;", " }", " return arr[0];", "}", "var /** (number|undefined) */ x = get([5]);")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {Array<T>} arr", " * @return {T|undefined}", " */", "function get(arr) {", " if (arr === null || arr.length === 0) {", " return undefined;", " }", " return arr[0];", "}", "var /** null */ x = get([5]);"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor @template U */ function Foo(/** U */ x){}", "/**", " * @template T", " * @param {U|!Array<T>} arr", " * @return {U}", " */", "Foo.prototype.get = function(arr, /** ? */ opt_arg) {", " return opt_arg;", "}", "var /** null */ x = (new Foo('str')).get([5], 1);"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor @template U */ function Foo(/** U */ x){}", "/**", " * @template T", " * @param {U|!Array<T>} arr", " * @return {U}", " */", "Foo.prototype.get = function(arr, /** ? */ opt_arg) {", " return opt_arg;", "}", "Foo.prototype.f = function() {", " var /** null */ x = this.get([5], 1);", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @constructor", " */", "function Bar() {}", "/** @constructor */", "function Foo() {}", "/**", " * @template T", " * @param {!Bar<(!Bar<T>|!Foo)>} x", " */", "function f(x) {}", "f(/** @type {!Bar<!Bar<number>>} */ (new Bar));")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/**", " * @template T", " * @param {T|null} x", " */", "function f(x) {}", "f(new Foo);")); } public void testBoxedUnification() { typeCheck(LINE_JOINER.join( "/**", " * @param {V} value", " * @constructor", " * @template V", " */", "function Box(value) {};", "/**", " * @constructor", " * @param {K} key", " * @param {V} val", " * @template K, V", " */", "function Map(key, val) {};", "/**", " * @param {!Map<K, (V | !Box<V>)>} inMap", " * @constructor", " * @template K, V", " */", "function WrappedMap(inMap){};", "/** @return {(boolean |!Box<boolean>)} */", "function getUnion(/** ? */ u) { return u; }", "var inMap = new Map('asdf', getUnion(123));", "/** @param {!WrappedMap<string, boolean>} x */", "function getWrappedMap(x) {}", "getWrappedMap(new WrappedMap(inMap));")); } public void testUnification() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){};", "/** @constructor */ function Bar(){};", "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "function id(x) { return x; }", "var /** Bar */ x = id(new Foo);"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "function id(x) { return x; }", "id({}) - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "function id(x) { return x; }", "var /** (number|string) */ x = id('str');")); typeCheck(LINE_JOINER.join( "function f(/** * */ a, /** string */ b) {", " /**", " * @template T", " * @param {T} x", " * @param {T} y", " */", " function f(x, y) {}", " f(a, b);", "}")); typeCheck(LINE_JOINER.join( "function f(/** string */ b) {", " /**", " * @template T", " * @param {T} x", " * @param {T} y", " */", " function f(x, y) {}", " f({p:5, r:'str'}, {p:20, r:b});", "}")); typeCheck(LINE_JOINER.join( "function f(/** string */ b) {", " /**", " * @template T", " * @param {T} x", " * @param {T} y", " */", " function f(x, y) {}", " f({r:'str'}, {p:20, r:b});", "}")); typeCheck(LINE_JOINER.join( "function g(x) {", " /**", " * @template T", " * @param {T} x", " * @param {T} y", " */", " function f(x, y) {}", " var /** boolean */ y = true;", " f(x, y);", "}", "g('str');")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {number} y", " */", "function f(x, y) {}", "f(123, 'asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() {}", "/**", " * @template T", " * @param {Foo<T>} x", " */", "function takesFoo(x) {}", "takesFoo(undefined);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T|undefined} x", " */", "function f(x) {}", "/**", " * @template T", " * @param {T|undefined} x", " */", "function g(x) { f(x); }")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T|undefined} x", " * @return {T}", " */", "function f(x) {", " if (x === undefined) {", " throw new Error('');", " }", " return x;", "}", "/**", " * @template T", " * @param {T|undefined} x", " * @return {T}", " */", "function g(x) { return f(x); }", "g(123) - 5;")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T|undefined} x", " * @return {T}", " */", "function f(x) {", " if (x === undefined) {", " throw new Error('');", " }", " return x;", "}", "/**", " * @template T", " * @param {T|undefined} x", " * @return {T}", " */", "function g(x) { return f(x); }", "g(123) < 'asdf';"), NewTypeInference.INVALID_OPERAND_TYPE); // Here, we infer the type of y to be a loose object with a truthy property prop. // This type doesn't unify with Foo<T>, because they don't have the same structure. // We are testing here that the failed unification doesn't produce a warning. typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @param {T} x", " * @template T", " */", "function Foo(x) {", " this.prop = x;", "}", "/**", " * @param {!Foo<T>} x", " * @template T", " */", "function f(x) {}", "function g(y) {", " if (y.prop) {", " f(y);", " }", "}")); } public void testUnifyObjects() { typeCheck(LINE_JOINER.join( "function f(b) {", " /**", " * @template T", " * @param {T} x", " * @param {T} y", " */", " function f(x, y) {}", " f({p:5, r:'str'}, {p:20, r:b});", "}")); typeCheck(LINE_JOINER.join( "function f(b) {", " /**", " * @template T", " * @param {T} x", " * @param {T} y", " */", " function f(x, y) {}", " f({p:20, r:b}, {p:5, r:'str'});", "}")); typeCheck(LINE_JOINER.join( "function g(x) {", " /**", " * @template T", " * @param {T} x", " * @param {T} y", " */", " function f(x, y) {}", " f({prop: x}, {prop: 5});", "}", "g('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function g(x, cond) {", " /**", " * @template T", " * @param {T} x", " * @param {T} y", " */", " function f(x, y) {}", " var y = cond ? {prop: 'str'} : {prop: 5};", " f({prop: x}, y);", "}", "g({}, true);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function g(x, cond) {", " /**", " * @template T", " * @param {T} x", " * @param {T} y", " */", " function f(x, y) {}", " /** @type {{prop : (string | number)}} */", " var y = cond ? {prop: 'str'} : {prop: 5};", " f({prop: x}, y);", "}", "g({}, true);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {{a: number, b: T}} x", " * @return {T}", " */", "function f(x) { return x.b; }", "f({a: 1, b: 'asdf'}) - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @return {T}", " */", "function f(x) { return x.b; }", "f({b: 'asdf'}) - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testFunctionTypeUnifyUnknowns() { typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "/** @type {function(number)} */", "function g(x) {}", "/** @type {function(?)} */", "function h(x) {}", "f(g, h);")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "/** @type {function(number)} */", "function g(x) {}", "/** @type {function(string)} */", "function h(x) {}", "f(g, h);"), NewTypeInference.NOT_UNIQUE_INSTANTIATION); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "/** @type {function(number)} */", "function g(x) {}", "/** @type {function(?, string)} */", "function h(x, y) {}", "f(g, h);")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "/** @type {function(number=, ...string)} */", "function g(x) {}", "/** @type {function(number=, ...?)} */", "function h(x) {}", "f(g, h);")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "/** @type {function(number):number} */", "function g(x) { return 1; }", "/** @type {function(?):string} */", "function h(x) { return ''; }", "f(g, h);"), NewTypeInference.NOT_UNIQUE_INSTANTIATION); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "/** @constructor */ function Foo() {}", "/** @type {function(new:Foo)} */", "function g() {}", "/** @type {function(new:Foo)} */", "function h() {}", "f(g, h);")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "/** @constructor */ function Foo() {}", "/** @constructor */ function Bar() {}", "/** @type {function(this:Foo)} */", "function g() {}", "/** @type {function(this:Bar)} */", "function h() {}", "f(g, h);"), NewTypeInference.NOT_UNIQUE_INSTANTIATION); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @constructor", " */", "function Foo(x) {}", "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "/** @type {function(!Foo<!Foo<number>>, !Foo<!Foo<?>>)} */", "function g(x, y) {}", "/** @type {function(!Foo<!Foo<?>>, !Foo<!Foo<number>>)} */", "function h(x, y) {}", "f(g, h);")); } public void testInstantiationInsideObjectTypes() { typeCheck(LINE_JOINER.join( "/**", " * @template U", " * @param {U} y", " */", "function g(y) {", " /**", " * @template T", " * @param {{a: U, b: T}} x", " * @return {T}", " */", " function f(x) { return x.b; }", " f({a: y, b: 'asdf'}) - 5;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template U", " * @param {U} y", " */", "function g(y) {", " /**", " * @template T", " * @param {{b: T}} x", " * @return {T}", " */", " function f(x) { return x.b; }", " f({b: y}) - 5;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testInstantiateInsideFunctionTypes() { typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {function(T):T} fun", " */", "function f(x, fun) {}", "function g(x) { return x - 5; }", "f('asdf', g);"), NewTypeInference.NOT_UNIQUE_INSTANTIATION); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {function(T):number} fun", " */", "function f(fun) {}", "function g(x) { return 'asdf'; }", "f(g);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {function(T=)} fun", " */", "function f(fun) {}", "/** @param{string=} x */ function g(x) {}", "f(g);")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {function(...T)} fun", " */", "function f(fun) {}", "/** @param {...number} var_args */ function g(var_args) {}", "f(g);")); } public void testPolymorphicFuncallsFromDifferentScope() { typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "function id(x) { return x; }", "function g() {", " id('asdf') - 5;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {number} y", " */", "function f(x, y) {}", "function g() {", " f('asdf', 'asdf');", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "function g() {", " f(123, 'asdf');", "}"), NewTypeInference.NOT_UNIQUE_INSTANTIATION); } public void testOpacityOfTypeParameters() { typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " */", "function f(x) {", " x - 5;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {{ a: T }} x", " */", "function f(x) {", " x.a - 5;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {function(T):T} fun", " */", "function f(x, fun) {", " fun(x) - 5;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "function f(x) {", " return 5;", "}"), NewTypeInference.RETURN_NONDECLARED_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " */", "function f(x) {", " var /** ? */ y = x;", "}")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @return {(T|number)}", " */", "function f(x) {", " var y;", " if (1 < 2) {", " y = x;", " } else {", " y = 123;", " }", " return y;", "}")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @return {(T|number)}", " */", "function f(x) {", " var y;", " if (1 < 2) {", " y = x;", " } else {", " y = 123;", " }", " return y;", "}", "f(123) - 5;")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @return {(T|number)}", " */", "function f(x) {", " var y;", " if (1 < 2) {", " y = x;", " } else {", " y = 123;", " }", " return y;", "}", "var /** (number|boolean) */ z = f('asdf');"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " */", "function f(x) {", " var /** T */ y = x;", " y - 5;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T, U", " * @param {T} x", " * @param {U} y", " */", "function f(x, y) {", " x = y;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testGenericClassInstantiation() { typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @constructor", " */", "function Foo(x) {}", "/** @param {T} y */", "Foo.prototype.bar = function(y) {}", "new Foo('str').bar(5)"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @constructor", " */", "function Foo(x) {}", "/** @type {function(T)} y */", "Foo.prototype.bar = function(y) {};", "new Foo('str').bar(5)"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @constructor", " */", "function Foo(x) { /** @type {T} */ this.x = x; }", "/** @return {T} */", "Foo.prototype.bar = function() { return this.x; };", "new Foo('str').bar() - 5"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @constructor", " */", "function Foo(x) { /** @type {T} */ this.x = x; }", "/** @type {function() : T} */", "Foo.prototype.bar = function() { return this.x; };", "new Foo('str').bar() - 5"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @constructor", " */", "function Foo(x) {}", "/** @type {function(this:Foo<T>, T)} */", "Foo.prototype.bar = function(x) { this.x = x; };", "new Foo('str').bar(5)"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @constructor", " */", "function Foo(x) {}", "/** @param {!Foo<number>} x */", "function f(x) {}", "f(new Foo(7));")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @constructor", " */", "function Foo(x) {}", "/** @param {Foo<number>} x */", "function f(x) {}", "f(new Foo('str'));"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @constructor", " */", "function Foo(x) {}", "/** @param {T} x */", "Foo.prototype.method = function(x) {};", "/** @param {!Foo<number>} x */", "function f(x) { x.method('asdf'); }"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() {}", "/** @param {T} x */", "Foo.prototype.method = function(x) {};", "var /** @type {Foo<string>} */ foo = null;", "foo.method('asdf');"), NewTypeInference.PROPERTY_ACCESS_ON_NONOBJECT); } public void testLooserCheckingForInferredProperties() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo(x) { this.prop = x; }", "function f(/** !Foo */ obj) {", " obj.prop = true ? 1 : 'asdf';", " obj.prop - 5;", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo(x) { this.prop = x; }", "function f(/** !Foo */ obj) {", " if (!(typeof obj.prop == 'number')) {", " obj.prop < 'asdf';", " }", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo(x) { this.prop = x; }", "function f(/** !Foo */ obj) {", " obj.prop = true ? 1 : 'asdf';", " obj.prop - 5;", " obj.prop < 'asdf';", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function /** string */ f(/** ?number */ x) {", " var o = { prop: 'str' };", " if (x) {", " o.prop = x;", " }", " return o.prop;", "}"), NewTypeInference.RETURN_NONDECLARED_TYPE); } public void testInheritanceWithGenerics() { typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @interface", " */", "function I() {}", "/** @param {T} x */", "I.prototype.bar = function(x) {};", "/** @constructor @implements {I<number>} */", "function Foo() {}", "Foo.prototype.bar = function(x) {};", "(new Foo).bar(123);")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @interface", " */", "function I() {}", "/** @param {T} x */", "I.prototype.bar = function(x) {};", "/** @constructor @implements {I<number>} */", "function Foo() {}", "Foo.prototype.bar = function(x) {};", "(new Foo).bar('str');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @interface", " */", "function I() {}", "/** @param {T} x */", "I.prototype.bar = function(x) {};", "/** @constructor @implements {I<number>} */", "function Foo() {}", "/** @override */", "Foo.prototype.bar = function(x) {};", "new Foo().bar('str');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @interface", " */", "function I() {}", "/** @param {T} x */", "I.prototype.bar = function(x) {};", "/**", " * @template U", " * @constructor", " * @implements {I<U>}", " * @param {U} x", " */", "function Foo(x) {}", "Foo.prototype.bar = function(x) {};{}", "new Foo(5).bar('str');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @interface", " */", "function I() {}", "/** @param {T} x */", "I.prototype.bar = function(x) {};", "/** @constructor @implements {I<number>} */", "function Foo() {}", "Foo.prototype.bar = function(x) {};", "/** @param {I<string>} x */ function f(x) {};", "f(new Foo());"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @interface", " */", "function I() {}", "/** @param {T} x */", "I.prototype.bar = function(x) {};", "/** @constructor @implements {I<number>} */", "function Foo() {}", "/** @param {string} x */", "Foo.prototype.bar = function(x) {};"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @interface", " */", "function I() {}", "/** @param {T} x */", "I.prototype.bar = function(x) {};", "/**", " * @template T", " * @param {T} x", " * @constructor @implements {I<number>}", " */", "function Foo(x) {}", "/** @param {T} x */", "Foo.prototype.bar = function(x) {};"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @constructor", " */", "function Foo() {}", "/** @param {T} x */", "Foo.prototype.method = function(x) {};", "/**", " * @template T", " * @constructor", " * @extends {Foo<T>}", " * @param {T} x", " */", "function Bar(x) {}", "/** @param {number} x */", "Bar.prototype.method = function(x) {};"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @constructor", " */", "function High() {}", "/** @param {Low<T>} x */", "High.prototype.method = function(x) {};", "/**", " * @template T", " * @constructor", " * @extends {High<T>}", " */", "function Low() {}")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @constructor", " */", "function High() {}", "/** @param {Low<number>} x */", "High.prototype.method = function(x) {};", "/**", " * @template T", " * @constructor", " * @extends {High<T>}", " */", "function Low() {}")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @constructor", " */", "function High() {}", "/** @param {Low<T>} x */ // error, low is not templatized", "High.prototype.method = function(x) {};", "/**", " * @constructor", " * @extends {High<number>}", " */", "function Low() {}"), JSTypeCreatorFromJSDoc.INVALID_GENERICS_INSTANTIATION); // BAD INHERITANCE, WE DON'T HAVE A WARNING TYPE FOR THIS // TODO(dimvar): fix typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @interface", " */", "function I() {}", "/**", " * @template T", " * @constructor", " * @implements {I<T>}", " * @extends {Bar}", " */", "function Foo(x) {}", "/**", " * @constructor", " * @implements {I<number>}", " */", "function Bar(x) {}")); typeCheck(LINE_JOINER.join( "/**", " * @interface", " * @template T", " */", "function Foo() {}", "/** @constructor @implements {Foo<number>} */", "function A() {}", "var /** Foo<number> */ x = new A();")); typeCheck(LINE_JOINER.join( "/** @interface */", "function High() {}", "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "High.prototype.method = function (x) {};", "/** @constructor @implements {High} */", "function Low() {}", "Low.prototype.method = function (x) {", " return x;", "};", "(new Low).method(123) - 123;")); typeCheck(LINE_JOINER.join( "/** @interface */", "function High() {}", "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "High.prototype.method = function (x) {};", "/** @constructor @implements {High} */", "function Low() {}", "Low.prototype.method = function (x) {", " return x;", "};", "(new Low).method('str') - 123;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @interface", " */", "function High() {}", "/** @return {T} */", "High.prototype.method = function () {};", "/** @constructor @implements {High} */", "function Low() {}", "Low.prototype.method = function () { return /** @type {?} */ (null); };", "(new Low).method() - 123;", "(new Low).method() < 'asdf';")); } public void testGenericsSubtyping() { typeCheck(LINE_JOINER.join( "/** @interface */ function Parent() {}", "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "Parent.prototype.method = function(x, y){};", "/** @constructor @implements {Parent} */", "function Child() {}", "/**", " * @param {number} x", " * @param {number} y", " */", "Child.prototype.method = function(x, y){};"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); typeCheck(LINE_JOINER.join( "/** @interface */ function Parent() {}", "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "Parent.prototype.method = function(x, y){};", "/** @constructor @implements {Parent} */", "function Child() {}", "/**", " * @param {?} x", " * @param {number} y", " */", "Child.prototype.method = function(x, y){};"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); typeCheck(LINE_JOINER.join( "/** @interface */ function Parent() {}", "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "Parent.prototype.method = function(x, y){};", "/** @constructor @implements {Parent} */", "function Child() {}", "/**", " * @param {*} x", " * @param {*} y", " */", "Child.prototype.method = function(x, y){};")); typeCheck(LINE_JOINER.join( "/** @interface */ function Parent() {}", "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "Parent.prototype.method = function(x, y){};", "/** @constructor @implements {Parent} */", "function Child() {}", "/**", " * @param {?} x", " * @param {?} y", " */", "Child.prototype.method = function(x, y){};")); typeCheck(LINE_JOINER.join( "/** @interface */ function Parent() {}", "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "Parent.prototype.method = function(x){};", "/** @constructor @implements {Parent} */", "function Child() {}", "/**", " * @param {?} x", " * @return {?}", " */", "Child.prototype.method = function(x){ return x; };")); typeCheck(LINE_JOINER.join( "/** @interface */ function Parent() {}", "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "Parent.prototype.method = function(x){};", "/** @constructor @implements {Parent} */", "function Child() {}", "/**", " * @param {*} x", " * @return {?}", " */", "Child.prototype.method = function(x){ return x; };")); typeCheck(LINE_JOINER.join( "/** @interface */ function Parent() {}", "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "Parent.prototype.method = function(x){};", "/** @constructor @implements {Parent} */", "function Child() {}", "/**", " * @param {*} x", " * @return {*}", " */", "Child.prototype.method = function(x){ return x; };"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); typeCheck(LINE_JOINER.join( "/** @interface */ function Parent() {}", "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "Parent.prototype.method = function(x){};", "/** @constructor @implements {Parent} */", "function Child() {}", "/**", " * @param {number} x", " * @return {number}", " */", "Child.prototype.method = function(x){ return x; };"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); typeCheck(LINE_JOINER.join( "/** @interface */ function Parent() {}", "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "Parent.prototype.method = function(x){};", "/** @constructor @implements {Parent} */", "function Child() {}", "/**", " * @param {?} x", " * @return {*}", " */", "Child.prototype.method = function(x){ return x; };"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); typeCheck(LINE_JOINER.join( "/** @interface */ function Parent() {}", "/**", " * @template T", " * @param {function(T, T) : boolean} x", " */", "Parent.prototype.method = function(x){};", "/** @constructor @implements {Parent} */", "function Child() {}", "/**", " * @param {function(number, number) : boolean} x", " */", "Child.prototype.method = function(x){ return x; };"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " */", "function f(x) {}", "/** @param {function(number, number)} x */", "function g(x) {}", "g(f);")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " */", "function f(x) {}", "/** @param {function()} x */", "function g(x) {}", "g(f);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */", "function Parent() {}", "/**", " * @template T", " * @param {T} x", " */", "Parent.prototype.method = function(x) {};", "/**", " * @constructor", " * @implements {Parent}", " */", "function Child() {}", "/**", " * @template U", " * @param {U} x", " */", "Child.prototype.method = function(x) {};")); typeCheck(LINE_JOINER.join( "/** @interface */ function Parent() {}", "/** @param {string} x */", "Parent.prototype.method = function(x){};", "/** @constructor @implements {Parent} */", "function Child() {}", "/**", " * @template T", " * @param {T} x", " */", "Child.prototype.method = function(x){};")); typeCheck(LINE_JOINER.join( "/** @interface */ function Parent() {}", "/** @param {*} x */", "Parent.prototype.method = function(x){};", "/** @constructor @implements {Parent} */", "function Child() {}", "/**", " * @template T", " * @param {T} x", " */", "Child.prototype.method = function(x){};")); typeCheck(LINE_JOINER.join( "/** @interface */ function Parent() {}", "/** @param {?} x */", "Parent.prototype.method = function(x){};", "/** @constructor @implements {Parent} */", "function Child() {}", "/**", " * @template T", " * @param {T} x", " */", "Child.prototype.method = function(x){};")); // This shows a bug in subtyping of generic functions. // We don't catch the invalid prop override. typeCheck(LINE_JOINER.join( "/** @interface */ function Parent() {}", "/**", " * @param {string} x", " * @param {number} y", " */", "Parent.prototype.method = function(x, y){};", "/** @constructor @implements {Parent} */", "function Child() {}", "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "Child.prototype.method = function(x, y){};")); // This shows a bug in subtyping of generic functions. // We don't catch the invalid prop override. typeCheck(LINE_JOINER.join( "/** @interface */ function Parent() {}", "/**", " * @template A, B", " * @param {A} x", " * @param {B} y", " * @return {A}", " */", "Parent.prototype.method = function(x, y){};", "/** @constructor @implements {Parent} */", "function Child() {}", "/**", " * @template A, B", " * @param {A} x", " * @param {B} y", " * @return {B}", " */", "Child.prototype.method = function(x, y){ return y; };")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @constructor", " */", "function Foo(x) {}", "/**", " * @template T", " * @param {?} x", " * @return {!Foo<!Foo<T>>}", " */", "function f(x) {", " return new Foo(new Foo(x));", "}")); } public void testGenericsVariance() { // Array generic parameter is co-variant typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @constructor @extends {Foo} */ function Bar() {}", "var /** Array<Foo> */ a = [new Bar];")); typeCheck(LINE_JOINER.join( "/**", " * @param {!Array<number|string>} x", " * @return {!Array<number>}", " */", "function f(x) {", " return /** @type {!Array<number>} */ (x);", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @constructor @extends {Foo} */ function Bar() {}", "var /** Array<Bar> */ a = [new Foo];"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/**", " * @template T", " * @param {T} x", " * @param {!Array<null|T>} y", " */", "function f(x, y) {}", "f(new Foo, [new Foo]);")); typeCheck(LINE_JOINER.join( "/** @constructor @param {T} x @template T */ function Gen(x){}", "/** @constructor */ function Foo() {}", "/** @constructor @extends {Foo} */ function Bar() {}", "var /** Gen<Foo> */ a = new Gen(new Bar);")); typeCheck(LINE_JOINER.join( "/** @constructor @param {T} x @template T */ function Gen(x){}", "/** @constructor */ function Foo() {}", "/** @constructor @extends {Foo} */ function Bar() {}", "var /** Gen<Bar> */ a = new Gen(new Foo);"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @param {!Object} x", " * @return {!Promise<?Object>}", " */", "function foo(x) {", " return Promise.resolve(x);", "}")); } public void testCastsOfGenericTypes() { typeCheck(LINE_JOINER.join( "/** @constructor @struct */", "function Bar() {", " this.prop = 123;", "}", "function g(/** !Array<!Bar> */ x) {", " return /** @type {!Array<{prop}>} */ (x);", "}")); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " * @param {T} x", " */", "function Bar(x) {}", "var x = /** @type {!Bar<(number|null)>} */ (new Bar(null));")); typeCheck(LINE_JOINER.join( "/** @constructor @struct */", "function Foo() {}", "/** @constructor @struct @extends {Foo} */", "function Bar() {}", "/** @constructor @template T,U */", "function Baz() {}", "function f(/** !Baz<!Bar,?> */ x) {", " return /** @type {!Baz<!Foo,?>} */ (x);", "}")); } public void testInferredArrayGenerics() { typeCheck("/** @const */ var x = [];"); typeCheck(LINE_JOINER.join( "/** @const */ var x = [1, 'str'];", "function g() { x; }"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @constructor @extends {Foo} */ function Bar() {}", "/** @const */ var x = [new Foo, new Bar];", "function g() { x; }"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); typeCheck( "var /** Array<string> */ a = [1, 2];", NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "var arr = [];", "var /** Array<string> */ as = arr;")); typeCheck(LINE_JOINER.join( "var arr = [1, 2, 3];", "var /** Array<string> */ as = arr;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "var /** Array<string> */ a = [new Foo, new Foo];"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @constructor @extends {Foo} */ function Bar() {}", "var /** Array<Foo> */ a = [new Foo, new Bar];")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @constructor @extends {Foo} */ function Bar() {}", "var /** Array<Bar> */ a = [new Foo, new Bar];"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @const */ var x = [1, 2, 3];", "function g() { var /** Array<string> */ a = x; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @constructor @extends {Foo} */ function Bar() {}", "/** @const */ var x = [new Foo, new Foo];", "function g() { var /** Array<Bar> */ a = x; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); // [] is inferred as Array<?>, so we miss the warning here typeCheck(LINE_JOINER.join( "/** @const */", "var x = [];", "function f() {", " x[0] = 'asdf';", "}", "function g() {", " return x[0] - 5;", "}")); } public void testSpecializedInstanceofCantGoToBottom() { typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "ns.f = function() {};", "if (ns.f instanceof Function) {}")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){}", "/** @const */ var ns = {};", "ns.f = new Foo;", "if (ns.f instanceof Foo) {}")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){}", "/** @constructor */ function Bar(){}", "/** @const */ var ns = {};", "ns.f = new Foo;", "if (ns.f instanceof Bar) {}")); } public void testDeclaredGenericArrayTypes() { typeCheck(LINE_JOINER.join( "/** @type {Array<string>} */", "var arr = ['str'];", "arr[0]++;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "var arr = ['str'];", "arr[0]++;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function foo (/** Array<string> */ a) {}", "/** @type {Array<number>} */", "var b = [1];", "foo(b);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function foo (/** Array<string> */ a) {}", "foo([1]);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @type {!Array<number>} */", "var arr = [1, 2, 3];", "arr[0] = 'str';"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @type {!Array<number>} */", "var arr = [1, 2, 3];", "arr['0'] = 'str';"), NewTypeInference.INVALID_INDEX_TYPE); // We warn here even though the declared type of the lvalue includes null. typeCheck(LINE_JOINER.join( "/** @type {Array<number>} */", "var arr = [1, 2, 3];", "arr[0] = 'str';"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** Array<number> */ arr) {", " arr[0] = 'str';", "}"), NewTypeInference.NULLABLE_DEREFERENCE, NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @const */", "var arr = [1, 2, 3];", "arr[0] = 'str';"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "var arr = [1, 2, 3];", "arr[0] = 'str';")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Super(){}", "/** @constructor @extends {Super} */ function Sub(){}", "/** @type {!Array<Super>} */ var arr = [new Sub];", "arr[0] = new Super;")); typeCheck(LINE_JOINER.join( "/** @type {Array<number>} */ var arr = [];", "arr[0] = 'str';"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @type {Array<number>} */ var arr = [];", "(function (/** Array<string> */ x){})(arr);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function /** string */ f(/** !Array<number> */ arr) {", " return arr[0];", "}"), NewTypeInference.RETURN_NONDECLARED_TYPE); // TODO(blickly): Would be nice if we caught the MISTYPED_ASSIGN_RHS here typeCheck(LINE_JOINER.join( "var arr = [];", "arr[0] = 5;", "var /** Array<string> */ as = arr;")); } public void testInferConstTypeFromGoogGetMsg() { typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @const */", "var s = goog.getMsg('asdf');", "s - 1;"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testInferConstTypeForMethods() { typeCheck(LINE_JOINER.join( "/** @const */ var ns = {}", "/** @return {string} */ ns.f = function() { return 'str'; };", "/** @const */ var s = ns.f();", "function f() {", " s - 1;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @return {string} */", "Foo.prototype.method = function() { return 'asdf'; };", "function f(/** !Foo */ obj) {", " /** @const */", " var x = obj.method();", "}")); typeCheck(LINE_JOINER.join( "function foo(bar) {", " /** @const */", " var ns = {};", " /** @type {function():number} */", " ns.f = bar;", " /** @const */", " var x = ns.f();", "};")); } public void testInferConstTypeFromGenerics() { typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "function f(x) { return x; }", "/** @const */ var x = f(5);", "function g() { var /** null */ n = x; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @constructor", " */", "function Foo(x) {}", "/** @const */ var foo_str = new Foo('str');", "function g() { var /** !Foo<number> */ foo_num = foo_str; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "function f(x) { return x; }", "/** @const */ var x = f(f ? 'str' : 5);", "function g() { x; }")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {T} y", " * @return {T}", " */", "function f(x, y) { return true ? y : x; }", "/** @const */ var x = f(5, 'str');", "function g() { var /** null */ n = x; }"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE, NewTypeInference.NOT_UNIQUE_INSTANTIATION); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "function f(x) { return x; }", "/** @const */", "var y = f(1, 2);", "function g() { y; }"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE, NewTypeInference.WRONG_ARGUMENT_COUNT); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "function f(x) { return x; }", "/** @const */", "var y = f();", "function g() { y; }"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE, NewTypeInference.WRONG_ARGUMENT_COUNT); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {!IArrayLike<T>} x", " * @return {T}", " */", "function f(x) { return x[0]; }", "/** @const */", "var c = f({ length: 1 });", "function g() { return c; }"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " * @param {T=} x", " */", "function Foo(x) {}", "/** @const */", "var c = new Foo;", "(function() { var /** !Foo */ x = c; });")); typeCheck( LINE_JOINER.join( "/** @type {!Array<number>} */", "var arr = [1,2,3];", "/** @const */", "var c = arr.shift();", "function f() {", " var /** string */ s = c;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testInferConstTypeFromNestedObjectLiterals() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {};", "/** @param {!{service: !{eventLogging: !Foo }}} x */", "function f(x) {", " /** @const */", " var z = x.service.eventLogging;", "}")); typeCheck(LINE_JOINER.join( "/** @param {{p1: {p2: string }}} x */", "function f(x) {", " /** @const */", " var z = x.p1.p2;", " return z - 5;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testDifficultClassGenericsInstantiation() { typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @constructor", " * @param {T} x", " */", "function Foo(x) {}", "/** @param {Bar<T>} x */", "Foo.prototype.method = function(x) {};", "/**", " * @template T", " * @constructor", " * @param {T} x", " */", "function Bar(x) {}", "/** @param {Foo<T>} x */", "Bar.prototype.method = function(x) {};", "(new Foo(123)).method(new Bar('asdf'));"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @constructor", " * @param {T} x", " */", "function Foo(x) {}", "/** @param {Foo<Foo<T>>} x */", "Foo.prototype.method = function(x) {};", "(new Foo(123)).method(new Foo(new Foo('asdf')));"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @interface\n @template T */function A() {};", "/** @return {T} */A.prototype.foo = function() {};", "/** @interface\n @template U\n @extends {A<U>} */function B() {};", "/** @constructor\n @implements {B<string>} */function C() {};", "/** @return {string}\n @override */", "C.prototype.foo = function() { return 123; };"), NewTypeInference.RETURN_NONDECLARED_TYPE); // Polymorphic method on a generic class. typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " * @param {T} x", " */", "function Foo(x) {}", "/**", " * @template U", " * @param {U} x", " * @return {U}", " */", "Foo.prototype.method = function(x) { return x; };", "(new Foo(123)).method('asdf') - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); // typeCheck(LINE_JOINER.join( // "/**", // " * @template T", // " * @constructor", // " */", // "function Foo() {}", // "/** @param {T} x */", // "Foo.prototype.method = function(x) {};", // "", // "/**", // " * @template T", // " * @constructor", // " * @extends {Foo<T>}", // " * @param {T} x", // " */", // "function Bar(x) {}", // // Invalid instantiation here, must be T, o/w bugs like the call to f // "/** @param {number} x */", // "Bar.prototype.method = function(x) {};", // "", // "/** @param {!Foo<string>} x */", // "function f(x) { x.method('sadf'); };", // "f(new Bar('asdf'));"), // NewTypeInference.FAILED_TO_UNIFY); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T,U", " */", "function Foo() {}", "Foo.prototype.m1 = function() {", " this.m2(123);", "};", "/**", " * @template U", " * @param {U} x", " */", "Foo.prototype.m2 = function(x) {};")); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T, U", " */", "function Foo() {}", "/**", " * @template T", // shadows Foo#T, U still visible " * @param {T} x", " * @param {U} y", " */", "Foo.prototype.method = function(x, y) {};", "var obj = /** @type {!Foo<number, number>} */ (new Foo);", "obj.method('asdf', 123);", // OK "obj.method('asdf', 'asdf');"), // warning NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @interface", " * @template T", " */", "function High() {}", "/** @param {T} x */", "High.prototype.method = function(x) {};", "/**", " * @constructor", " * @implements {High<number>}", " */", "function Low() {}", "/**", " * @template T", " * @param {T} x", " */", "Low.prototype.method = function(x) {};")); typeCheck(LINE_JOINER.join( "/**", " * @template T, U", " * @constructor", " */", "function Foo() {}", "/**", " * @template T", " * @param {!Foo<T,T>} x", " */", "function f(x) {}", "/**", " * @param {!Foo<?,number>} x", " */", "function g(x) {", " f(x);", "}")); typeCheck(LINE_JOINER.join( "/**", " * @template T, U", " * @constructor", " */", "function Foo() {}", "/**", " * @template T", " * @param {!Foo<T,T>} x", " */", "function f(x) {}", "/**", " * @param {!Foo<Foo<?,?>,Foo<number,number>>} x", " */", "function g(x) {", " f(x);", "}")); } public void testNominalTypeUnification() { typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T, U", " * @param {T} x", " */", "function Foo(x) {}", "/**", " * @template T", // {!Foo<T>} is instantiating only the 1st template var of Foo " * @param {!Foo<T>} x", " */", "function fn(x) {}", "fn(new Foo('asdf'));")); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template S, T", " * @param {S} x", " */", "function Foo(x) {", " /** @type {S} */ this.prop = x;", "}", "/**", " * @template T", // {!Foo<T>} is instantiating only the 1st template var of Foo " * @param {!Foo<T>} x", " * @return {T}", " */", "function fn(x) { return x.prop; }", "fn(new Foo('asdf')) - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testCasts() { typeCheck( "(/** @type {number} */ ('asdf'));", NewTypeInference.INVALID_CAST); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " */", "function f(x) {", " /** @type{!Object} */ (x);", "}")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T|string} x", " */", "function f(x) {", " /** @type{!Object} */ (x);", "}")); typeCheck(LINE_JOINER.join( "function f(/** (number|string) */ x) {", " var y = /** @type {number} */ (x);", "}")); typeCheck("(/** @type {(number|string)} */ (1));"); typeCheck("(/** @type {number} */ (/** @type {?} */ ('asdf')))"); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Parent() {}", "/** @constructor @extends {Parent} */", "function Child() {}", "/** @type {Child|null} */ (new Parent);")); typeCheck(LINE_JOINER.join( "function f(/** (number|string) */ x) {", " return /** @type {number|boolean} */ (x);", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function High() {}", "/** @constructor @extends {High} */", "function Low() {}", "/** @constructor */", "function Foo() {}", "function f(/** (!Foo|!Low) */ x) {", " return /** @type {!High} */ (x);", "}")); typeCheck(LINE_JOINER.join( "function f(/** function(function(number)=) */ f1) {", " return /** @type {function(function(string)=)} */ (f1);", "}"), NewTypeInference.INVALID_CAST); typeCheck(LINE_JOINER.join( "function f(/** function(... function(number)) */ f1) {", " return /** @type {function(... function(string))} */ (f1);", "}"), NewTypeInference.INVALID_CAST); } public void testOverride() { typeCheck(LINE_JOINER.join( "/** @interface */", "function Intf() {}", "/** @param {(number|string)} x */", "Intf.prototype.method = function(x) {};", "/**", " * @constructor", " * @implements {Intf}", " */", "function C() {}", "/** @override */", "C.prototype.method = function (x) { x - 1; };", "(new C).method('asdf');"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */", "function Intf() {}", "/** @param {(number|string)} x */", "Intf.prototype.method = function(x) {};", "/**", " * @constructor", " * @implements {Intf}", " */", "function C() {}", "/** @inheritDoc */", "C.prototype.method = function (x) { x - 1; };", "(new C).method('asdf');"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @override */", "Foo.prototype.method = function() {};"), GlobalTypeInfo.UNKNOWN_OVERRIDE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @inheritDoc */", "Foo.prototype.method = function() {};"), GlobalTypeInfo.UNKNOWN_OVERRIDE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function High() {}", "/** @param {number=} x */", "High.prototype.f = function(x) {};", "/** @constructor @extends {High} */", "function Low() {}", "/** @override */", "Low.prototype.f = function(x) {};", "(new Low).f();", "(new Low).f('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function F() {}", "/**", " * @param {string} x", " * @param {...*} var_args", " * @return {*}", " */", "F.prototype.method;", "/**", " * @constructor", " * @extends {F}", " */", "function G() {}", "/** @override */", "G.prototype.method = function (x, opt_index) {};", "(new G).method('asdf');")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function F() {}", "/**", " * @param {string} x", " * @param {...number} var_args", " * @return {number}", " */", "F.prototype.method;", "/**", " * @constructor", " * @extends {F}", " */", "function G() {}", "/** @override */", "G.prototype.method = function (x, opt_index) {};", "(new G).method('asdf', 'asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE, NewTypeInference.MISSING_RETURN_STATEMENT); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prototype.m = function() {};", "/** @constructor @extends {Foo}*/", "function Bar() {}", "/**", " * @param {number=} x", " * @override", " */", "Bar.prototype.m = function(x) {};", "(new Bar).m(123);")); typeCheck("(123).toString(16);"); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor @extends {Foo}*/", "function Bar() {}", "/**", " * @param {number=} x", " * @override", " */", "Bar.prototype.m = function(x) {};", "(new Bar).m(123);"), GlobalTypeInfo.UNKNOWN_OVERRIDE); typeCheck(LINE_JOINER.join( "/** @interface */", "var MassObject = function() {};", "/** @return {number} */", "MassObject.prototype.getBottomBody;", "/**", "* @constructor", "* @abstract", "* @implements {MassObject}", "*/", "var AbstractMassObject = function() {};", "/**", " * @override", " * @abstract", " */", "AbstractMassObject.prototype.getBottomBody = function() {};")); } public void testOverrideNoInitializer() { typeCheck(LINE_JOINER.join( "/** @interface */ function Intf() {}", "/** @param {number} x */", "Intf.prototype.method = function(x) {};", "/** @interface @extends {Intf} */", "function Subintf() {}", "/** @override */", "Subintf.prototype.method;", "function f(/** !Subintf */ x) { x.method('asdf'); }"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */ function Intf() {}", "/** @param {number} x */", "Intf.prototype.method = function(x) {};", "/** @interface @extends {Intf} */", "function Subintf() {}", "Subintf.prototype.method;", "function f(/** !Subintf */ x) { x.method('asdf'); }"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */ function Intf() {}", "/** @param {number} x */", "Intf.prototype.method = function(x) {};", "/** @constructor @implements {Intf} */", "function C() {}", "/** @override */", "C.prototype.method = (function(){ return function(x){}; })();", "(new C).method('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */ function Intf() {}", "/** @param {number} x */", "Intf.prototype.method = function(x) {};", "/** @constructor @implements {Intf} */", "function C() {}", "C.prototype.method = (function(){ return function(x){}; })();", "(new C).method('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */ function Intf() {}", "/** @type {string} */", "Intf.prototype.s;", "/** @constructor @implements {Intf} */", "function C() {}", "/** @override */", "C.prototype.s = 'str2';", "(new C).s - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */ function Intf() {}", "/** @type {string} */", "Intf.prototype.s;", "/** @constructor @implements {Intf} */", "function C() {}", "/** @type {number} @override */", "C.prototype.s = 72;"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); typeCheck(LINE_JOINER.join( "/** @interface */ function Intf() {}", "/** @type {string} */", "Intf.prototype.s;", "/** @constructor @implements {Intf} */", "function C() {}", "/** @override */", "C.prototype.s = 72;"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testFunctionConstructor() { typeCheck(LINE_JOINER.join( "/** @type {Function} */ function topFun() {}", "topFun(1);")); typeCheck( "/** @type {Function} */ function topFun(x) { return x - 5; }"); typeCheck(LINE_JOINER.join( "function f(/** Function */ fun) {}", "f(function g(x) { return x - 5; });")); typeCheck( "function f(/** !Function */ fun) { return new fun(1, 2); }"); typeCheck("function f(/** !Function */ fun) { [] instanceof fun; }"); } public void testConditionalExBranch() { typeCheck(LINE_JOINER.join( "function g() { throw 1; }", "function f() {", " try {", " if (g()) {}", " } catch (e) {}", "};")); } public void testGenericInterfaceDoesntCrash() { typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @interface @template T */", "ns.Interface = function(){}")); } public void testGetpropOnTopDoesntCrash() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {};", "/** @type {*} */ Foo.prototype.stuff;", "function f(/** !Foo */ foo, x) {", " (foo.stuff.prop = x) || false;", "};"), NewTypeInference.ADDING_PROPERTY_TO_NON_OBJECT); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {};", "/** @type {*} */ Foo.prototype.stuff;", "function f(/** Foo */ foo) {", " foo.stuff.prop || false;", "};"), NewTypeInference.NULLABLE_DEREFERENCE); } public void testImplementsGenericInterfaceDoesntCrash() { typeCheck(LINE_JOINER.join( "/** @interface @template Z */", "function Foo(){}", "Foo.prototype.getCount = function /** number */ (){};", "/**", " * @constructor @implements {Foo<T>}", " * @template T", " */", "function Bar(){}", "Bar.prototype.getCount = function /** number */ (){};")); } public void testDeadCodeDoesntCrash() { typeCheck(LINE_JOINER.join( "function f() {", " throw 'Error';", " return 5;", "}")); } public void testSpecializeFunctionToNominalDoesntCrash() { typeCheck(LINE_JOINER.join( "/** @interface */ function Foo() {}", "function reqFoo(/** Foo */ foo) {};", "/** @param {Function} fun */", "function f(fun) {", " reqFoo(fun);", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){}", "function f(x) {", " if (typeof x == 'function') {", " var /** !Foo */ y = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** !Object */ x) {", " if (x instanceof Function) {", " x(123);", " }", "}")); } public void testPrototypeMethodOnUndeclaredDoesntCrash() { typeCheck( "Foo.prototype.method = function(){ this.x = 5; };", // VarCheck.UNDEFINED_VAR_ERROR, NewTypeInference.GLOBAL_THIS); } public void testFunctionGetpropDoesntCrash() { typeCheck(LINE_JOINER.join( "function g() {}", "function f() {", " g();", " return g.prop;", "}"), NewTypeInference.INEXISTENT_PROPERTY); } public void testUnannotatedBracketAccessDoesntCrash() { typeCheck(LINE_JOINER.join( "Object.prototype.array;", "function f(foo, i, j) {", " foo.array[i][j] = 5;", "}")); } public void testUnknownTypeReferenceDoesntCrash() { typeCheck(LINE_JOINER.join( "/** @interface */ function I(){}", "/** @type {function(NonExistentClass)} */", "I.prototype.method;"), GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME); } public void testSpecializingTypeVarDoesntGoToBottom() { typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " */", "function f(x) {", " if (typeof x === 'string') {", " return x.length;", " }", "}")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " */", "function f(x) {", " if (typeof x === 'string') {", " return x - 5;", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " */", "function f(x) {", " if (typeof x === 'string') {", " return x - 5;", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " */", "function f(x) {", " if (typeof x === 'number') {", " (function(/** string */ y){})(x);", " }", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testBottomPropAccessDoesntCrash() { // Questionable whether or not we should warn here. We just do it because obj being null // (instead of bottom) in the THEN branch makes other more useful cases behave better. typeCheck(LINE_JOINER.join( "Object.prototype.prop;", "var obj = null;", "if (obj) obj.prop += 7;"), NewTypeInference.ADDING_PROPERTY_TO_NON_OBJECT); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prototype.m = function(/** number */ x) {};", "/** @constructor */", "function Bar() {}", "Bar.prototype.m = function(/** string */ x) {};", "function f(/** null|!Foo|!Bar */ x, y) {", " if (x) {", " return x.m(y);", " }", "}"), NewTypeInference.BOTTOM_PROP); } public void testUnannotatedFunctionSummaryDoesntCrash() { typeCheck(LINE_JOINER.join( "var /** !Promise */ p;", "function f(unused) {", " function g(){ return 5; }", " p.then(g);", "}")); typeCheck(LINE_JOINER.join( "var /** !Promise */ p;", "function f(unused) {", " function g(){ return 5; }", " var /** null */ n = p.then(g);", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheckCustomExterns( DEFAULT_EXTERNS + "function f(x, y, z) {}", "f(1, 2, 3);"); } public void testSpecializeLooseNullDoesntCrash() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){}", "function reqFoo(/** Foo */ x) {}", "function f(x) {", " x = null;", " reqFoo(x);", "}")); } public void testUnparameterizedArrayDefinitionDoesntCrash() { typeCheckCustomExterns( DEFAULT_EXTERNS + LINE_JOINER.join( "/** @constructor */ function Function(){}", "/** @constructor */ function Array(){}", "Object.prototype.prop2;"), LINE_JOINER.join( "function f(/** !Array */ arr) {", " var newarr = [];", " newarr[0] = arr[0];", " newarr[0].prop1 = newarr[0].prop2;", "};")); } public void testInstanceofGenericTypeDoesntCrash() { typeCheck(LINE_JOINER.join( "/** @constructor @template T */ function Foo(){}", "function f(/** !Foo<?> */ f) {", " if (f instanceof Foo) return true;", "};")); } public void testRedeclarationOfFunctionAsNamespaceDoesntCrash() { typeCheck(LINE_JOINER.join( "/** @const */ var ns = ns || {};", "ns.fun = function(name) {};", "ns.fun = ns.fun || {};", "ns.fun.get = function(/** string */ name) {};")); typeCheck(LINE_JOINER.join( "/** @const */ var ns = ns || {};", "ns.fun = function(name) {};", "ns.fun.get = function(/** string */ name) {};", "ns.fun = ns.fun || {};")); typeCheck(LINE_JOINER.join( "/** @const */ var ns = ns || {};", "ns.fun = function(name) {};", "/** @const */ ns.fun = ns.fun || {};", "ns.fun.get = function(/** string */ name) {};")); typeCheck(LINE_JOINER.join( "/** @const */ var ns = ns || {};", "ns.fun = function(name) {};", "ns.fun.get = function(/** string */ name) {};", "/** @const */ ns.fun = ns.fun || {};")); typeCheck(LINE_JOINER.join( "/** @const */ var ns = ns || {};", "/** @param {string} name */", "ns.fun = function(name) {};", "ns.fun.get = function(/** string */ name) {};", "/** @const */ ns.fun = ns.fun || {};", "ns.fun(123);"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testRemoveNonexistentPropDoesntCrash() { // TODO(blickly): Would be nice not to warn here, // even if it means missing the warning below typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {", " /** @type {!Object} */ this.obj = {arr : []}", "}", "Foo.prototype.bar = function() {", " this.obj.arr.length = 0;", "}"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {", " /** @type {!Object} */ this.obj = {}", "}", "Foo.prototype.bar = function() {", " this.obj.prop1.prop2 = 0;", "}"), NewTypeInference.INEXISTENT_PROPERTY); } public void testDoublyAssignedPrototypeMethodDoesntCrash() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){}", "Foo.prototype.method = function(){};", "var f = function() {", " Foo.prototype.method = function(){};", "}"), GlobalTypeInfo.CTOR_IN_DIFFERENT_SCOPE, GlobalTypeInfo.REDECLARED_PROPERTY); } public void testTopFunctionAsArgumentDoesntCrash() { typeCheck(LINE_JOINER.join( "function f(x) {}", "function g(value) {", " if (typeof value == 'function') {", " f(value);", " }", "}")); } public void testGetpropDoesntCrash() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Obj(){}", "/** @constructor */ var Foo = function() {", " /** @private {Obj} */ this.obj;", "};", "Foo.prototype.update = function() {", " if (!this.obj) {}", "};")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Obj(){}", "/** @constructor */", "var Foo = function() {", " /** @private {Obj} */ this.obj;", "};", "Foo.prototype.update = function() {", " if (!this.obj.size) {}", "};"), NewTypeInference.NULLABLE_DEREFERENCE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Obj(){}", "/** @constructor */", "var Foo = function() {", " /** @private {Obj} */ this.obj;", "};", "/** @param {!Foo} x */", "function f(x) {", " if (!x.obj.size) {}", "};"), NewTypeInference.NULLABLE_DEREFERENCE); } public void testLooseFunctionSubtypeDoesntCrash() { typeCheck(LINE_JOINER.join( "/** @constructor */", "var Foo = function() {};", "/** @param {function(!Foo)} fooFun */", "var reqFooFun = function(fooFun) {};", "/** @type {function(!Foo)} */", "var declaredFooFun;", "function f(opt_fooFun) {", " reqFooFun(opt_fooFun);", " var fooFun = opt_fooFun || declaredFooFun;", " reqFooFun(fooFun);", "};")); typeCheck(LINE_JOINER.join( "var /** @type {function(number)} */ f;", "f = (function(x) {", " x(1, 2);", " return x;", "})(function(x, y) {});")); typeCheck(LINE_JOINER.join( "/**", " * @template T,U", " * @param {function(T,U):T} x", " */", "function f(x) {}", "function g(arr, fun) {", " arr.push(fun());", " return arr;", "}", "f(g);")); } public void testMeetOfLooseObjAndNamedDoesntCrash() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){ this.prop = 5; }", "/** @constructor */ function Bar(){}", "/** @param {function(!Foo)} func */", "Bar.prototype.forEach = function(func) {", " this.forEach(function(looseObj) { looseObj.prop; });", "};")); } public void testVarargs() { typeCheck(LINE_JOINER.join( "function foo(baz, /** ...number */ es6_rest_args) {", " var bar = [].slice.call(arguments, 0);", "}", "foo(); foo(3); foo(3, 4);")); } public void testUninhabitableObjectTypeDoesntCrash() { typeCheck(LINE_JOINER.join( "function f(/** number */ n) {", " if (typeof n == 'string') {", " return { 'First': n, 'Second': 5 };", " }", "};")); } public void testMockedOutConstructorDoesntCrash() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){}", "/** @constructor */ Foo = function(){};"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testNamespacePropWithNoTypeDoesntCrash() { typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @public */ ns.prop;")); } public void testArrayLiteralUsedGenericallyDoesntCrash() { typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {!Array<T>} arr", " * @return {T}", " */", "function f(arr) { return arr[0]; }", "f([1,2,3]);")); } public void testSpecializeLooseFunctionDoesntCrash() { typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(/** !Function */ func) {};", "function g(obj) {", " if (goog.isFunction(obj)) {", " f(obj);", " }", "};")); typeCheck(LINE_JOINER.join( "function f(/** !Function */ func) {};", "function g(obj) {", " if (typeof obj === 'function') {", " f(obj);", " }", "};")); typeCheck(LINE_JOINER.join( "/**", " * @param {function(T)} fn", " * @param {T} x", " * @template T", " */", "function reqGenFun(fn, x) {};", "function g(obj, str) {", " var member = obj[str];", " if (typeof member === 'function') {", " reqGenFun(member, str);", " }", "};")); } public void testGetpropOnPossiblyInexistentPropertyDoesntCrash() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){};", "function f() {", " var obj = 3 ? new Foo : { prop : { subprop : 'str'}};", " obj.prop.subprop = 'str';", "};"), NewTypeInference.POSSIBLY_INEXISTENT_PROPERTY); } public void testCtorManipulationDoesntCrash() { typeCheck(LINE_JOINER.join( "/** @constructor */ var X = function() {};", "var f = function(ctor) {", " /** @type {function(new: X)} */", " function InstantiableCtor() {};", " InstantiableCtor.prototype = ctor.prototype;", "}")); } public void testAbstractMethodOverrides() { typeCheck(LINE_JOINER.join( "/** @const */ var goog = {};", "/** @type {!Function} */ goog.abstractMethod = function(){};", "/** @interface */ function I() {}", "/** @param {string=} opt_str */", "I.prototype.done = goog.abstractMethod;", "/** @implements {I} @constructor */ function Foo() {}", "/** @override */ Foo.prototype.done = function(opt_str) {}", "/** @param {I} stats */ function f(stats) {}", "function g() {", " var x = new Foo();", " f(x);", " x.done();", "}")); } public void testThisReferenceUsedGenerically() { typeCheck(LINE_JOINER.join( "/** @constructor @template T */", "var Foo = function(t) {", " /** @type {Foo<T>} */", " this.parent_ = null;", "}", "Foo.prototype.method = function() {", " var p = this;", " while (p != null) p = p.parent_;", "}")); typeCheck(LINE_JOINER.join( "/** @constructor @template T */", "var Foo = function(t) {", " /** @type {Foo<T>} */", " var p = this;", "}")); } public void testGrandparentTemplatizedDoesntCrash() { typeCheck(LINE_JOINER.join( "/** @constructor @template VALUE */", "var Grandparent = function() {};", "/** @constructor @extends {Grandparent<number>} */", "var Parent = function(){};", "/** @constructor @extends {Parent} */ function Child(){}")); typeCheck(LINE_JOINER.join( "/** @constructor @template VALUE */", "var Grandparent = function() {};", "/** @constructor @extends {Grandparent} */", "var Parent = function(){};", "/** @constructor @extends {Parent} */ function Child(){}")); } public void testDirectPrototypeAssignmentDoesntCrash() { typeCheck(LINE_JOINER.join( "function UndeclaredCtor(parent) {}", "UndeclaredCtor.prototype = {__proto__: Object.prototype};")); } public void testDebuggerStatementDoesntCrash() { typeCheck("debugger;"); } public void testDeclaredMethodWithoutScope() { typeCheck(LINE_JOINER.join( "/** @interface */ function Foo(){}", "/** @type {function(number)} */ Foo.prototype.bar;", "/** @constructor @implements {Foo} */ function Bar(){}", "Bar.prototype.bar = function(x){}")); typeCheck(LINE_JOINER.join( "/** @type {!Function} */", "var g = function() { throw 0; };", "/** @constructor */ function Foo(){}", "/** @type {function(number)} */ Foo.prototype.bar = g;", "/** @constructor @extends {Foo} */ function Bar(){}", "Bar.prototype.bar = function(x){}")); typeCheck(LINE_JOINER.join( "/** @param {string} s */", "var reqString = function(s) {};", "/** @constructor */ function Foo(){}", "/** @type {function(string)} */ Foo.prototype.bar = reqString;", "/** @constructor @extends {Foo} */ function Bar(){}", "Bar.prototype.bar = function(x){}")); typeCheck(LINE_JOINER.join( "/** @param {string} s */", "var reqString = function(s) {};", "/** @constructor */ function Foo(){}", "/** @type {function(number)} */ Foo.prototype.bar = reqString;", "/** @constructor @extends {Foo} */ function Bar(){}", "Bar.prototype.bar = function(x){}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){}", "/** @type {Function} */ Foo.prototype.bar = null;", "/** @constructor @extends {Foo} */ function Bar(){}", "Bar.prototype.bar = function(){}")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){}", "/** @type {!Function} */ Foo.prototype.bar = null;", "/** @constructor @extends {Foo} */ function Bar(){}", "Bar.prototype.bar = function(){}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @interface */ function I(){}", "/** @return {void} */", "I.prototype.method;")); } public void testDontOverrideNestedPropWithWorseType() { typeCheck(LINE_JOINER.join( "/** @interface */", "var Bar = function() {};", "/** @type {Function} */", "Bar.prototype.method;", "/** @interface */", "var Baz = function() {};", "Baz.prototype.method = function() {};", "/** @constructor */", "var Foo = function() {};", "/** @type {!Bar|!Baz} */", "Foo.prototype.obj;", "Foo.prototype.set = function() {", " this.obj.method = 5;", "};"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** { prop: number } */ obj, x) {", " x < obj.prop;", " obj.prop < 'str';", " obj.prop = 123;", " x = 123;", "}", "f({ prop: 123}, 123)"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testPropNamesWithDot() { typeCheck("var x = { '.': 1, ';': 2, '/': 3, '{': 4, '}': 5 }"); typeCheck(LINE_JOINER.join( "function f(/** { foo : { bar : string } } */ x) {", " x['foo.bar'] = 5;", "}")); typeCheck(LINE_JOINER.join( "var x = { '.' : 'str' };", "x['.'] - 5"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testObjLitDeclaredProps() { typeCheck( "({ /** @type {string} */ prop: 123 });", NewTypeInference.INVALID_OBJLIT_PROPERTY_TYPE); typeCheck(LINE_JOINER.join( "var lit = { /** @type {string} */ prop: 'str' };", "lit.prop = 123;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "var lit = { /** @type {(number|string)} */ prop: 'str' };", "var /** string */ s = lit.prop;")); } public void testCallArgumentsChecked() { typeCheck( "3(1 - 'str');", NewTypeInference.NOT_CALLABLE, NewTypeInference.INVALID_OPERAND_TYPE); } public void testRecursiveFunctions() { typeCheck( "function foo(){ foo() - 123; return 'str'; }", NewTypeInference.INVALID_INFERRED_RETURN_TYPE); typeCheck( "/** @return {string} */ function foo(){ foo() - 123; return 'str'; }", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @return {number} */", "var f = function rec() { return rec; };"), NewTypeInference.RETURN_NONDECLARED_TYPE); } public void testStructPropAccess() { typeCheck(LINE_JOINER.join( "/** @constructor @struct */ function Foo() { this.prop = 123; }", "(new Foo).prop;")); typeCheck(LINE_JOINER.join( "/** @constructor @struct */ function Foo() { this.prop = 123; }", "(new Foo)['prop'];"), NewTypeInference.ILLEGAL_PROPERTY_ACCESS); typeCheck(LINE_JOINER.join( "/** @interface */ function Foo() {}", "/** @type {number} */ Foo.prototype.prop;", "function f(/** !Foo */ x) { x['prop']; }")); typeCheck(LINE_JOINER.join( "/** @constructor @struct */ function Foo() {", " this.prop = 123;", " this['prop'] - 123;", "}"), NewTypeInference.ILLEGAL_PROPERTY_ACCESS); typeCheck(LINE_JOINER.join( "/** @constructor @struct */ function Foo() { this.prop = 123; }", "(new Foo)['prop'] = 123;"), NewTypeInference.ILLEGAL_PROPERTY_ACCESS); typeCheck(LINE_JOINER.join( "/** @constructor @struct */ function Foo() { this.prop = 123; }", "function f(pname) { (new Foo)[pname] = 123; }"), NewTypeInference.ILLEGAL_PROPERTY_ACCESS); typeCheck(LINE_JOINER.join( "/** @constructor @struct */ function Foo() { this.prop = {}; }", "(new Foo)['prop'].newprop = 123;"), NewTypeInference.ILLEGAL_PROPERTY_ACCESS); typeCheck(LINE_JOINER.join( "/** @constructor @struct */ function Foo() {}", "/** @constructor */ function Bar() {}", "function f(cond) {", " var x;", " if (cond) {", " x = new Foo;", " }", " else {", " x = new Bar;", " }", " x['prop'] = 123;", "}"), NewTypeInference.ILLEGAL_PROPERTY_ACCESS); typeCheck("(/** @struct */ { 'prop' : 1 });", NewTypeInference.ILLEGAL_OBJLIT_KEY); typeCheck( "var lit = /** @struct */ { prop : 1 }; lit['prop'];", NewTypeInference.ILLEGAL_PROPERTY_ACCESS); typeCheck(LINE_JOINER.join( "function f(cond) {", " var x;", " if (cond) {", " x = /** @struct */ { a: 1 };", " }", " else {", " x = /** @struct */ { a: 2 };", " }", " x['a'] = 123;", "}"), NewTypeInference.ILLEGAL_PROPERTY_ACCESS); typeCheck(LINE_JOINER.join( "function f(cond) {", " var x;", " if (cond) {", " x = /** @struct */ { a: 1 };", " }", " else {", " x = { b: 2 };", " }", " x['random' + 'propname'] = 123;", "}"), NewTypeInference.ILLEGAL_PROPERTY_ACCESS); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @struct", " */", "function Foo() {", " /** @type {number} */", " this.prop;", " this.prop = 'asdf';", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @struct", " */", "function Foo() {", " /** @type {number} */", " this.prop;", "}", "(new Foo).prop = 'asdf';"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @record", " * @struct", " */", "function Foo() {}", "/** @type {string} */", "Foo.prototype.prop;", "function f(/** !Foo */ x) { return x['prop']; }"), NewTypeInference.ILLEGAL_PROPERTY_ACCESS); } public void testDictPropAccess() { typeCheck(LINE_JOINER.join( "/** @constructor @dict */ function Foo() { this['prop'] = 123; }", "(new Foo)['prop'];")); typeCheck(LINE_JOINER.join( "/** @constructor @dict */ function Foo() { this['prop'] = 123; }", "(new Foo).prop;"), NewTypeInference.ILLEGAL_PROPERTY_ACCESS); typeCheck(LINE_JOINER.join( "/** @constructor @dict */ function Foo() {", " this['prop'] = 123;", " this.prop - 123;", "}"), NewTypeInference.ILLEGAL_PROPERTY_ACCESS); typeCheck(LINE_JOINER.join( "/** @constructor @dict */ function Foo() { this['prop'] = 123; }", "(new Foo).prop = 123;"), NewTypeInference.ILLEGAL_PROPERTY_ACCESS); typeCheck(LINE_JOINER.join( "Object.prototype.prop;", "/** @constructor @dict */ function Foo() { this['prop'] = {}; }", "var x = new Foo;", "x.prop.newprop = 123;"), NewTypeInference.ILLEGAL_PROPERTY_ACCESS); typeCheck(LINE_JOINER.join( "/** @constructor @dict */ function Foo() {}", "/** @constructor */ function Bar() {}", "function f(cond) {", " var x;", " if (cond) {", " x = new Foo;", " }", " else {", " x = new Bar;", " }", " x.prop = 123;", "}"), NewTypeInference.ILLEGAL_PROPERTY_ACCESS); typeCheck("(/** @dict */ { prop : 1 });", NewTypeInference.ILLEGAL_OBJLIT_KEY); typeCheck( "var lit = /** @dict */ { 'prop' : 1 }; lit.prop;", NewTypeInference.ILLEGAL_PROPERTY_ACCESS); typeCheck( "(/** @dict */ {}).toString();", NewTypeInference.ILLEGAL_PROPERTY_ACCESS); typeCheck(LINE_JOINER.join( "function f(/** !Object */ o) {", " if ('num' in o) {", " o.num++;", " }", "}")); typeCheck(LINE_JOINER.join( "function f(/** !Object */ x, y) {", " if ((y in x) && x.otherProp) {", " x.otherProp++;", " }", "}")); } public void testStructWithIn() { typeCheck("('prop' in /** @struct */ {});", NewTypeInference.IN_USED_WITH_STRUCT); typeCheck( "for (var x in /** @struct */ {});", NewTypeInference.IN_USED_WITH_STRUCT); // Don't warn in union, it's fine to ask about property existence of the // non-struct part. typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @struct", " */", "function Foo() {}", "/** @constructor */", "function Bar() {", " this['asdf'] = 123;", "}", "function f(/** (!Foo|!Bar) */ x) {", " if ('asdf' in x) {}", "}")); typeCheck(LINE_JOINER.join( "function f(p1, /** !Object */ obj) {", " if (p1 in obj['asdf']) {}", "}")); } public void testStructDictSubtyping() { typeCheck("var lit = { a: 1 }; lit.a - 2; lit['a'] + 5;"); typeCheck(LINE_JOINER.join( "/** @constructor @struct */ function Foo() {}", "/** @constructor @dict */ function Bar() {}", "function f(/** Foo */ x) {}", "f(/** @dict */ {});"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(/** { a : number } */ x) {}", "f(/** @dict */ { 'a' : 5 });")); } public void testDontInferStructDictFormal() { typeCheck(LINE_JOINER.join( "function f(obj) {", " return obj.prop;", "}", "f(/** @dict */ { 'prop': 123 });")); typeCheck(LINE_JOINER.join( "function f(obj) {", " return obj['prop'];", "}", "f(/** @struct */ { prop: 123 });")); typeCheck(LINE_JOINER.join( "/** @constructor @struct */", "function Foo() {}", "function f(obj) { obj['prop']; return obj; }", "var /** !Foo */ x = f({ prop: 123 });")); typeCheck(LINE_JOINER.join( "Object.prototype.p2;", "function f(obj) {", " if (obj['p1']) {", " obj.p2.p3 = 123;", " }", "}")); typeCheck(LINE_JOINER.join( "function f(obj) {", " return obj['a'] + obj.b;", "}", "f({ a: 123, 'b': 234 });")); } public void testStructDictInheritance() { typeCheck(LINE_JOINER.join( "/** @constructor @struct */", "function Foo() {}", "/** @constructor @struct @extends {Foo} */", "function Bar() {}")); typeCheck(LINE_JOINER.join( "/** @constructor @struct */", "function Foo() {}", "/** @constructor @unrestricted @extends {Foo} */", "function Bar() {}")); typeCheck(LINE_JOINER.join( "/** @constructor @dict */", "function Foo() {}", "/** @constructor @dict @extends {Foo} */", "function Bar() {}")); // Don't warn when structs extend non-structs typeCheck(LINE_JOINER.join( "/** @constructor @unrestricted */", "function Foo() {}", "/** @constructor @struct @extends {Foo} */", "function Bar() {}")); // Don't warn when dicts extend non-dicts typeCheck(LINE_JOINER.join( "/** @constructor @unrestricted */", "function Foo() {}", "/** @constructor @dict @extends {Foo} */", "function Bar() {}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {string} */", " this.prop = 'asdf';", "}", "/**", " * @constructor", " * @extends {Foo}", " * @struct", " */", "function Bar() {}", "(new Bar).prop - 123;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "/** @constructor @dict @implements {Foo} */", "function Bar() {}"), JSTypeCreatorFromJSDoc.DICT_IMPLEMENTS_INTERF); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/**", " * @constructor", " * @struct", " * @extends {Foo}", " * @suppress {newCheckTypesAllChecks}", " */", "function Bar() {}", "var /** !Foo */ x = new Bar;")); } public void testStructPropCreation() { typeCheck(LINE_JOINER.join( "/** @constructor @struct */", "function Foo() { this.prop = 1; }", "(new Foo).prop = 2;")); typeCheck(LINE_JOINER.join( "/** @constructor @struct */", "function Foo() {}", "Foo.prototype.method = function() { this.prop = 1; };"), NewTypeInference.ILLEGAL_PROPERTY_CREATION); typeCheck(LINE_JOINER.join( "/** @constructor @struct */", "function Foo() {}", "Foo.prototype.method = function() { this.prop = 1; };", "(new Foo).prop = 2;"), NewTypeInference.ILLEGAL_PROPERTY_CREATION, NewTypeInference.ILLEGAL_PROPERTY_CREATION); typeCheck(LINE_JOINER.join( "/** @constructor @struct */", "function Foo() {}", "(new Foo).prop += 2;"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor @struct */", "function Foo() {}", "Foo.prototype.method = function() { this.prop = 1; };", "(new Foo).prop++;"), NewTypeInference.ILLEGAL_PROPERTY_CREATION, NewTypeInference.INEXISTENT_PROPERTY); typeCheck( "(/** @struct */ { prop: 1 }).prop2 = 123;", NewTypeInference.ILLEGAL_PROPERTY_CREATION); typeCheck(LINE_JOINER.join( "/** @constructor @struct */", "function Foo() {}", "/** @constructor @struct @extends {Foo} */", "function Bar() {}", "Bar.prototype.prop = 123;")); typeCheck(LINE_JOINER.join( "/** @constructor @struct */", "function Foo() {}", "/** @constructor @struct @extends {Foo} */", "function Bar() {}", "Bar.prototype.prop = 123;", "(new Foo).prop = 234;"), NewTypeInference.ILLEGAL_PROPERTY_CREATION); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @struct", " */", "function Foo() {", " var t = this;", " t.x = 123;", "}"), NewTypeInference.ILLEGAL_PROPERTY_CREATION); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @struct", " */", "function Foo() {}", "Foo.someprop = 123;")); // TODO(dimvar): the current type inf also doesn't catch this. // Consider warning when the prop is not an "own" prop. typeCheck(LINE_JOINER.join( "/** @constructor @struct */", "function Foo() {}", "Foo.prototype.bar = 123;", "(new Foo).bar = 123;")); typeCheck(LINE_JOINER.join( "function f(obj) { obj.prop = 123; }", "f(/** @struct */ {});")); typeCheck(LINE_JOINER.join( "/** @constructor @struct */", "function Foo() {}", "function f(obj) { obj.prop - 5; return obj; }", "var s = (1 < 2) ? new Foo : f({ prop: 123 });", "s.newprop = 123;"), NewTypeInference.ILLEGAL_PROPERTY_CREATION); } public void testMisplacedStructDictAnnotation() { typeCheck( "/** @struct */ function Struct1() {}", GlobalTypeInfo.STRUCT_WITHOUT_CTOR_OR_INTERF); typeCheck( "/** @dict */ function Dict() {}", GlobalTypeInfo.DICT_WITHOUT_CTOR); typeCheck( "/** @dict @interface */ function Foo() {}", GlobalTypeInfo.DICT_WITHOUT_CTOR); } public void testFunctionUnions() { typeCheck("/** @type {?function()} */ function f() {};"); typeCheck( "/** @type {?function()} */ function f() {}; f = 7;", NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck("/** @type {?function()} */ function f() {}; f = null;"); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @type {?function()} */", "ns.f = function() {};", "ns.f = 7;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck( "/** @const */var ns = {}; /** @type {?function()} */ns.f = function(){}; ns.f = null;"); typeCheck( "/** @type {?function(string)} */ function f(x) { x-5; };", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck( "/** @const */var ns = {}; /** @type {?function(string)} */ns.f = function(x){ x-5; };", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){}", "/** @type {?function()} */ Foo.prototype.f;", "(new Foo).f = 7;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){}", "/** @type {?function()} */ Foo.prototype.f;", "(new Foo).f = null;")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){}", "/** @type {?function()} */ Foo.prototype.f = function() {};", "(new Foo).f = 7;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){}", "/** @type {?function()} */ Foo.prototype.f = function() {};", "(new Foo).f = null;")); } public void testFunctionTypedefs() { typeCheck("/** @typedef {function()} */ var Fun; /** @type {Fun} */ function f() {};"); typeCheck(LINE_JOINER.join( "/** @typedef {function(string)} */ var TakesString;", "/** @type {TakesString} */ function f(x) {}", "f(123);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @typedef {number|function()} */ var FunctionUnion;", "/** @type {FunctionUnion} */ function f(x) {}"), GlobalTypeInfo.WRONG_PARAMETER_COUNT); } public void testGetters() { typeCheck( "var x = { /** @return {string} */ get a() { return 1; } };", NewTypeInference.RETURN_NONDECLARED_TYPE); typeCheck( "var x = { /** @param {number} n */ get a() {} };", GlobalTypeInfo.INEXISTENT_PARAM); typeCheck( "var x = { /** @type {string} */ get a() {} };", JSTypeCreatorFromJSDoc.FUNCTION_WITH_NONFUNC_JSDOC); typeCheck(LINE_JOINER.join( "var x = {", " /**", " * @return {T|number} b", " * @template T", " */", " get a() {}", "};"), JSTypeCreatorFromJSDoc.TEMPLATED_GETTER_SETTER); typeCheck( "var x = /** @dict */ { get a() {} };", NewTypeInference.ILLEGAL_OBJLIT_KEY); typeCheck( "var x = /** @struct */ { get 'a'() {} };", NewTypeInference.ILLEGAL_OBJLIT_KEY); typeCheck( "var x = { get a() { 1 - 'asdf'; } };", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "var x = { get a() { return 1; } };", "x.a < 'str';"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "var x = { get a() { return 1; } };", "x.a();"), NewTypeInference.NOT_CALLABLE); typeCheck(LINE_JOINER.join( "var x = { get 'a'() { return 1; } };", "x['a']();"), NewTypeInference.NOT_CALLABLE); // assigning to a getter doesn't remove it typeCheck(LINE_JOINER.join( "var x = { get a() { return 1; } };", "x.a = 'str';", "x.a - 1;")); typeCheck( "var x = /** @struct */ { get a() {} }; x.a = 123;", NewTypeInference.ILLEGAL_PROPERTY_CREATION); } public void testSetters() { typeCheck( "var x = { /** @return {string} */ set a(b) { return ''; } };", GlobalTypeInfo.SETTER_WITH_RETURN); typeCheck( "var x = { /** @type{function(number):number} */ set a(b) { return 5; } };", GlobalTypeInfo.SETTER_WITH_RETURN); typeCheck(LINE_JOINER.join( "var x = {", " /**", " * @param {T|number} b", " * @template T", " */", " set a(b) {}", "};"), JSTypeCreatorFromJSDoc.TEMPLATED_GETTER_SETTER); typeCheck( "var x = { set a(b) { return 1; } };", NewTypeInference.RETURN_NONDECLARED_TYPE); typeCheck( "var x = { /** @type {string} */ set a(b) {} };", JSTypeCreatorFromJSDoc.FUNCTION_WITH_NONFUNC_JSDOC); typeCheck( "var x = /** @dict */ { set a(b) {} };", NewTypeInference.ILLEGAL_OBJLIT_KEY); typeCheck( "var x = /** @struct */ { set 'a'(b) {} };", NewTypeInference.ILLEGAL_OBJLIT_KEY); typeCheck( "var x = { set a(b) { 1 - 'asdf'; } };", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck( "var x = { set a(b) {}, prop: 123 }; var y = x.a;", NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "var x = { /** @param {string} b */ set a(b) {} };", "x.a = 123;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "var x = { set a(b) { b - 5; } };", "x.a = 'str';"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "var x = { set 'a'(b) { b - 5; } };", "x['a'] = 'str';"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testConstMissingInitializer() { typeCheck( "/** @const */ var x;", GlobalTypeInfo.CONST_WITHOUT_INITIALIZER); typeCheckCustomExterns( DEFAULT_EXTERNS + "/** @const {number} */ var x;", ""); typeCheckCustomExterns( DEFAULT_EXTERNS + "/** @const {number} */ var x;", "x - 5;"); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @const */", "Foo.prop;"), GlobalTypeInfo.CONST_WITHOUT_INITIALIZER); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @const */ this.prop;", "}"), GlobalTypeInfo.CONST_WITHOUT_INITIALIZER); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @const */", "Foo.prototype.prop;"), GlobalTypeInfo.CONST_WITHOUT_INITIALIZER); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @const */", "ns.prop;"), GlobalTypeInfo.CONST_WITHOUT_INITIALIZER); } public void testMisplacedConstPropertyAnnotation() { typeCheck( "function f(obj) { /** @const */ obj.prop = 123; }", GlobalTypeInfo.MISPLACED_CONST_ANNOTATION); typeCheck( "function f(obj) { /** @const */ obj.prop; }", GlobalTypeInfo.MISPLACED_CONST_ANNOTATION); typeCheck( "var obj = { /** @const */ prop: 1 };", GlobalTypeInfo.MISPLACED_CONST_ANNOTATION); // A final constructor isn't the same as a @const property typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /**", " * @constructor", " * @final", " */", " this.Bar = function() {};", "}")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @enum */", "ns.e = {/** @const */ A:1};")); } public void testConstVarsDontReassign() { typeCheck( "/** @const */ var x = 1; x = 2;", NewTypeInference.CONST_REASSIGNED); typeCheck( "/** @const */ var x = 1; x += 2;", NewTypeInference.CONST_REASSIGNED); typeCheck( "/** @const */ var x = 1; x -= 2;", NewTypeInference.CONST_REASSIGNED); typeCheck( "/** @const */ var x = 1; x++;", NewTypeInference.CONST_REASSIGNED); typeCheck(LINE_JOINER.join( "/** @const */ var x = 1;", "function f() { x = 2; }"), NewTypeInference.CONST_REASSIGNED); typeCheckCustomExterns( DEFAULT_EXTERNS + "/** @const {number} */ var x;", "x = 2;", NewTypeInference.CONST_REASSIGNED); typeCheck(LINE_JOINER.join( "/** @const */ var x = 1;", "function g() {", " var x = 2;", " x = x + 3;", "}")); } public void testConstPropertiesDontReassign() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @const */ this.prop = 1;", "}", "var obj = new Foo;", "obj.prop = 2;"), NewTypeInference.CONST_PROPERTY_REASSIGNED); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @const {number} */", " this.prop = 1;", "}", "var obj = new Foo;", "obj.prop = 2;"), NewTypeInference.CONST_PROPERTY_REASSIGNED); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @const */ this.prop = 1;", "}", "var obj = new Foo;", "obj.prop += 2;"), NewTypeInference.CONST_PROPERTY_REASSIGNED); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @const */ this.prop = 1;", "}", "var obj = new Foo;", "obj.prop++;"), NewTypeInference.CONST_PROPERTY_REASSIGNED); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @const */", "ns.prop = 1;", "ns.prop = 2;"), NewTypeInference.CONST_PROPERTY_REASSIGNED); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @const */", "ns.prop = 1;", "function f() { ns.prop = 2; }"), NewTypeInference.CONST_PROPERTY_REASSIGNED); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @const {number} */", "ns.prop = 1;", "ns.prop = 2;"), NewTypeInference.CONST_PROPERTY_REASSIGNED); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @const */", "ns.prop = 1;", "ns.prop++;"), NewTypeInference.CONST_PROPERTY_REASSIGNED); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @const */ Foo.prop = 1;", "Foo.prop = 2;"), NewTypeInference.CONST_PROPERTY_REASSIGNED); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @const {number} */ Foo.prop = 1;", "Foo.prop++;"), NewTypeInference.CONST_PROPERTY_REASSIGNED); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @const */ Foo.prototype.prop = 1;", "Foo.prototype.prop = 2;"), NewTypeInference.CONST_PROPERTY_REASSIGNED); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @const */ Foo.prototype.prop = 1;", "var protoAlias = Foo.prototype;", "protoAlias.prop = 2;"), NewTypeInference.CONST_PROPERTY_REASSIGNED); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() { /** @const */ this.X = 4; }", "/** @constructor */", "function Bar() { /** @const */ this.X = 5; }", "var fb = true ? new Foo : new Bar;", "fb.X++;"), NewTypeInference.CONST_PROPERTY_REASSIGNED); } public void testConstantByConvention() { typeCheck(LINE_JOINER.join( "var ABC = 123;", "ABC = 321;"), NewTypeInference.CONST_REASSIGNED); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " this.ABC = 123;", "}", "(new Foo).ABC = 321;"), NewTypeInference.CONST_PROPERTY_REASSIGNED); } public void testDontOverrideFinalMethods() { // TODO(blickly): Add support for @final annotation to NTI. typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @const */", "Foo.prototype.method = function(x) {};", "/** @constructor @extends {Foo} */", "function Bar() {}", "Bar.prototype.method = function(x) {};"), GlobalTypeInfo.CANNOT_OVERRIDE_FINAL_METHOD); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @const */", "Foo.prototype.num = 123;", "/** @constructor @extends {Foo} */", "function Bar() {}", "Bar.prototype.num = 2;")); // // TODO(dimvar): fix // typeCheck(LINE_JOINER.join( // "/** @constructor */", // "function High() {}", // "/**", // " * @param {number} x", // " * @final", // " */", // "High.prototype.method = function(x) {};", // "/** @constructor @extends {High} */", // "function Mid() {}", // "/** @constructor @extends {Mid} */", // "function Low() {}", // "Low.prototype.method = function(x) {};"), // GlobalTypeInfo.CANNOT_OVERRIDE_FINAL_METHOD); } public void testInferenceOfConstType() { typeCheck(LINE_JOINER.join( "/** @const */", "var s = 'str';", "function f() { s - 5; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(/** string */ x) {", " /** @const */", " var s = x;", " function g() { s - 5; }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */", "var r = /find/;")); typeCheck(LINE_JOINER.join( "/** @const */", "var r = /find/;", "function g() { r - 5; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */", "var a = [5];")); typeCheck(LINE_JOINER.join( "/** @const */", "var a = [5];", "function g() { a - 5; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "var x;", "/** @const */ var o = x = {};", "function g() { return o.prop; }"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @const */ var o = (0,{});", "function g() { return o.prop; }"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @const */ var s = true ? null : null;", "function g() { s - 5; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */ var s = true ? void 0 : undefined;", "function g() { s - 5; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */ var b = true ? (1<2) : ('' in {});", "function g() { b - 5; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */ var n = 0 || 6;", "function g() { n < 'str'; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */ var s = 'str' + 5;", "function g() { s - 5; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "var /** string */ x;", "/** @const */", "var s = x;", "function g() { s - 5; }"), NewTypeInference.MISTYPED_ASSIGN_RHS, NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @const */", " this.prop = 'str';", "}", "(new Foo).prop - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo(x) {", " /** @const */", " this.prop = x;", "}"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @const */", "Foo.prop = 'str';", "function g() { Foo.prop - 5; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(/** string */ s) {", " /** @constructor */", " function Foo() {}", " /** @const */", " Foo.prototype.prop = s;", " function g() {", " (new Foo).prop - 5;", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(s) {", " /** @constructor */", " function Foo() {}", " /** @const */", " Foo.prototype.prop = s;", "}"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @const */", "ns.prop = 'str';", "function f() {", " ns.prop - 5;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x, y) {", " /** @const */", " var n = x - y;", " function g() { n < 'str'; }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " /** @const */", " var notx = !x;", " function g() { notx - 5; }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */", "var lit = { a: 'a', b: 'b' };", "function g() { lit.a - 5; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */", "var n = ('str', 123);", "function f() { n < 'str'; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */", "var s = x;", "var /** string */ x;", "function f() { s; }"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " /** @const */", " var c = x;", "}")); typeCheck(LINE_JOINER.join( "function f(x) {", " /** @const */", " var c = x;", " function g() { c; }", "}"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " /** @const */", " var c = { a: 1, b: x };", "}")); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @param {{ a: string }} x", " */", "function Foo(x) {", " /** @const */", " this.prop = x.a;", "}", "(new Foo({ a: ''})).prop - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */", "var n1 = 1;", "/** @const */", "var n2 = n1;", "function g() { n2 < 'str'; }"), NewTypeInference.INVALID_OPERAND_TYPE); // Don't treat aliased constructors as if they were const variables. typeCheck(LINE_JOINER.join( "/** @constructor */ function Bar() {}", "/**", " * @constructor", " * @final", " */", "var Foo = Bar;")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Bar() {}", "/** @const */", "var ns = {};", "/**", " * @constructor", " * @final", " */", "ns.Foo = Bar;")); // (Counterintuitive) On a constructor, @final means don't subclass, not // that it's a const. We don't warn about reassignment. typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @final", " */", "var Foo = function() {};", "Foo = /** @type {?} */ (function() {});")); typeCheck(LINE_JOINER.join( "/** @const */", "var x = whatever.toString;", "function g() { x; }"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); typeCheckCustomExterns( DEFAULT_EXTERNS + "var NOT_A_CONST_DONT_WARN;", ""); typeCheck(LINE_JOINER.join( "function f(x) {", " /** @const */ var y = /** @type {number} */ (x);", " return function() { y; }", "}")); } public void testConstInferenceCalls() { typeCheck(LINE_JOINER.join( "/** @return {string} */", "function f() { return ''; }", "/** @const */", "var s = f();", "function g() { s - 5; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */", "var s = f();", "/** @return {string} */", "function f() { return ''; }", "function g() { s; }"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {}", "/** @const */", "var foo = new Foo;", "function g() {", " var /** Bar */ bar = foo;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @constructor */", "ns.Foo = function() {};", "/** @const */", "var c = new ns.Foo();")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/**", " * @constructor", " * @param {T} x", " * @template T", " */", "ns.Foo = function(x) {};", "/** @const */", "var c = new ns.Foo(123);", "var /** !ns.Foo<string> */ x = c;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/**", " * @param {number} x", " * @return {number}", " */", "function local_f(x) { return x; };", "local_f.prop = 123;", // Makes local_f a function namespace "/** @const */", "ns.f = local_f;", "/** @const */ var f = ns.f;", "/** @const */ var n = f(123);", "function g(x) {", " return n + x + local_f.prop;", "}")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/**", " * @param {number} x", " * @return {number}", " */", "ns.f = function(x) { return x; };", "/** @const */ var f = ns.f;", "/** @const */ var n = f(123);", "function g(x) { return n + x; }")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @const */ var f = ns.f;", "/** @const */ var n = f(123);", "/**", " * @param {number} x", " * @return {number}", " */", "ns.f = function(x) { return x; };", "function g(x) { return n + x; }"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); } public void testConstInferenceAndOf() { typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "/** @const */", "var goog = {};", "/** @const */", "goog.userAgent = {};", "/** @type {boolean} */", "goog.userAgent.IE;", "/**", " * @param {string|number} version", " * @return {boolean}", " */", "goog.userAgent.isVersionOrHigher = function(version) {};"), LINE_JOINER.join( "/** @const */", "var LEGACY = goog.userAgent.IE && !goog.userAgent.isVersionOrHigher(9);", "function f() { var /** number */ n = LEGACY; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f(/** ?Foo */ x) {", " /** @const */", " var y = x || new Foo;", " function g() {", " /** @type {!Foo} */", " var z = y;", " }", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f(/** ?Foo */ x) {", " /** @const */", " var y = x && new Foo;", " function g() {", " /** @type {!Foo} */", " var z = y;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(x, /** number */ y) {", " /** @const */", " var z = x || y;", " return function() { return z; };", "}"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); typeCheck(LINE_JOINER.join( "function f(/** number */ x, y) {", " /** @const */", " var z = x || y;", " return function() { return z; };", "}"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); typeCheck(LINE_JOINER.join( "/** @const */", "var x = null || 'asdf';", "function f() {", " var /** string */ s = x;", "}")); } public void testSuppressions() { typeCheck(LINE_JOINER.join( "/**", " * @fileoverview", " * @suppress {newCheckTypes}", " */", "123();")); typeCheck(LINE_JOINER.join( "123();", "/** @suppress {newCheckTypes} */", "function f() { 123(); }"), NewTypeInference.NOT_CALLABLE); typeCheck(LINE_JOINER.join( "123();", "/** @suppress {newCheckTypes} */", "function f() { 1 - 'str'; }"), NewTypeInference.NOT_CALLABLE); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @suppress {constantProperty}", " */", "function Foo() {", " /** @const */ this.bar = 3; this.bar += 4;", "}")); } public void testTypedefs() { typeCheck(LINE_JOINER.join( "/** @typedef {number} */", "var num = 1;"), GlobalTypeInfo.CANNOT_INIT_TYPEDEF); // typeCheck(LINE_JOINER.join( // "/** @typedef {number} */", // "var num;", // "num - 5;"), // VarCheck.UNDEFINED_VAR_ERROR); typeCheck(LINE_JOINER.join( "/** @typedef {NonExistentType} */", "var t;", "function f(/** t */ x) { x - 1; }"), GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME); // typeCheck(LINE_JOINER.join( // "/** @typedef {number} */", // "var dup;", // "/** @typedef {number} */", // "var dup;"), // VariableReferenceCheck.REDECLARED_VARIABLE); typeCheck(LINE_JOINER.join( "/** @typedef {number} */", "var dup;", "/** @typedef {string} */", "var dup;", "var /** dup */ n = 'str';"), // VariableReferenceCheck.REDECLARED_VARIABLE, NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @typedef {number} */", "var num;", "/** @type {num} */", "var n = 1;")); typeCheck(LINE_JOINER.join( "/** @typedef {number} */", "var num;", "/** @type {num} */", "var n = 'str';"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @type {num} */", "var n = 'str';", "/** @typedef {number} */", "var num;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @typedef {number} */", "var num;", "function f() {", " /** @type {num} */", " var n = 'str';", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @type {num2} */", "var n = 'str';", "/** @typedef {num} */", "var num2;", "/** @typedef {number} */", "var num;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @typedef {rec2} */", "var rec1;", "/** @typedef {rec1} */", "var rec2;"), JSTypeCreatorFromJSDoc.CIRCULAR_TYPEDEF_ENUM); typeCheck(LINE_JOINER.join( "/** @typedef {{ prop: rec2 }} */", "var rec1;", "/** @typedef {{ prop: rec1 }} */", "var rec2;"), JSTypeCreatorFromJSDoc.CIRCULAR_TYPEDEF_ENUM); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @typedef {Foo} */", "var Bar;", "var /** Bar */ x = null;")); // NOTE(dimvar): I don't know if long term we want to support ! on anything // other than a nominal-type name, but for now it's good to have this test. typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @typedef {Foo} */", "var Bar;", "var /** !Bar */ x = null;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @typedef {number} */", "var N;", "function f() {", " /** @constructor */", " function N() {}", " function g(/** N */ obj) { obj - 5; }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testLends() { typeCheck( "(/** @lends {InexistentType} */ { a: 1 });", GlobalTypeInfo.LENDS_ON_BAD_TYPE); typeCheck( "(/** @lends {number} */ { a: 1 });", GlobalTypeInfo.LENDS_ON_BAD_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "(/** @lends {Foo.badname} */ { a: 1 });"), GlobalTypeInfo.LENDS_ON_BAD_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "(/** @lends {Foo} */ { a: 1 });"), GlobalTypeInfo.LENDS_ON_BAD_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "(/** @lends {Foo.prototype} */ { a: 1 });"), GlobalTypeInfo.LENDS_ON_BAD_TYPE); typeCheck( "(/** @lends {Inexistent.Type} */ { a: 1 });", GlobalTypeInfo.LENDS_ON_BAD_TYPE); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "(/** @lends {ns} */ { /** @type {number} */ prop : 1 });", "function f() { ns.prop = 'str'; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "(/** @lends {ns} */ { /** @type {number} */ prop : 1 });", "/** @const */ var ns = {};", "function f() { ns.prop = 'str'; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "(/** @lends {ns} */ { prop : 1 });", "function f() { var /** string */ s = ns.prop; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @const */", "ns.subns = {};", "(/** @lends {ns.subns} */ { prop: 1 });", "var /** string */ s = ns.subns.prop;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "(/** @lends {Foo} */ { prop: 1 });", "var /** string */ s = Foo.prop;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "(/** @lends {Foo} */ { prop: 1 });", "function f() { var /** string */ s = Foo.prop; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @constructor */", "ns.Foo = function() {};", "(/** @lends {ns.Foo} */ { prop: 1 });", "function f() { var /** string */ s = ns.Foo.prop; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "(/** @lends {Foo.prototype} */ { /** @type {number} */ a: 1 });", "var /** string */ s = Foo.prototype.a;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "(/** @lends {Foo.prototype} */ { /** @type {number} */ a: 1 });", "var /** string */ s = (new Foo).a;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() { /** @type {number} */ this.prop = 1; }", "(/** @lends {Foo.prototype} */", " { /** @return {number} */ m: function() { return this.prop; } });", "var /** string */ s = (new Foo).m();"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testEnumBasicTyping() { typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E = {", " ONE: 1,", " TWO: 2", "};", "function f(/** E */ x) { x < 'str'; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @enum */", // No type annotation defaults to number "var E = {", " ONE: 1,", " TWO: 2", "};", "function f(/** E */ x) { x < 'str'; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E = {", " ONE: 1,", " TWO: 2", "};", "function f(/** E */ x) {}", "function g(/** number */ x) {}", "f(E.TWO);", "g(E.TWO);")); typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E = {", " ONE: 1,", " TWO: 2", "};", "function f(/** E */ x) {}", "f(1);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E = {", " ONE: 1,", " TWO: 2", "};", "function f() { E.THREE - 5; }"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @enum {!Foo} */", "var E = { ONE: new Foo };", "/** @constructor */", "function Foo() {}")); typeCheck(LINE_JOINER.join( "/** @typedef {number} */", "var num;", "/** @enum {num} */", "var E = { ONE: 1 };", "function f(/** E */ x) { x < 'str'; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @enum {string} */", "var E = { A: 'asdf', B: 'adf' };", "function f() { var /** string */ y = E.A + E.B; }")); typeCheck(LINE_JOINER.join( "/** @enum {Array<number>} */", "var FooEnum = {", " BAR: [5]", "};", "/** @param {FooEnum} x */", "function f(x) {", " var y = x[0];", "};")); } public void testEnumsWithNonScalarDeclaredType() { typeCheck(LINE_JOINER.join( "/** @enum {!Object} */ var E = {FOO: { prop: 1 }};", "E.FOO.prop - 5;"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @enum {{prop: number}} */ var E = {FOO: { prop: 1 }};", "E.FOO.prop - 5;")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @const */ this.prop = 1;", "}", "/** @enum {!Foo} */", "var E = { ONE: new Foo() };", "function f(/** E */ x) { x.prop < 'str'; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @const */ this.prop = 1;", "}", "/** @enum {!Foo} */", "var E = { ONE: new Foo() };", "function f(/** E */ x) { x.prop = 2; }"), NewTypeInference.CONST_PROPERTY_REASSIGNED); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @enum {!Foo} */", "var E = { A: new Foo };", "function f(/** E */ x) { x instanceof Foo; }")); } public void testEnumBadInitializer() { typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E;"), GlobalTypeInfo.MALFORMED_ENUM); typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E = {};"), GlobalTypeInfo.MALFORMED_ENUM); typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E = 1;"), GlobalTypeInfo.MALFORMED_ENUM); typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E = {", " ONE: 1,", " TWO: true", "};"), NewTypeInference.INVALID_OBJLIT_PROPERTY_TYPE); typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E = { A: 1, A: 2 };"), GlobalTypeInfo.DUPLICATE_PROP_IN_ENUM); typeCheck(LINE_JOINER.join( "var ns = {};", "function f() {", " /** @enum {number} */ var EnumType = ns;", "}"), GlobalTypeInfo.MALFORMED_ENUM); } public void testEnumPropertiesConstant() { typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E = {", " ONE: 1,", " TWO: 2", "};", "E.THREE = 3;")); typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E = {", " ONE: 1,", " TWO: 2", "};", "E.ONE = E.TWO;"), NewTypeInference.CONST_PROPERTY_REASSIGNED); typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E = {", " ONE: 1,", " TWO: 2", "};", "function f(/** E */) { E.ONE = E.TWO; }"), NewTypeInference.CONST_PROPERTY_REASSIGNED); } public void testEnumIllegalRecursion() { typeCheck(LINE_JOINER.join( "/** @enum {Type2} */", "var Type1 = {", " ONE: null", "};", "/** @enum {Type1} */", "var Type2 = {", " ONE: null", "};"), JSTypeCreatorFromJSDoc.CIRCULAR_TYPEDEF_ENUM, // This warning is a side-effect of the fact that, when there is a // cycle, the resolution of one enum will fail but the others will // complete successfully. NewTypeInference.INVALID_OBJLIT_PROPERTY_TYPE); typeCheck(LINE_JOINER.join( "/** @enum {Type2} */", "var Type1 = {", " ONE: null", "};", "/** @typedef {Type1} */", "var Type2;"), JSTypeCreatorFromJSDoc.CIRCULAR_TYPEDEF_ENUM); } public void testEnumBadDeclaredType() { typeCheck(LINE_JOINER.join( "/** @enum {InexistentType} */", "var E = { ONE : null };"), GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME); typeCheck(LINE_JOINER.join( "/** @enum {*} */", "var E = { ONE: 1, STR: '' };"), JSTypeCreatorFromJSDoc.ENUM_IS_TOP); // No free type variables in enums typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " */", "function f(x) {", " /** @enum {function(T):number} */", " var E = { ONE: /** @type {?} */ (x) };", "}"), GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " */", "function f(x) {", " /** @enum {T} */", " var E1 = { ONE: 1 };", " /** @enum {function(E1):E1} */", " var E2 = { ONE: function(x) { return x; } };", "}"), GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " */", "function f(x) {", " /** @typedef {T} */ var AliasT;", " /** @enum {T} */", " var E1 = { ONE: 1 };", " /** @enum {function(E1):T} */", " var E2 = { ONE: function(x) { return x; } };", "}"), GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME, GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME, GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME); // No unions in enums typeCheck(LINE_JOINER.join( "/** @enum {number|string} */", "var E = { ONE: 1, STR: '' };"), JSTypeCreatorFromJSDoc.ENUM_IS_UNION); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @enum {?Foo} */", "var E = { ONE: new Foo, TWO: null };"), JSTypeCreatorFromJSDoc.ENUM_IS_UNION); typeCheck(LINE_JOINER.join( "/** @typedef {number|string} */", "var NOS;", "/** @enum {NOS} */", "var E = { ONE: 1, STR: '' };"), JSTypeCreatorFromJSDoc.ENUM_IS_UNION); } public void testEnumsWithGenerics() { typeCheck(LINE_JOINER.join( "/** @enum */ var E1 = { A: 1};", "/**", " * @template T", " * @param {(T|E1)} x", " * @return {(T|E1)}", " */", "function f(x) { return x; }", "var /** string */ n = f('str');"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @enum */ var E1 = { A: 1};", "/** @enum */ var E2 = { A: 2};", "/**", " * @template T", " * @param {(T|E1)} x", " * @return {(T|E1)}", " */", "function f(x) { return x; }", "var /** (E2|string) */ x = f('str');"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @enum */", "var E = { A: 1 };", "/**", " * @template T", " * @param {number|!Array<T>} x", " */", "function f(x) {}", "f(E.A);", "f(123);")); typeCheck(LINE_JOINER.join( "/** @enum {string} */", "var e1 = { A: '' };", "/** @enum {string} */", "var e2 = { B: '' };", "/**", " * @template T", " * @param {T|e1} x", " * @return {T}", " */", "function f(x) { return /** @type {T} */ (x); }", "/** @param {number|e2} x */", "function g(x) { f(x) - 5; }"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testEnumJoinSpecializeMeet() { // join: enum {number} with number typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E = { ONE: 1 };", "function f(cond) {", " var x = cond ? E.ONE : 5;", " x - 2;", " var /** E */ y = x;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); // join: enum {Low} with High, to High typeCheck(LINE_JOINER.join( "/** @constructor */", "function High() {}", "/** @constructor @extends {High} */", "function Low() {}", "/** @enum {!Low} */", "var E = { A: new Low };", "function f(cond) {", " var x = cond ? E.A : new High;", " var /** High */ y = x;", " var /** E */ z = x;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); // join: enum {High} with Low, to (enum{High}|Low) typeCheck(LINE_JOINER.join( "/** @constructor */", "function High() {}", "/** @constructor @extends {High} */", "function Low() {}", "/** @enum {!High} */", "var E = { A: new High };", "function f(cond) {", " var x = cond ? E.A : new Low;", " if (!(x instanceof Low)) { var /** E */ y = x; }", "}")); // meet: enum {?} with string, to enum {?} typeCheck(LINE_JOINER.join( "/** @enum {?} */", "var E = { A: 123 };", "function f(x) {", " var /** string */ s = x;", " var /** E */ y = x;", " s = x;", "}", "f('str');"), NewTypeInference.INVALID_ARGUMENT_TYPE); // meet: E1|E2 with E1|E3, to E1 typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E1 = { ONE: 1 };", "/** @enum {number} */", "var E2 = { TWO: 1 };", "/** @enum {number} */", "var E3 = { THREE: 1 };", "function f(x) {", " var /** (E1|E2) */ y = x;", " var /** (E1|E3) */ z = x;", " var /** E1 */ w = x;", "}", "f(E2.TWO);"), NewTypeInference.INVALID_ARGUMENT_TYPE); // meet: enum {number} with number, to enum {number} typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E = { ONE: 1 };", "function f(x) {", " var /** E */ y = x;", " var /** number */ z = x;", "}", "f(123);"), NewTypeInference.INVALID_ARGUMENT_TYPE); // meet: enum {Low} with High, to enum {Low} typeCheck(LINE_JOINER.join( "/** @constructor */", "function High() {}", "/** @constructor @extends {High} */", "function Low() {}", "/** @enum {!Low} */", "var E = { A: new Low };", "function f(x) {", " var /** !High */ y = x;", " var /** E */ z = x;", "}", "f(new High);"), NewTypeInference.INVALID_ARGUMENT_TYPE); // meet: enum {Low} with (High1|High2), to enum {Low} typeCheck(LINE_JOINER.join( "/** @interface */", "function High1() {}", "/** @interface */", "function High2() {}", "/** @constructor @implements {High1} @implements {High2} */", "function Low() {}", "/** @enum {!Low} */", "var E = { A: new Low };", "function f(x) {", " var /** (!High1 | !High2) */ y = x;", " var /** E */ z = x;", "}", "f(/** @type {!High1} */ (new Low));"), NewTypeInference.INVALID_ARGUMENT_TYPE); // meet: enum {High} with Low typeCheck(LINE_JOINER.join( "/** @constructor */", "function High() {}", "/** @constructor @extends {High} */", "function Low() {}", "/** @enum {!High} */", "var E = { A: new High };", "/** @param {function(E)|function(!Low)} x */", "function f(x) { x(123); }"), JSTypeCreatorFromJSDoc.UNION_IS_UNINHABITABLE); } public void testEnumAliasing() { typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var e1 = { A: 1 };", "/** @enum {number} */", "var e2 = e1;")); typeCheck(LINE_JOINER.join( "var x;", "/** @enum {number} */", "var e1 = { A: 1 };", "/** @enum {number} */", "var e2 = x;"), GlobalTypeInfo.MALFORMED_ENUM); typeCheck(LINE_JOINER.join( "/** @const */", "var ns1 = {};", "/** @enum {number} */", "ns1.e1 = { A: 1 };", "/** @const */", "var ns2 = {};", "/** @enum {number} */", "ns2.e2 = ns1.e1;")); typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var e1 = { A: 1 };", "/** @enum {number} */", "var e2 = e1;", "function f(/** e2 */ x) {}", "f(e1.A);")); typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var e1 = { A: 1 };", "/** @enum {number} */", "var e2 = e1;", "function f(/** e2 */ x) {}", "f(e2.A);")); typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var e1 = { A: 1 };", "/** @enum {number} */", "var e2 = e1;", "function f(/** e2 */ x) {}", "f('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @const */", "var ns1 = {};", "/** @enum {number} */", "ns1.e1 = { A: 1 };", "/** @const */", "var ns2 = {};", "/** @enum {number} */", "ns2.e2 = ns1.e1;", "function f(/** ns2.e2 */ x) {}", "f(ns1.e1.A);")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns1 = {};", "/** @enum {number} */", "ns1.e1 = { A: 1 };", "/** @const */", "var ns2 = {};", "/** @enum {number} */", "ns2.e2 = ns1.e1;", "function f(/** ns1.e1 */ x) {}", "f(ns2.e2.A);")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns1 = {};", "/** @enum {number} */", "ns1.e1 = { A: 1 };", "function g() {", " /** @const */", " var ns2 = {};", " /** @enum {number} */", " ns2.e2 = ns1.e1;", " function f(/** ns1.e1 */ x) {}", " f(ns2.e2.A);", "}")); } public void testNoDoubleWarnings() { typeCheck( "if ((4 - 'str') && true) { 4 + 5; }", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck("(4 - 'str') ? 5 : 6;", NewTypeInference.INVALID_OPERAND_TYPE); } public void testRecordSpecializeNominalPreservesRequired() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {number|string} */", " this.x = 5;", "}", "/** @param {{x: (number|undefined), y: (string|undefined)}} o */", "function f(o) {", " if (o instanceof Foo) {", " var /** {x:number} */ o2 = o;", " }", " (function(/** {x:number} */ o3){})(o);", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testGoogIsPredicatesNoSpecializedContext() { typeCheck( CLOSURE_BASE + "goog.isNull();", NewTypeInference.WRONG_ARGUMENT_COUNT); typeCheck( CLOSURE_BASE + "goog.isNull(1, 2, 5 - 'str');", NewTypeInference.WRONG_ARGUMENT_COUNT, NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(CLOSURE_BASE + "function f(x) { var /** boolean */ b = goog.isNull(x); }"); } public void testIsArrayPredicate() { typeCheck(LINE_JOINER.join( "function f(/** (Array<number>|number) */ x) {", " var /** Array<number> */ a;", " if (Array.isArray(x)) {", " a = x;", " }", " a = x;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testGoogIsPredicatesTrue() { typeCheck(CLOSURE_BASE + "function f(x) { if (goog.isNull(x)) { var /** undefined */ y = x; } }", NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @param {number=} x */", "function f(x) {", " if (goog.isDef(x)) {", " x - 5;", " }", " x - 5;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */", "function Foo() {}", "/** @param {Foo=} x */", "function f(x) {", " var /** !Foo */ y;", " if (goog.isDefAndNotNull(x)) {", " y = x;", " }", " y = x;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(/** (Array<number>|number) */ x) {", " var /** Array<number> */ a;", " if (goog.isArray(x)) {", " a = x;", " }", " a = x;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @param {null|function(number)} x */ ", "function f(x) {", " if (goog.isFunction(x)) {", " x('str');", " }", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(x) {", " if (goog.isObject(x)) {", " var /** null */ y = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(/** (number|string) */ x) {", " if (goog.isString(x)) {", " x < 'str';", " }", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(/** (number|string) */ x) {", " if (goog.isNumber(x)) {", " x - 5;", " }", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(/** (number|boolean) */ x) {", " if (goog.isBoolean(x)) {", " var /** boolean */ b = x;", " }", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/**", " * @param {number|string} x", " * @return {string}", " */", "function f(x) {", " return goog.isString(x) && (1 < 2) ? x : 'a';", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/**", " * @param {*} o1", " * @param {*} o2", " * @return {boolean}", " */", "function deepEquals(o1, o2) {", " if (goog.isObject(o1) && goog.isObject(o2)) {", " if (o1.length != o2.length) {", " return true;", " }", " }", " return false;", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(/** !Object */ obj) {", " if (goog.isDef(obj.myfun)) {", " return obj.myfun();", " }", "}")); typeCheck(LINE_JOINER.join( "function f(/** !Object */ x) {", " if (goog.isArrayLike(x)) {", " return x.length - 1;", " }", "}")); } public void testGoogIsPredicatesFalse() { typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */", "function Foo() {}", "function f(/** Foo */ x) {", " var /** !Foo */ y;", " if (!goog.isNull(x)) {", " y = x;", " }", " y = x;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @param {number=} x */", "function f(x) {", " if (!goog.isDef(x)) {", " var /** undefined */ u = x;", " }", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */", "function Foo() {}", "/** @param {Foo=} x */", "function f(x) {", " if (!goog.isDefAndNotNull(x)) {", " var /** (null|undefined) */ y = x;", " }", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(/** (number|string) */ x) {", " if (!goog.isString(x)) {", " x - 5;", " }", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(/** (number|string) */ x) {", " if (!goog.isNumber(x)) {", " x < 'str';", " }", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(/** (number|boolean) */ x) {", " if (!goog.isBoolean(x)) {", " x - 5;", " }", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(/** (number|!Array<number>) */ x) {", " if (!goog.isArray(x)) {", " x - 5;", " }", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(x) {", " if (goog.isArray(x)) {", " return x[0] - 5;", " }", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(/** (number|function(number)) */ x) {", " if (!goog.isFunction(x)) {", " x - 5;", " }", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */", "function Foo() {}", "/** @param {?Foo} x */", "function f(x) {", " if (!goog.isObject(x)) {", " var /** null */ y = x;", " }", "}")); } public void testGoogTypeof() { typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(/** (number|string) */ x) {", " if (goog.typeOf(x) === 'number') {", " var /** number */ n = x;", " } else {", " var /** string */ s = x;", " }", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(/** (number|string) */ x) {", " if ('number' === goog.typeOf(x)) {", " var /** number */ n = x;", " }", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(/** (number|string) */ x) {", " if (goog.typeOf(x) == 'number') {", " var /** number */ n = x;", " }", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @param {number=} x */", "function f(x) {", " if (goog.typeOf(x) === 'undefined') {", " var /** undefined */ u = x;", " } else {", " var /** number */ n = x;", " }", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @param {string} x */", "function f(x, other) {", " if (goog.typeOf(x) === other) {", " var /** null */ n = x;", " } else {", " x - 5;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS, NewTypeInference.INVALID_OPERAND_TYPE); } public void testSuperClassCtorProperty() throws Exception { String CLOSURE_DEFS = LINE_JOINER.join( "/** @const */ var goog = {};", "goog.inherits = function(child, parent){};"); typeCheck(CLOSURE_DEFS + LINE_JOINER.join( "/** @constructor */function base() {}", "/** @return {number} */ ", " base.prototype.foo = function() { return 1; };", "/** @extends {base}\n * @constructor */function derived() {}", "goog.inherits(derived, base);", "var /** number */ n = derived.superClass_.foo()")); typeCheck(CLOSURE_DEFS + LINE_JOINER.join( "/** @constructor */ function OldType() {}", "/** @param {?function(new:OldType)} f */ function g(f) {", " /**", " * @constructor", " * @extends {OldType}", " */", " function NewType() {};", " goog.inherits(NewType, f);", " NewType.prototype.method = function() {", " NewType.superClass_.foo.call(this);", " };", "}"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(CLOSURE_DEFS + LINE_JOINER.join( "/** @constructor */ function Foo() {}", "goog.inherits(Foo, Object);", "var /** !Object */ b = Foo.superClass_;")); typeCheck(CLOSURE_DEFS + LINE_JOINER.join( "/** @constructor */ function base() {}", "/** @constructor @extends {base} */ function derived() {}", "goog.inherits(derived, base);", "var /** null */ b = derived.superClass_"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck( CLOSURE_DEFS + "var /** !Object */ b = Object.superClass_", NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck( CLOSURE_DEFS + "var o = {x: 'str'}; var q = o.superClass_;", NewTypeInference.INEXISTENT_PROPERTY); typeCheck(CLOSURE_DEFS + "var o = {superClass_: 'str'}; var /** string */ s = o.superClass_;"); } public void testAcrossScopeNamespaces() { typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "(function() {", " /** @constructor */", " ns.Foo = function() {};", "})();", "ns.Foo();"), NewTypeInference.CONSTRUCTOR_NOT_CALLABLE); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "(function() {", " /** @type {string} */", " ns.str = 'str';", "})();", "ns.str - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "(function() {", " /** @constructor */", " ns.Foo = function() {};", "})();", "function f(/** ns.Foo */ x) {}", "f(1);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "(function() {", " /** @constructor */", " ns.Foo = function() {};", "})();", "var /** ns.Foo */ x = 123;"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testQualifiedNamedTypes() { typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @constructor */", "ns.Foo = function() {};", "ns.Foo();"), NewTypeInference.CONSTRUCTOR_NOT_CALLABLE); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @typedef {number} */", "ns.num;", "var /** ns.num */ y = 'str';"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @enum {number} */", "ns.Foo = { A: 1 };", "var /** ns.Foo */ y = 'str';"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E = { A: 1 };", "/** @typedef {number} */", "E.num;", "var /** E.num */ x = 'str';"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testEnumsAsNamespaces() { typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @enum {number} */", "ns.E = {", " ONE: 1,", " TWO: true", "};"), NewTypeInference.INVALID_OBJLIT_PROPERTY_TYPE); typeCheck(LINE_JOINER.join( "/** @enum */", "var E = { A: 1 };", "/** @enum */", "E.E2 = { B: true };", "var /** E */ x = E.A;"), NewTypeInference.INVALID_OBJLIT_PROPERTY_TYPE); typeCheck(LINE_JOINER.join( "/** @enum */", "var E = { A: 1 };", "/** @constructor */", "E.Foo = function(x) {};", "var /** E */ x = E.A;")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @enum {number} */", "ns.E = { A: 1 };", "/** @constructor */", "ns.E.Foo = function(x) {};")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @enum {number} */", "ns.E = { A: 1 };", "/** @constructor */", "ns.E.Foo = function(x) {};", "function f() { ns.E.Foo(); }"), NewTypeInference.CONSTRUCTOR_NOT_CALLABLE); } public void testIncompatibleEnums() { typeCheck(LINE_JOINER.join( "/** @enum {string} */", "var Color = { RED: 'red' };", "/** @enum {string} */", "var Shape = { SQUARE: 'square' };", "/** @param {Shape} shape */", "function f(shape) {", " switch (shape) {", " case Color.RED:", " }", "}"), NewTypeInference.INCOMPATIBLE_STRICT_COMPARISON); } public void testStrictEquality() { typeCheck(LINE_JOINER.join( "var a = 1;", "var b = 'two';", "a === b;"), NewTypeInference.INCOMPATIBLE_STRICT_COMPARISON); typeCheck(LINE_JOINER.join( "var a = 1;", "var b = 'two';", "a !== b;"), NewTypeInference.INCOMPATIBLE_STRICT_COMPARISON); typeCheck(LINE_JOINER.join( "var a = 1;", "var b = 2;", "a !== b;")); typeCheck(LINE_JOINER.join( "var a = 1;", "var b = 2;", "a === b;")); typeCheck(LINE_JOINER.join( "var a = 1;", "var b;", "a === /** @type {?} */ (b);")); typeCheck(LINE_JOINER.join( "/**", " * @param {string|number} a", " * @param {number} b", " */", "function f(a, b) {", " a === b;", "}")); typeCheck(LINE_JOINER.join( "/** @const */", "var obj = { a: 123, b: 234 };", "/** @record */", "function Rec() {}", "Rec.prototype.a;", "Rec.prototype.b;", "function f(/** !Rec */ x) {", " if (x === obj) {", " return 1;", " }", "}")); typeCheck(LINE_JOINER.join( "/** @record */", "function Rec() {}", "Rec.prototype.a;", "Rec.prototype.b;", "/** @record */", "function Rec2() {}", "Rec2.prototype.a;", "Rec2.prototype.b;", "function f(/** !Rec */ x, /** !Rec2 */ y) {", " if (x === y) {", " return 1;", " }", "}")); } public void testStringMethods() { typeCheck(LINE_JOINER.join( "/** @this {String|string} */", "String.prototype.substr = function(start) {};", "/** @const */ var ns = {};", "/** @const */ var s = 'str';", "ns.r = s.substr(2);")); } public void testOutOfOrderDeclarations() { // This is technically valid JS, but we don't support it typeCheck("Foo.STATIC = 5; /** @constructor */ function Foo(){}"); } public void testAbstractMethodsAreTypedCorrectly() { typeCheck(LINE_JOINER.join( "/** @type {!Function} */", "var abstractMethod = function(){};", "/** @constructor */ function Foo(){};", "/** @param {number} index */", "Foo.prototype.m = abstractMethod;", "/** @constructor @extends {Foo} */ function Bar(){};", "/** @override */", "Bar.prototype.m = function(index) {};")); typeCheck(LINE_JOINER.join( "/** @type {!Function} */", "var abstractMethod = function(){};", "/** @constructor */ function Foo(){};", "/** @constructor @extends {Foo} */ function Bar(){};", "/** @param {number} index */", "Foo.prototype.m = abstractMethod;", "/** @override */", "Bar.prototype.m = function(index) {};", "(new Bar).m('str');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @type {!Function} */", "var abstractMethod = function(){};", "/** @constructor */ function Foo(){};", "/** @constructor @extends {Foo} */ function Bar(){};", "/**", " * @param {number} b", " * @param {string} a", " */", "Foo.prototype.m = abstractMethod;", "/** @override */", "Bar.prototype.m = function(a, b) {};", "(new Bar).m('str', 5);"), NewTypeInference.INVALID_ARGUMENT_TYPE, NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @type {!Function} */", "var abstractMethod = function(){};", "/** @constructor */ function Foo(){};", "/** @constructor @extends {Foo} */ function Bar(){};", "/** @type {function(number, string)} */", "Foo.prototype.m = abstractMethod;", "/** @override */", "Bar.prototype.m = function(a, b) {};", "(new Bar).m('str', 5);"), NewTypeInference.INVALID_ARGUMENT_TYPE, NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testUseJsdocOfCalleeForUnannotatedFunctionsInArgumentPosition() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() { /** @type {string} */ this.prop = 'asdf'; }", "/** @param {function(!Foo)} fun */", "function f(fun) {}", "f(function(x) { x.prop = 123; });"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() { /** @type {string} */ this.prop = 'asdf'; }", "function f(/** function(this:Foo) */ x) {}", "f(function() { this.prop = 123; });"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @param {function(string)} fun */", "function f(fun) {}", "f(function(str) { str - 5; });"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @param {function(number, number=)} fun */", "function f(fun) {}", "f(function(num, maybeNum) { num - maybeNum; });"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @param {function(string, ...string)} fun */", "function f(fun) {}", "f(function(str, maybeStrs) { str - 5; });"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @param {function(string)} fun */", "ns.f = function(fun) {}", "ns.f(function(str) { str - 5; });"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @type {function(function(string))} */", "ns.f = function(fun) {}", "ns.f(function(str) { str - 5; });"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @param {function(string)} fun */", "Foo.f = function(fun) {}", "Foo.f(function(str) { str - 5; });"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "(/** @param {function(string)} fun */ function(fun) {})(", " function(str) { str - 5; });"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @param {function(function(function(string)))} outerFun */", "ns.f = function(outerFun) {};", "ns.f(function(innerFun) {", " innerFun(function(str) { str - 5; });", "});"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {function(T)} x", " */", "function f(x) {}", "/** @const */", "var g = f;", "g(function(x) { return x - 1; });")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @param {function(this:Array)} x */", "Foo.prototype.method = function(x) {};", "function f(/** !Foo */ obj) {", " obj.method(function() {", " var /** null */ n = this;", " });", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {number} */", " this.myprop = 123;", "}", "/**", " * @param {!Foo} x", " * @param {function(!Foo, !Foo)} fun", " */", "function f(x, fun) { fun(x, x); }", "f(new Foo, function(x) { x.myprop = 'asdf'; });"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testNamespacesWithNonEmptyObjectLiteral() { typeCheck("/** @const */ var o = { /** @const */ PROP: 5 };"); typeCheck( "var x = 5; /** @const */ var o = { /** @const {number} */ PROP: x };"); typeCheck(LINE_JOINER.join( "var x = 'str';", "/** @const */ var o = { /** @const {string} */ PROP: x };", "function g() { o.PROP - 5; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @const */ ns.o = { /** @const */ PROP: 5 };")); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "var x = 5;", "/** @const */ ns.o = { /** @const {number} */ PROP: x };")); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "var x = 'str';", "/** @const */ ns.o = { /** @const {string} */ PROP: x };", "function g() { ns.o.PROP - 5; }"), NewTypeInference.INVALID_OPERAND_TYPE); // These declarations are not considered namespaces typeCheck( "(function(){ return {}; })().ns = { /** @const */ PROP: 5 };", GlobalTypeInfo.MISPLACED_CONST_ANNOTATION); typeCheck(LINE_JOINER.join( "function f(/** { x : string } */ obj) {", " obj.ns = { /** @const */ PROP: 5 };", "}"), GlobalTypeInfo.MISPLACED_CONST_ANNOTATION); } public void testNamespaceRedeclaredProps() { // TODO(dimvar): fix typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @const */", "ns.Foo = {};", "ns.Foo = { a: 123 };")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @const */", "ns.Foo = {};", "/** @const */", "ns.Foo = { a: 123 };"), GlobalTypeInfo.REDECLARED_PROPERTY); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @const */", "ns.Foo = {};", "/**", " * @const", // @suppress is ignored here b/c there is no @type in the jsdoc. " * @suppress {duplicate}", " */", "ns.Foo = { a: 123 };"), GlobalTypeInfo.REDECLARED_PROPERTY); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @const */", "ns.Foo = {};", "/** @type {number} */", "ns.Foo = 123;"), GlobalTypeInfo.REDECLARED_PROPERTY); typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var en = { A: 5 };", "/** @const */", "en.Foo = {};", "/** @type {number} */", "en.Foo = 123;"), GlobalTypeInfo.REDECLARED_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @const */", "Foo.ns = {};", "/** @const */", "Foo.ns = {};"), GlobalTypeInfo.REDECLARED_PROPERTY); } public void testNominalTypeAliasing() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "var Bar = Foo;", "var /** !Bar */ x = new Foo();")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @constructor */", "ns.Foo = function() {};", "/** @constructor */", "ns.Bar = ns.Foo;", "function g() {", " var /** !ns.Bar */ x = new ns.Foo();", " var /** !ns.Bar */ y = new ns.Bar();", "}")); typeCheck(LINE_JOINER.join( "/** @type {number} */", "var n = 123;", "/** @constructor */", "var Foo = n;"), GlobalTypeInfo.EXPECTED_CONSTRUCTOR); typeCheck(LINE_JOINER.join( "/** @type {number} */", "var n = 123;", "/** @interface */", "var Foo = n;"), GlobalTypeInfo.EXPECTED_INTERFACE); typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "/** @constructor */", "var Bar = Foo;"), GlobalTypeInfo.EXPECTED_CONSTRUCTOR); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @interface */", "var Bar = Foo;"), GlobalTypeInfo.EXPECTED_INTERFACE); typeCheckCustomExterns( DEFAULT_EXTERNS + "var Bar;", LINE_JOINER.join( "/**", " * @constructor", " * @final", " */", "var Foo = Bar;", "var /** !Foo */ x;"), GlobalTypeInfo.EXPECTED_CONSTRUCTOR); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @type {string} */ Foo.staticProp = 'asdf';", "/** @const */", "var exports = {", " /** @const */ Bar: Foo", "};", "function f() { exports.Bar.staticProp - 5; }"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheckCustomExterns(LINE_JOINER.join( DEFAULT_EXTERNS, "/** @constructor */", "function Foo() {}", "/** @const */", "var Bar = Foo;"), ""); } public void testTypeVariablesVisibleInPrototypeMethods() { typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() {}", "Foo.prototype.method = function() {", " /** @type {T} */", " this.prop = 123;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() {}", "/** @param {T} x */", "Foo.prototype.method = function(x) {", " x = 123;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() {}", "/** @param {T} x */", "Foo.prototype.method = function(x) {", " this.prop = x;", "}")); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() {}", "/** @param {T} x */", "Foo.prototype.method = function(x) {", " /** @const */", " this.prop = x;", "}")); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Parent() {}", "/**", " * @constructor", " * @extends {Parent<string>}", " */", "function Child() {}", "Child.prototype.method = function() {", " /** @type {T} */", " this.prop = 123;", "}"), GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME); } public void testInferConstTypeFromEnumProps() { typeCheck(LINE_JOINER.join( "/** @enum */", "var e = { A: 1 };", "/** @const */", "var numarr = [ e.A ];")); typeCheck(LINE_JOINER.join( "/** @enum */", "var e = { A: 1 };", "/** @type {number} */", "e.prop = 123;", "/** @const */", "var x = e.prop;")); } private static final String FORWARD_DECLARATION_DEFINITIONS = LINE_JOINER.join( "/** @const */ var goog = {};", "goog.addDependency = function(file, provides, requires){};", "goog.forwardDeclare = function(name){};"); // A forward declaration for a name A.B allows the name to appear only in // types, not in code. Also, only A.B may appear in the type, not A or A.B.C. public void testForwardDeclarations() { typeCheck(LINE_JOINER.join(FORWARD_DECLARATION_DEFINITIONS, "goog.addDependency('', ['Foo'], []);", "goog.forwardDeclare('Bar');", "function f(/** !Foo */ x) {}", "function g(/** !Bar */ y) {}")); typeCheck(LINE_JOINER.join(FORWARD_DECLARATION_DEFINITIONS, "/** @const */ var ns = {};", "goog.addDependency('', ['ns.Foo'], []);", "goog.forwardDeclare('ns.Bar');", "function f(/** !ns.Foo */ x) {}", "function g(/** !ns.Bar */ y) {}")); typeCheck(LINE_JOINER.join(FORWARD_DECLARATION_DEFINITIONS, "/** @const */ var ns = {};", "goog.forwardDeclare('ns.Bar');", "function f(/** !ns.Baz */ x) {}"), GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME); typeCheck(LINE_JOINER.join(FORWARD_DECLARATION_DEFINITIONS, "goog.forwardDeclare('num');", "/** @type {number} */ var num = 5;", "function f() { var /** null */ o = num; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join(FORWARD_DECLARATION_DEFINITIONS, "goog.forwardDeclare('Foo');", "/** @constructor */ function Foo(){}", "function f(/** !Foo */ x) { var /** null */ n = x; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join(FORWARD_DECLARATION_DEFINITIONS, "goog.forwardDeclare('ns.Foo');", "/** @const */ var ns = {};", "/** @constructor */ ns.Foo = function(){}", "function f(/** !ns.Foo */ x) { var /** null */ n = x; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); // In the following cases the old type inference warned about arg type, // but we allow rather than create synthetic named type typeCheck(LINE_JOINER.join(FORWARD_DECLARATION_DEFINITIONS, "goog.forwardDeclare('Foo');", "function f(/** !Foo */ x) {}", "/** @constructor */ function Bar(){}", "f(new Bar);")); typeCheck(LINE_JOINER.join(FORWARD_DECLARATION_DEFINITIONS, "/** @const */ var ns = {};", "goog.forwardDeclare('ns.Foo');", "function f(/** !ns.Foo */ x) {}", "/** @constructor */ function Bar(){}", "f(new Bar);")); typeCheck(LINE_JOINER.join(FORWARD_DECLARATION_DEFINITIONS, "goog.forwardDeclare('ns.Foo');", "/** @const */", "var ns = {};", "/** @const */", "var c = ns;")); typeCheck(LINE_JOINER.join(FORWARD_DECLARATION_DEFINITIONS, "goog.forwardDeclare('ns.ns2.Foo');", "/** @const */", "var ns = {};", "/** @const */", "ns.ns2 = {};", "/** @const */", "var c = ns;", "var x = new ns.ns2.Foo();"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join(FORWARD_DECLARATION_DEFINITIONS, "goog.forwardDeclare('Foo.Bar');", "/** @constructor */", "function Foo() {}", "var x = new Foo.Bar()"), NewTypeInference.INEXISTENT_PROPERTY); typeCheckCustomExterns(LINE_JOINER.join( DEFAULT_EXTERNS, "/** @constructor */", "function Document() {}"), "goog.forwardDeclare('Document')"); typeCheck(LINE_JOINER.join( "goog.forwardDeclare('a.b');", "/** @type {a.b} */", "var x;")); typeCheck(LINE_JOINER.join( "goog.forwardDeclare('a');", "/** @type {a.b.c} */", "var x;"), GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME); typeCheck(LINE_JOINER.join( "goog.forwardDeclare('a.b');", "/** @type {a} */", "var x;"), GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME); typeCheck(LINE_JOINER.join( "goog.forwardDeclare('a.b');", "/** @const */", "var a = {};", "function f() { /** @type {a.b} */ var x; }")); } public void testDontLookupInParentScopeForNamesWithoutDeclaredType() { typeCheck(LINE_JOINER.join( "/** @type {number} */", "var x;", "function f() {", " var x = true;", "}")); } public void testSpecializationInPropertyAccesses() { typeCheck(LINE_JOINER.join( "var obj = {};", "/** @type {?number} */ obj.n = 123;", "if (obj.n === null) {", "} else {", " obj.n - 5;", "}")); typeCheck(LINE_JOINER.join( "var obj = {};", "/** @type {?number} */ obj.n = 123;", "if (obj.n !== null) {", " obj.n - 5;", "}")); typeCheck(LINE_JOINER.join( "var obj = { n: (1 < 2 ? null : 123) };", "if (obj.n !== null) {", " obj.n - 123;", "}")); typeCheck(LINE_JOINER.join( "var obj = { n: (1 < 2 ? null : 123) };", "if (obj.n !== null) {", " obj.n - 123;", "}", "obj.n - 123;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " this.a = 123;", "}", "/** @constructor */", "function Bar(x) {", " /** @type {Foo} */", " this.b = x;", " if (this.b != null) {", " return this.b.a;", " }", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @type {?boolean} */", "Foo.prototype.prop = true;", "function f(/** !Foo */ x) {", " var /** boolean */ b = x.prop || true;", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {?boolean} */", " this.prop = true;", "}", "function f(/** !Foo */ x) {", " var /** boolean */ b = x.prop || true;", "}")); typeCheck(LINE_JOINER.join( "function f(x) {", " if (x.call) {", " x.call(null);", " } else {", " x();", " }", "}")); typeCheck(LINE_JOINER.join( "Object.prototype.prop;", "function f(/** !Object */ x) {", " if (x.prop === undefined) {}", "}")); typeCheck(LINE_JOINER.join( "function f(/** !Object */ x) {", " if (x.prop !== undefined) {", " x.prop();", " }", "}")); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @struct", " */", "function Foo() {", " /** @type {?Foo} */", " this.prop;", "}", "function f(x) {", " /**@type {!Foo} */", " var y = x;", " function g() {", " if (y.prop == null) {", " return;", " }", " var /** !Foo */ z = y.prop;", " }", "}")); typeCheck(LINE_JOINER.join( "/** @param {{x: (string|undefined)}} opts */", "function takeOptions(opts) {", " var /** string */ x = opts.x || 'some string';", "}")); typeCheck(LINE_JOINER.join( "Object.prototype.name;", "function f(v) {", " if (v.constructor) {", " return v.constructor.name;", " }", "}")); } public void testFunctionReturnTypeSpecialization() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @return {?boolean} */", "Foo.prototype.method = function() { return true; };", "function f(/** !Foo */ x) {", " var /** boolean */ b = x.method() || true;", " b = x.method();", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testAutoconvertBoxedNumberToNumber() { typeCheck( "var /** !Number */ n = 123;", NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck( "var /** number */ n = new Number(123);", NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** !Number */ x, y) {", " return x - y;", "}")); typeCheck(LINE_JOINER.join( "function f(x, /** !Number */ y) {", " return x - y;", "}")); typeCheck(LINE_JOINER.join( "function f(/** !Number */ x) {", " return x + 'asdf';", "}")); typeCheck(LINE_JOINER.join( "function f(/** !Number */ x) {", " return -x;", "}")); typeCheck(LINE_JOINER.join( "function f(/** !Number */ x) {", " x -= 123;", "}")); typeCheck(LINE_JOINER.join( "function f(/** !Number */ x, y) {", " y -= x;", "}")); typeCheck(LINE_JOINER.join( "function f(/** !Number */ x, /** !Array<string>*/ arr) {", " return arr[x];", "}"), NewTypeInference.INVALID_INDEX_TYPE); } public void testAutoconvertBoxedStringToString() { typeCheck( "var /** !String */ s = '';", NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck( "var /** string */ s = new String('');", NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f() {", " var /** !String */ x;", " for (x in { p1: 123, p2: 234 }) ;", "}")); typeCheck(LINE_JOINER.join( "function f(/** !String */ x) {", " return x + 1;", "}")); } public void testAutoconvertBoxedBooleanToBoolean() { typeCheck( "var /** !Boolean */ b = true;", NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck( "var /** boolean */ b = new Boolean(true);", NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** !Boolean */ x) {", " if (x) { return 123; };", "}")); } public void testAutoconvertScalarsToBoxedScalars() { typeCheck(LINE_JOINER.join( "var /** number */ n = 123;", "n.toString();")); typeCheck(LINE_JOINER.join( "var /** boolean */ b = true;", "b.toString();")); typeCheck(LINE_JOINER.join( "var /** string */ s = '';", "s.toString();")); typeCheck(LINE_JOINER.join( "var /** number */ n = 123;", "n.prop = 0;", "n.prop - 5;"), NewTypeInference.ADDING_PROPERTY_TO_NON_OBJECT, NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "var /** number */ n = 123;", "n['to' + 'String'];")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {number} */", " this.prop = 123;", "}", "(new Foo).prop.newprop = 5;"), NewTypeInference.ADDING_PROPERTY_TO_NON_OBJECT); typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E = { A: 1 };", "function f(/** E */ x) {", " return x.toString();", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prototype.toString = function() { return ''; };", "function f(/** (number|!Foo) */ x) {", " return x.toString();", "}")); } public void testConstructorsCalledWithoutNew() { typeCheck(LINE_JOINER.join( "var n = new Number();", "n.prop = 0;", "n.prop - 5;")); typeCheck(LINE_JOINER.join( "var n = Number();", "n.prop = 0;", "n.prop - 5;"), NewTypeInference.ADDING_PROPERTY_TO_NON_OBJECT, NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor @return {number} */ function Foo(){ return 5; }", "var /** !Foo */ f = new Foo;", "var /** number */ n = Foo();")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo(){ return 5; }", "var /** !Foo */ f = new Foo;", "var n = Foo();"), NewTypeInference.CONSTRUCTOR_NOT_CALLABLE); // For constructors, return of ? is interpreted the same as undeclared typeCheck(LINE_JOINER.join( "/** @constructor @return {?} */ function Foo(){}", "var /** !Foo */ f = new Foo;", "var n = Foo();"), NewTypeInference.CONSTRUCTOR_NOT_CALLABLE); } public void testFunctionBind() { // Don't handle specially typeCheck(LINE_JOINER.join( "var obj = { bind: function() { return 'asdf'; } };", "obj.bind() - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x) { return x; }", "f.bind(null, 1, 2);"), NewTypeInference.WRONG_ARGUMENT_COUNT); typeCheck(LINE_JOINER.join( "function f(x) { return x; }", "f.bind();"), NewTypeInference.WRONG_ARGUMENT_COUNT); typeCheck(LINE_JOINER.join( "function f(x) { return x - 1; }", "var g = f.bind(null);", "g();"), NewTypeInference.WRONG_ARGUMENT_COUNT); typeCheck(LINE_JOINER.join( "function f() {}", "f.bind(1);"), NewTypeInference.INVALID_THIS_TYPE_IN_BIND); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {}", "/** @this {!Foo} */", "function f() {}", "f.bind(new Bar);"), NewTypeInference.INVALID_THIS_TYPE_IN_BIND); typeCheck(LINE_JOINER.join( "function f(/** number */ x, /** number */ y) { return x - y; }", "var g = f.bind(null, 123);", "g('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x, y) { return x - y; }", "var g = f.bind(null, 123);", "g('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(/** number */ x) { return x - 1; }", "var g = f.bind(null, 'asdf');", "g() - 3;"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @param {number=} x */", "function f(x) {}", "var g = f.bind(null);", "g();", "g('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @param {...number} var_args */", "function f(var_args) {}", "var g = f.bind(null);", "g();", "g(123, 'asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @param {number=} x */", "function f(x) {}", "f.bind(null, undefined);")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "f.bind(null, 1, 2);")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "var g = f.bind(null, 1);", "g(2);", "g('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); // T was instantiated to ? in the f.bind call. typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "var g = f.bind(null);", "g(2, 'asdf');")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "f.bind(null, 1, 'asdf');"), NewTypeInference.NOT_UNIQUE_INSTANTIATION); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " * @param {T} x", " */", " function Foo(x) {}", "/**", " * @template T", " * @this {Foo<T>}", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "f.bind(new Foo('asdf'), 1, 2);"), NewTypeInference.INVALID_THIS_TYPE_IN_BIND); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " * @param {T} x", " */", " function Foo(x) {}", "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "Foo.prototype.f = function(x, y) {};", "Foo.prototype.f.bind(new Foo('asdf'), 1, 2);"), NewTypeInference.INVALID_THIS_TYPE_IN_BIND); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.bind(new Foo);"), NewTypeInference.CANNOT_BIND_CTOR); // We can't detect that f takes a string typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " * @param {T} x", " */", "function Foo(x) {}", "/**", " * @template T", " * @this {Foo<T>}", " * @param {T} x", " */", "function poly(x) {}", "function f(x) {", " poly.bind(new Foo('asdf'), x);", "}", "f(123);")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {string} */", " this.p = 'asdf';", "}", "(function() { this.p - 5; }).bind(new Foo);"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {number} */", " this.p = 123;", "}", "(function(x) { this.p - x; }).bind(new Foo, 321);")); typeCheck( "(function() {}).bind();", NewTypeInference.WRONG_ARGUMENT_COUNT); } public void testClosureStyleFunctionBind() { typeCheck( "goog.bind(123, null);", NewTypeInference.GOOG_BIND_EXPECTS_FUNCTION); typeCheck(LINE_JOINER.join( "function f(x) { return x; }", "goog.bind(f, null, 1, 2);"), NewTypeInference.WRONG_ARGUMENT_COUNT); typeCheck(LINE_JOINER.join( "function f(x) { return x; }", "goog.bind(f);"), NewTypeInference.WRONG_ARGUMENT_COUNT); typeCheck(LINE_JOINER.join( "function f() {}", "goog.bind(f, 1);"), NewTypeInference.INVALID_THIS_TYPE_IN_BIND); typeCheck(LINE_JOINER.join( "function f(/** number */ x, /** number */ y) { return x - y; }", "var g = goog.bind(f, null, 123);", "g('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(/** number */ x) { return x - 1; }", "var g = goog.partial(f, 'asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(/** number */ x) { return x - 1; }", "var g = goog.partial(f, 'asdf');", "g() - 3;"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " if (typeof x == 'function') {", " goog.bind(x, {}, 1, 2);", " }", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {string} */", " this.p = 'asdf';", "}", "goog.bind(function() { this.p - 5; }, new Foo);"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck("goog.partial(function(x) {}, 123)"); typeCheck("goog.bind(function() {}, null)();"); } public void testPlusBackwardInference() { typeCheck(LINE_JOINER.join( "function f(/** number */ x, w) {", " var y = x + 2;", " function g() { return (y + 2) - 5; }", "}")); typeCheck(LINE_JOINER.join( "function f(/** number */ x, w) {", " function h() { return (w + 2) - 5; }", "}")); } public void testPlus() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @param {!Foo} x */", "function f(x, i) {", " var /** string */ s = x[i];", " var /** number */ y = x[i] + 123;", "}")); typeCheck(LINE_JOINER.join( "function f(/** ? */ x) {", " var /** string */ s = '' + x;", "}")); typeCheck(LINE_JOINER.join( "function f(/** ? */ x) {", " var /** number */ s = '' + x;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** ? */ x, /** ? */ y) {", " var /** number */ s = x + y;", "}")); typeCheck(LINE_JOINER.join( "function f(/** * */ x) {", " var /** number */ n = x + 1;", " var /** string */ s = 1 + x;", "}"), NewTypeInference.INVALID_OPERAND_TYPE, NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "var /** number */ n = 1 + null;", "var /** string */ s = 1 + null;"), NewTypeInference.INVALID_OPERAND_TYPE, NewTypeInference.INVALID_OPERAND_TYPE, NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "var /** number */ n = undefined + 2;", "var /** string */ s = undefined + 2;"), NewTypeInference.INVALID_OPERAND_TYPE, NewTypeInference.INVALID_OPERAND_TYPE, NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "var /** number */ n = 3 + true;", "var /** string */ s = 3 + true;"), NewTypeInference.INVALID_OPERAND_TYPE, NewTypeInference.INVALID_OPERAND_TYPE, NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheckCustomExterns( DEFAULT_EXTERNS + "/** @type {number} */ var NaN;", LINE_JOINER.join( "var /** number */ n = NaN + 1;", "var /** string */ s = NaN + 1;"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testUndefinedFunctionCtorNoCrash() { typeCheckCustomExterns("", "function f(x) {}", GlobalTypeInfo.FUNCTION_CONSTRUCTOR_NOT_DEFINED); // Test that NTI is not run typeCheckCustomExterns("", "function f(x) { 1 - 'asdf'; }", GlobalTypeInfo.FUNCTION_CONSTRUCTOR_NOT_DEFINED); } public void testTrickyPropertyJoins() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @type {number} */", "Foo.prototype.length;", "/** @param {{length:number}|!Foo} x */", "function f(x) {", " return x.length - 123;", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @type {number} */", "Foo.prototype.length;", "/** @param {null|{length:number}|!Foo} x */", "function f(x) {", " return x.length - 123;", "}"), NewTypeInference.NULLABLE_DEREFERENCE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @type {number} */", "Foo.prototype.length;", "/** @param {null|!Foo|{length:number}} x */", "function f(x) {", " return x.length - 123;", "}"), NewTypeInference.NULLABLE_DEREFERENCE); } public void testJoinOfClassyAndLooseObject() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo(){}", "/** @type {number} */", "Foo.prototype.p = 5;", "function f(o) {", " if (o.p == 5) {", " (function(/** !Foo */ x){})(o);", " }", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() { this.p = 123; }", "function f(x) {", " var y;", " if (x.p) {", " y = x;", " } else {", " y = new Foo;", " y.prop = 'asdf';", " }", " y.p - 123;", "}")); } public void testJoinWithTopObject() { typeCheck(LINE_JOINER.join( "/** @param {!Function|!Object} x */", "function f(x) {}", "f({ a: 1, b: 2 });")); typeCheck(LINE_JOINER.join( "function f(x) {", " var y;", " if (x) {", " y = {};", " } else {", " y = { focus: 123 };", " }", " return y.focus;", "}")); } public void testJoinOfGenericNominalTypes() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {}", "/**", " * @param {(!Array<number>| !Array<!Foo> | !Array<!Bar>)} x", " * @param {!Array<number>} y", " */", "function f(x, y, z) {", " x = y;", "}")); } public void testUnificationWithSubtyping() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @constructor @extends {Foo} */ function Bar() {}", "/** @constructor @extends {Foo} */ function Baz() {}", "/**", " * @template T", " * @param {T|!Foo} x", " * @param {T} y", " * @return {T}", " */", "function f(x, y) { return y; }", "/** @param {!Bar|!Baz} x */", "function g(x) {", " f(x, 123) - 123;", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Parent() {}", "/** @constructor @extends {Parent} */", "function Child() {}", "/**", " * @template T", " * @param {T|!Parent} x", " * @return {T}", " */", "function f(x) { return /** @type {?} */ (x); }", "function g(/** (number|!Child) */ x) {", " f(x) - 5;", "}")); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Parent() {}", "/**", " * @constructor", " * @extends {Parent<number>}", " */", "function Child() {}", "/**", " * @template T", " * @param {!Parent<T>} x", " */", "function f(x) {}", "/**", " * @param {!Child} x", " */", "function g(x) { f(x); }")); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Parent() {}", "/**", " * @constructor", " * @template U", " * @extends {Parent<U>}", " */", "function Child() {}", "/**", " * @template T", " * @param {!Child<T>} x", " */", "function f(x) {}", "/**", " * @param {!Parent<number>} x", " */", "function g(x) { f(x); }"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @constructor", " */", "function High() {}", "/** @constructor @extends {High<number>} */", "function Low() {}", "/**", " * @template T", " * @param {!High<T>} x", " * @return {T}", " */", "function f(x) { return /** @type {?} */ (null); }", "var /** string */ s = f(new Low);"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function High() {}", "/** @return {T} */", "High.prototype.get = function() { return /** @type {?} */ (null); };", "/**", " * @constructor", " * @template U", " * @extends {High<U>}", " */", "function Low() {}", "/**", " * @template V", " * @param {!High<V>} x", " * @return {V}", " */", "function f(x) { return x.get(); }", "/** @param {!Low<number>} x */", "function g(x) {", " var /** number */ n = f(x);", " var /** string */ s = f(x);", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @interface", " * @template T", " */", "function High() {}", "/**", " * @constructor", " * @template T", " * @implements {High<T>}", " */", "function Mid() {}", "/**", " * @constructor", " * @template T", " * @extends {Mid<T>}", " * @param {T} x", " */", "function Low(x) {}", "/**", " * @template T", " * @param {!High<T>} x", " * @return {T}", " */", "function f(x) {", " return /** @type {?} */ (null);", "}", "f(new Low('asdf')) - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {function(function(T=))} y", " */", "function googPromiseReject(x, y) {}", "googPromiseReject(123, function(x) { x(123); } )")); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() {}", "/** @constructor */", "function Bar() {}", "/** @type {number} */", "Bar.prototype.length;", "/**", " * @template T", " * @param {{length:number}|Foo<T>} x", " */", "function h(x) {}", "h(new Bar);")); typeCheck(LINE_JOINER.join( "function f(x) {", " Array.prototype.slice.call(arguments, 1);", "}")); } public void testArgumentsArray() { typeCheck("function f() { arguments = 123; }"); typeCheck( "function f(x, i) { return arguments[i]; }"); typeCheck( "function f(x) { return arguments['asdf']; }", NewTypeInference.INVALID_INDEX_TYPE); // Arguments is array-like, but not Array typeCheck( "function f() { return arguments.splice(); }", NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "function f(x, i) { return arguments[i]; }", "f(123, 'asdf')"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f() {", " var arguments = 1;", " return arguments - 1;", "}")); typeCheck(LINE_JOINER.join( "/** @param {string} var_args */", "function f(var_args) {", " return arguments[0];", "}", "f('asdf') - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @param {string} var_args */", "function f(var_args) {", " var x = arguments;", " return x[0];", "}", "f('asdf') - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x, i) {", " x < i;", " arguments[i];", "}", "f('asdf', 0);"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testGenericResolutionWithPromises() { typeCheck(LINE_JOINER.join( "/**", " * @param {function():(T|!Promise<T>)} x", " * @return {!Promise<T>}", " * @template T", " */", "function f(x) { return /** @type {?} */ (null); }", "function g(/** function(): !Promise<number> */ x) {", " var /** !Promise<number> */ n = f(x);", "}")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {(T|!Promise<T>)} x", " * @return {T}", " */", "function f(x) { return /** @type {?} */ (null); }", "function g(/** !Promise<number> */ x) {", " var /** number */ n = f(x);", " var /** string */ s = f(x);", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testFunctionCallProperty() { typeCheck(LINE_JOINER.join( "function f(/** number */ x) {}", "f.call(null, 'asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(/** number */ x) {}", "f.call(null);"), NewTypeInference.WRONG_ARGUMENT_COUNT); typeCheck(LINE_JOINER.join( "function f(/** number */ x) {}", "f.call(null, 1, 2);"), NewTypeInference.WRONG_ARGUMENT_COUNT); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @param {number} x */", "Foo.prototype.f = function(x) {};", "Foo.prototype.f.call({ a: 123}, 1);"), NewTypeInference.INVALID_ARGUMENT_TYPE); // We don't infer anything about a loose function from a .call invocation. typeCheck(LINE_JOINER.join( "function f(x) {", " x(123) - 5;", " x.call(null, 'asdf');", "}", "f(function(/** string */ s) { return s; });"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "function f(x) { return x; }", "var /** number */ n = f.call(null, 'asdf');"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "new Foo.call(null);"), NewTypeInference.NOT_A_CONSTRUCTOR); } public void testFunctionApplyProperty() { // We only check the receiver argument of a .apply invocation, unless the applied function // uses typed var_args. typeCheck(LINE_JOINER.join( "function f(/** number */ x) {}", "f.apply(null, ['asdf']);")); typeCheck(LINE_JOINER.join( "function f(/** number */ x) {}", "f.apply(null, 'asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); // We don't check arity in the array argument typeCheck(LINE_JOINER.join( "function f(/** number */ x) {}", "f.apply(null, []);")); typeCheck(LINE_JOINER.join( "function f(/** number */ x) {}", "f.apply(null, [], 1, 2);"), NewTypeInference.WRONG_ARGUMENT_COUNT); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @param {number} x */", "Foo.prototype.f = function(x) {};", "Foo.prototype.f.apply({ a: 123}, [1]);"), NewTypeInference.INVALID_ARGUMENT_TYPE); // We don't infer anything about a loose function from a .apply invocation. typeCheck(LINE_JOINER.join( "function f(x) {", " x(123) - 5;", " x.apply(null, ['asdf']);", "}", "f(function(/** string */ s) { return s; });"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "function f(x) { return x; }", "var /** number */ n = f.apply(null, ['asdf']);")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "new Foo.apply(null);"), NewTypeInference.NOT_A_CONSTRUCTOR); typeCheck(LINE_JOINER.join( "function f(x) {}", "function g() { f.apply(null, arguments); }")); typeCheck(LINE_JOINER.join( "/** @param {...number} var_args */", "String.fromCharCode = function(var_args) {};", "/** @param {!IArrayLike<number>} x */", "function f(x) {", " String.fromCharCode.apply(null, x);", "}")); typeCheck(LINE_JOINER.join( "/** @param {...number} var_args */", "String.fromCharCode = function(var_args) {};", "/** @param {!IArrayLike<string>} x */", "function f(x) {", " String.fromCharCode.apply(null, x);", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @param {...number} var_args */", "function f(var_args) {}", "f.apply({}, null);")); } public void testDontWarnOnPropAccessOfBottom() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Bar() {", " /** @type {?Object} */", " this.obj;", "}", "Bar.prototype.f = function() {", " this.obj = {};", " if (this.obj != null) {}", "};")); typeCheck(LINE_JOINER.join( "var x = {};", "x.obj = {};", "if (x.obj != null) {}")); typeCheck(LINE_JOINER.join( "var x = {};", "x.obj = {};", "if (x['obj'] != null) {}")); } public void testClasslessObjectsHaveBuiltinProperties() { typeCheck(LINE_JOINER.join( "function f(/** !Object */ x) {", " return x.hasOwnProperty('asdf');", "}")); typeCheck(LINE_JOINER.join( "function f(/** { a: number } */ x) {", " return x.hasOwnProperty('asdf');", "}")); typeCheck(LINE_JOINER.join( "var x = {};", "x.hasOwnProperty('asdf');")); typeCheck(LINE_JOINER.join( "var x = /** @struct */ { a: 1 };", "x.hasOwnProperty('asdf');")); typeCheck(LINE_JOINER.join( "var x = /** @dict */ { 'a': 1 };", "x['hasOwnProperty']('asdf');")); } public void testInferThisInSimpleInferExprType() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @const */ var x = this", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {string} */", " this.p = 'asdf';", "}", "Foo.prototype.m = function() {", " goog.bind(function() { this.p - 5; }, this);", "};"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testNoInexistentPropWarningsForDicts() { typeCheck(LINE_JOINER.join( "/** @constructor @dict */", "function Foo() {}", "(new Foo)['prop'] - 1;")); } public void testAddingPropsToExpandosInWhateverScopes() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f(/** !Foo */ x) {", " x.prop = 123;", "}", "(new Foo).prop - 1;")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f() {", " (new Foo).prop = 123;", "}", "var s = (new Foo).prop;"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f(/** !Foo */ x) {", " x.prop = 'asdf';", // we don't declare the type to be string "}", "(new Foo).prop - 1;")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f(/** !Foo */ x) {", " var y = x.prop;", "}", "var z = (new Foo).prop;"), NewTypeInference.INEXISTENT_PROPERTY, NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f() {", " var x = new Foo;", " x.prop = 123;", // x not inferred as Foo during GTI "}", "(new Foo).prop - 1;"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f(/** !Foo */ x) {", " /** @const */", " x.prop = 123;", "}", "(new Foo).prop - 1;"), GlobalTypeInfo.MISPLACED_CONST_ANNOTATION); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f(/** !Foo */ x) {", " /** @type {string} */", " x.prop = 'asdf';", "}", "(new Foo).prop - 123;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function High() {}", "function f(/** !High */ x) {", " /** @type {string} */", " x.prop = 'asdf';", "}", "/** @constructor @extends {High} */", "function Low() {}", "(new Low).prop - 123;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {number} */", " this.prop = 123;", "}", "function f(/** !Foo */ x) {", " /** @type {string} */", " x.prop = 'asdf';", "}"), GlobalTypeInfo.REDECLARED_PROPERTY, NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {number} */", " this.prop = 123;", "}", "function f(/** !Foo */ x) {", " x.prop = 'asdf';", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @type {number} */", "Foo.prototype.prop = 123;", "function f(/** !Foo */ x) {", " x.prop = 'asdf';", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() {}", "function f(/** !Foo<string> */ x) {", " x.prop = 123;", "}", "(new Foo).prop - 1;")); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo(/** T */ x) {}", "/** @template U */", "function addProp(/** !Foo<U> */ x, /** U */ y) {", " /** @type {U} */ x.prop = y;", " return x;", "}", "addProp(new Foo(1), 5).prop - 1;")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prototype.m = function() {", " this.prop = 123;", "}", "function f(/** !Foo */ x) {", " /** @type {number} */", " x.prop = 123;", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prototype.prop = 123;", "/** @type {!Foo} */", "var x = new Foo;", "/** @type {number} */", "x.prop = 123;")); } public void testAddingPropsToObject() { typeCheck(LINE_JOINER.join( "Object.prototype.m = function() {", " /** @type {string} */", " this.prop = 'asdf';", "};", "(new Object).prop - 123;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(/** !Object */ x) {", " /** @type {string} */", " x.prop = 'asdf';", "}", "(new Object).prop - 123;"), NewTypeInference.INEXISTENT_PROPERTY); } public void testFunctionSubtypingWithReceiverTypes() { typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {function(this:T)} x", " */", "function f(x) {}", "/** @constructor */", "function Foo() {}", "f(/** @this{Foo} */ function () {});")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {function(this:T)} y", " */", "function f(x, y) {}", "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {}", "f(new Bar, /** @this{Foo} */function () {});"), NewTypeInference.NOT_UNIQUE_INSTANTIATION); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {", " /** @type {string} */ this.p = 'asdf';", "}", "/**", " * @this {Foo}", " * @param {number} x", " */", "function f(x) { this.p = x; }", "/** @param {function(number)} x */", "function g(x) { x.call(new Bar, 123); }", // Passing a fun w/ @this to a context that expects a fun w/out @this. "g(f);"), NewTypeInference.INVALID_ARGUMENT_TYPE); // Sets Bar#p to a number. We could maybe find this, non trivial though. typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor @extends {Foo} */", "function Bar() {", " /** @type {string} */ this.p = 'asdf';", "}", "/**", " * @this {Foo}", " * @param {number} x", " */", "function f(x) { this.p = x; }", "/** @param {function(number)} x */", "f.call(new Bar, 123);")); } public void testBadWorksetConstruction() { typeCheck(LINE_JOINER.join( "function f(x) {", " for (var i = 0; i < 10; i++) {", " break;", " }", " x++;", "};")); } public void testFunctionNamespacesThatArentProperties() { typeCheck(LINE_JOINER.join( "function f(x) {}", "/** @type {number} */", "f.prop = 123;", "function h() {", " var /** string */ s = f.prop;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(x) {}", "function g() {", " /** @type {number} */", " f.prop = 123;", "}", "function h() {", " var /** string */ s = f.prop;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(x) {}", "/** @constructor */", "f.Foo = function() {};", "/** @param {!f.Foo} x */", "function g(x) {}", "function h() { g(new f.Foo()); }")); typeCheck(LINE_JOINER.join( "function f(x) {}", "/** @constructor */", "f.Foo = function() {};", "/** @param {!f.Foo} x */", "function g(x) {}", "function h() { g(123); }"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(/** number */ x) {}", "/** @type {number} */", "f.prop = 123;", "f('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(/** number */ x) {}", "/** @type {string} */", "f.prop = 'str';", "function g() { f(f.prop); }"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x) { return x - 1; }", "/** @type {string} */", "f.prop = 'str';", "function g() { f(f.prop); }"), NewTypeInference.INVALID_ARGUMENT_TYPE); // f is ? in NTI, so we get no warning for f.e.A. typeCheck(LINE_JOINER.join( "f = function() {};", "/** @enum */", "f.e = { A: 1 };", "function g() { var /** string */ s = f.e.A; }")); typeCheck(LINE_JOINER.join( "var f = function() {};", "/** @enum */", "f.e = { A: 1 };", "function g() { var /** string */ s = f.e.A; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "function f(x) {}", "/** @type {number} */", "f.prop;"), LINE_JOINER.join( "function h() {", " var /** string */ s = f.prop;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "function f(x) {}", "/** @constructor */", "f.Foo = function() {};"), LINE_JOINER.join( "/** @param {!f.Foo} x */", "function g(x) {}", "function h() { g(123); }"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {};", "/** @const */ f.subns = {};", "function g() {", " /** @type {number} */", " f.subns.prop = 123;", "}", "function h() {", " var /** string */ s = f.subns.prop;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f() {", " f.prop = function() {};", "}")); } public void testFunctionNamespacesThatAreProperties() { typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "ns.f = function(x) {};", "/** @type {number} */", "ns.f.prop = 123;", "function h() {", " var /** string */ s = ns.f.prop;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "ns.f = function(x) {};", "function g() {", " /** @type {number} */", " ns.f.prop = 123;", "}", "function h() {", " var /** string */ s = ns.f.prop;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "ns.f = function(x) {};", "/** @constructor */", "ns.f.Foo = function() {};", "/** @param {!ns.f.Foo} x */", "function g(x) {}", "function h() { g(new ns.f.Foo()); }")); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "ns.f = function(x) {};", "/** @constructor */", "ns.f.Foo = function() {};", "/** @param {!ns.f.Foo} x */", "function g(x) {}", "function h() { g(123); }"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "ns.f = function(/** number */ x) {};", "/** @type {number} */", "ns.f.prop = 123;", "ns.f('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "ns.f = function(/** number */ x) {};", "/** @type {string} */", "ns.f.prop = 'str';", "function g() { ns.f(ns.f.prop); }"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "ns.f = function(x) { return x - 1; };", "/** @type {string} */", "ns.f.prop = 'asdf';", "ns.f(ns.f.prop);"), NewTypeInference.INVALID_ARGUMENT_TYPE); // TODO(dimvar): Needs deferred checks for known property-functions. // typeCheck(LINE_JOINER.join( // "/** @const */ var ns = {};", // "ns.f = function(x) { return x - 1; };", // "/** @type {string} */", // "ns.f.prop = 'asdf';", // "function g() { ns.f(ns.f.prop); }"), // NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "/** @const */ var ns = {};", "ns.f = function(/** number */ x) {};", "/** @type {number} */", "ns.f.prop;"), LINE_JOINER.join( "function h() {", " var /** string */ s = ns.f.prop;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "/** @const */ var ns = {};", "ns.f = function(/** number */ x) {};", "/** @constructor */", "ns.f.Foo = function() {};"), LINE_JOINER.join( "/** @param {!ns.f.Foo} x */", "function g(x) {}", "function h() { g(123); }"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @enum */ var e = { A: 1 };", "e.f = function(x) {};", "function g() {", " /** @type {number} */", " e.f.prop = 123;", "}", "function h() {", " var /** string */ s = e.f.prop;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {};", "Foo.f = function(x) {};", "function g() {", " /** @type {number} */", " Foo.f.prop = 123;", "}", "function h() {", " /** @type {string} */", " Foo.f.prop = 'asdf';", "}"), GlobalTypeInfo.REDECLARED_PROPERTY); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "ns.f = function(x) {};", "/** @const */ ns.f.subns = {};", "function g() {", " /** @type {number} */", " ns.f.subns.prop = 123;", "}", "function h() {", " var /** string */ s = ns.f.subns.prop;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testInterfaceMethodNoReturn() { typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @interface */", "ns.Foo = function() {};", "/** @return {number} */", "ns.Foo.prototype.m = function() {};")); // Don't crash when ns.Foo is not defined. typeCheck( LINE_JOINER.join( "Object.prototype.Foo;", "ns.Foo.prototype.m = function() {};")); } public void testUnknownNewAndThisFunctionAnnotations() { // Don't warn for unknown this typeCheck(LINE_JOINER.join( "/** @this {Number|String} */", "function f() {", " return this.toString();", "}"), NewTypeInference.NULLABLE_DEREFERENCE); // Don't warn for unknown this typeCheck(LINE_JOINER.join( "/** @type {function(this:(Number|String))} */", "function f() {", " return this.toString();", "}"), NewTypeInference.NULLABLE_DEREFERENCE); // Don't warn that f isn't a constructor typeCheck(LINE_JOINER.join( "/** @type {function(new:(!Number|!String))} */", "function f() {}", "var x = new f();")); } public void testFixAdditionOfStaticCtorProps() { // TODO(dimvar): The expected formal type is string if g appears before f // and number o/w. Also, we allow adding named types to ctors in any scope, // but other properties only in the same scope where the ctor is defined. // Must be consistent about which scopes can add new props. typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function g() {", " /**", " * @constructor", " * @param {string} x", " */", " Foo.Bar = function(x) {};", "}", "function f() {", " /**", " * @constructor", " * @param {number} x", " */", " Foo.Bar = function(x) {};", "}", "function h() {", " return new Foo.Bar(true);", "}"), GlobalTypeInfo.REDECLARED_PROPERTY, NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testFinalizingRecursiveSubnamespaces() { typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @constructor */", "ns.Foo = function() {};", "/** @const */", "ns.Foo.ns2 = ns;")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @const */", "Foo.alias = Foo;")); typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "/** @constructor @implements {Foo} */", "Foo.Bar = function() {};", "/** @const */", "var exports = Foo;")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @const */", "Foo.alias = Foo;", "var x = new Foo.alias.alias.alias.alias();")); } public void testAddingPropsToTypedefs() { typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "/** @typedef {number} */", "var num2;", "/** @type {number} */", "num2.prop;"), "/** empty code */", GlobalTypeInfo.CANNOT_ADD_PROPERTIES_TO_TYPEDEF); typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "/** @const */ var ns = {};", "/** @typedef {number} */", "ns.num2;", "/** @type {number} */", "ns.num2.prop;"), "/** empty code */", GlobalTypeInfo.CANNOT_ADD_PROPERTIES_TO_TYPEDEF); typeCheck(LINE_JOINER.join( "/** @typedef {number} */", "var num2;", "/** @type {number} */", "num2.prop;"), GlobalTypeInfo.CANNOT_ADD_PROPERTIES_TO_TYPEDEF); // TODO(dimvar): fix handling of namespace types in markAndGetTypeOfPreanalyzedNode // and uncomment // typeCheck(LINE_JOINER.join( // "/** @const */ var ns = {};", // "/** @typedef {number} */", // "ns.num2;", // "/** @type {number} */", // "ns.num2.prop = 123;"), // GlobalTypeInfo.CANNOT_ADD_PROPERTIES_TO_TYPEDEF); } public void testNamespacePropsAfterAliasing() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @const */", "var exports = Foo;", "Foo.prop = 123;", "exports.prop2 = 234;", "function f() {", " return exports.prop + Foo.prop2;", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @const */", "var exports = Foo;", "/** @type {number} */ exports.prop = 123;", "/** @type {string} */ exports.prop2 = 'str';", "function f() {", " return Foo.prop - Foo.prop2;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */", "var $jscomp$scope = {};", "/** @const */", "var exports = {};", "/** @constructor */", "$jscomp$scope.Foo = function() {};", "/** @constructor */", "$jscomp$scope.Foo.Bar = function() {};", "/** @const */", "exports.Foo = $jscomp$scope.Foo;", "/** @type {exports.Foo} */", "var w = 123;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @const */", "var $jscomp$scope = {};", "/** @const */", "var exports = {};", "/** @constructor */", "$jscomp$scope.Foo = function() {};", "/** @constructor */", "$jscomp$scope.Foo.Bar = function() {};", "/** @const */", "exports.Foo = $jscomp$scope.Foo;", "/** @type {exports.Foo.Bar} */", "var z = 123;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {}", "/** @const */", "var x = ns;", "/** @type {number} */", "ns.prop = 123;", "function f() {", " var /** string */ s = x.prop;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @enum */", "var e = { A: 1 }", "/** @const */", "var x = e;", "/** @type {number} */", "e.prop = 123;", "function f() {", " var /** string */ s = x.prop;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @interface */", "function Parent() {}", "/** @return {number} */", "Parent.prototype.method = function() {};", "/** @constructor @implements {Parent} */", "function Foo() {}", "Foo.prototype.method = function() { return 1; };", "/** @const */", "var exports = Foo;", "function f() {", " var /** null */ x = exports.prototype.method.call(new Foo);", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @interface */", "function Parent() {}", "/** @return {number} */", "Parent.prototype.method = function() {};", "/** @constructor @implements {Parent} */", "function Foo() {}", "Foo.prototype.method = function() { return 1; };", "/** @const */", "var exports = Foo;", "function f() {", " var /** null */ x = (new exports).method();", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @interface */", "function Parent() {}", "/** @return {number} */", "Parent.prototype.method = function() {};", "/** @constructor @implements {Parent} */", "function Foo() {}", "Foo.prototype.method = function() { return 1; };", "/** @const */", "var exports = new Foo;", "function f() {", " var /** null */ x = exports.method();", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @interface */", "function I1() {}", "/** @return {number|string} */", "I1.prototype.method = function() {};", "/** @interface */", "function I2() {}", "/** @return {number|boolean} */", "I2.prototype.method = function() {};", "/**", " * @constructor", " * @implements {I1}", " * @implements {I2}", " */", "function Foo() {}", "Foo.prototype.method = function() { return 1; };", "/** @const */", "var exports = Foo;", "function f() {", " var /** null */ x = exports.prototype.method.call(new Foo);", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function h() {", " /** @type {ns.ns2.Foo} */", " var w = 123;", " /** @type {ns.ns2.Foo.Bar} */", " var z = 123;", "}", "/** @const */", "var $jscomp = {};", "/** @const */", "$jscomp.scope = {};", "/** @const */", "var ns = {};", "/** @const */", "ns.ns2 = {};", "/** @constructor */", "$jscomp.scope.Foo = function() {};", "function f() {", " /** @constructor */", " $jscomp.scope.Foo.Bar = function() {};", "}", "/** @const */", "ns.ns2.Foo = $jscomp.scope.Foo;"), NewTypeInference.MISTYPED_ASSIGN_RHS, NewTypeInference.MISTYPED_ASSIGN_RHS); // 2 levels of aliasing typeCheck(LINE_JOINER.join( "function f() { Foo.prop - 1; }", "function g() { Foo2.prop - 1; }", "/** @constructor */", "var Foo = function() {};", "/** @const */", "var Foo2 = Foo;", "/** @const */", "var Foo3 = Foo2;", "/** @type {string} */", "Foo3.prop = '';"), NewTypeInference.INVALID_OPERAND_TYPE, NewTypeInference.INVALID_OPERAND_TYPE); } public void testNamespaceAliasingWithoutJsdoc() { typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "/** @const */", "var ns = {};", "/** @constructor */", "ns.Foo = function() {};", "var ns2 = ns;"), LINE_JOINER.join( "/** @type {!ns2.Foo} */", "var x;")); // In non externs we still require @const typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @constructor */", "ns.Foo = function() {};", "var ns2 = ns;", "/** @type {!ns2.Foo} */", "var x;"), GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME); // spurious warning, the second assignment to ns.prop is ignored. typeCheckCustomExterns(LINE_JOINER.join( DEFAULT_EXTERNS, "/** @const */", "var ns = {};", "/** @const */", "var ns2 = {};", "/** @type {number} */", "ns2.x;", "/** @const */", "var ns3 = {};", "/** @type {string} */", "ns3.x;", "ns.prop = ns2;", "ns.prop = ns3;"), "function f() { var /** string */ s = ns.prop.x; }", NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @const */", "var module$input0 = {};", "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {}", "module$input0.default = Foo;", "function f() {", " var /** !Bar */ a = new module$input0.default();", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @constructor */", "ns.Foo = function() {};", "/** @constructor */", "ns.Bar = function() {};", "ns.Baz = ns.Foo;", "ns.Baz = ns.Bar;")); } public void testOptionalPropertiesInRecordTypes() { typeCheck("var /** { a: (number|undefined) } */ obj = {};"); typeCheck(LINE_JOINER.join( "var /** { a: (number|undefined) } */ obj;", "var x;", "if (1 < 2) {", " x = { a: 1 };", "} else {", " x = {};", "}", "obj = x;")); typeCheck(LINE_JOINER.join( "function f(/** { a: (number|undefined) } */ x) {}", "f({});")); typeCheck(LINE_JOINER.join( "function f(/** { a: (number|undefined) } */ x) {}", "f({ a: undefined });")); typeCheck(LINE_JOINER.join( "function f(/** { a: (number|undefined) } */ x) {}", "f({ a: 'asdf' });"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @param {{foo: (?|undefined)}} x */", "function g(x) {}", "g({bar:123});")); typeCheck(LINE_JOINER.join( "/** @param {{foo: ?}} x */", "function g(x) {}", "g({bar:123});"), NewTypeInference.INVALID_ARGUMENT_TYPE); // Don't warn about possibly inexistent property. The property type includes // undefined, so the context where the property is used determines if there // will be a warning. typeCheck(LINE_JOINER.join( "function f(/** {a: (number|undefined)} */ x) {", " return x.a - 5;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(/** {a: (number|undefined)} */ x) {", " var /** number|undefined */ y = x.a;", "}")); } public void testJoinWithTruthyOrFalsy() { typeCheck(LINE_JOINER.join( "function f(x) {", " var y;", " if (x.p) {", " y = x;", " } else {", " y = { p: 123 };", " }", " y.p - 1;", "}")); typeCheck(LINE_JOINER.join( "function f(x) {", " var y;", " if (x.p) {", " y = { p: 123 };", " } else {", " y = x;", " }", " y.p - 1;", "}")); } public void testSpecializeTypesAfterNullableDereference() { typeCheck(LINE_JOINER.join( "function f(/** (null | { prop: (null|number) }) */ x) {", " if (x.prop !== null) {", " return x.prop - 1;", " }", "}"), NewTypeInference.NULLABLE_DEREFERENCE); typeCheck(LINE_JOINER.join( "function f(/** (null | { prop: (null|number) }) */ x) {", " if (x.prop === 1) {", " var /** number */ n = x.prop;", " }", "}"), NewTypeInference.NULLABLE_DEREFERENCE); typeCheck(LINE_JOINER.join( "function f(/** (null | { prop: (null|number) }) */ x) {", " if (x.prop == null) {", " return;", " }", " return x.prop - 1;", "}"), NewTypeInference.NULLABLE_DEREFERENCE); typeCheck(LINE_JOINER.join( "function f(/** (null | { prop: (null|number) }) */ x) {", " if (x.prop == null) {", " var /** (null|undefined) */ y = x.prop;", " }", "}"), NewTypeInference.NULLABLE_DEREFERENCE); typeCheck(LINE_JOINER.join( "function f(/** ?String|string */ x) {", " return x.length - 1;", "}"), NewTypeInference.NULLABLE_DEREFERENCE); typeCheck(LINE_JOINER.join( "function f(/** ?Object */ x) {", " if (x.prop) {", " x.prop();", " }", "}"), NewTypeInference.NULLABLE_DEREFERENCE); typeCheck(LINE_JOINER.join( "function f(/** ?Object */ x) {", " if (typeof x.prop == 'function') {", " x.prop();", " }", "}"), NewTypeInference.NULLABLE_DEREFERENCE); typeCheck(LINE_JOINER.join( "function f(/** ?Object */ x) {", " var y = x;", " if (goog.isDef(y.prop)) {", " y.prop();", " }", "}"), NewTypeInference.NULLABLE_DEREFERENCE); } public void testSingletonGetter() { typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */", "function Bar() {}", "/** @constructor */", "function Foo() {}", "goog.addSingletonGetter(Foo);", "var /** !Foo */ x = Foo.getInstance();", "var /** !Bar */ b = Foo.getInstance();"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */", "function Bar() {}", "/** @constructor */", "function Foo() {}", "goog.addSingletonGetter(Foo);", "var /** !Foo */ x = Foo.instance_;", "var /** !Bar */ b = Foo.instance_;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @const */", "var ns = {};", "/** @constructor */", "ns.Bar = function() {};", "/** @constructor */", "ns.Foo = function() {};", "goog.addSingletonGetter(ns.Foo);", "var /** !ns.Bar */ b = ns.Foo.getInstance();", "b = ns.Foo.instance_;"), NewTypeInference.MISTYPED_ASSIGN_RHS, NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testNoSpuriousWarningsInES6externs() { typeCheckCustomExterns(LINE_JOINER.join( DEFAULT_EXTERNS, "/**", " * @interface", " * @template VALUE", " */", "function I() {}", "/** @return {VALUE} */", "I.prototype['some-es6-symbol'] = function() {};"), ""); typeCheckCustomExterns(LINE_JOINER.join( DEFAULT_EXTERNS, "/**", " * @return {T}", " * @template T := number =:", " */", "function usesTTL() {}"), ""); typeCheckCustomExterns(LINE_JOINER.join( DEFAULT_EXTERNS, "/**", " * @param {VALUE} x", " * @return {RESULT}", " * @template VALUE", " * @template RESULT := number =:", " */", "function usesTTL(x) {}"), ""); } public void testCreatingPropsOnLooseOrUnknownObjects() { typeCheck(LINE_JOINER.join( "var goog = {};", // @const missing on purpose "goog.inherits = function(childCtor, parentCtor) {};", "/**", " * @constructor", " * @extends {goog.Plugin}", " */", "goog.Emoticons = function() { goog.Plugin.call(this); };", "goog.inherits(goog.Emoticons, goog.Plugin);", "goog.Emoticons.COMMAND = '+emoticon';", "goog.Emoticons.prototype.getTrogClassId = goog.Emoticons.COMMAND;"), GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME, JSTypeCreatorFromJSDoc.EXTENDS_NON_OBJECT, NewTypeInference.INEXISTENT_PROPERTY, NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "Object.prototype.asdf;", "function f() {", " this.asdf.qwer = 123;", "}"), NewTypeInference.GLOBAL_THIS); typeCheck(LINE_JOINER.join( "Object.prototype.asdf;", "function f() {", " this.asdf.qwer = 123;", " var /** string */ s = this.asdf.qwer;", "}"), NewTypeInference.GLOBAL_THIS, NewTypeInference.GLOBAL_THIS); } public void testTypeofIsPropertyExistenceCheck() { typeCheck(LINE_JOINER.join( "function f(/** { prop: number } */ x) {", " if (typeof x.newprop === 'string') {", " return x.newprop - 1;", " }", "}"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testWarnAboutBadNewType() { typeCheck(LINE_JOINER.join( "/** @type {function(new:number)} */", "function f() {}"), JSTypeCreatorFromJSDoc.NEW_EXPECTS_OBJECT_OR_TYPEVAR); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {function(new:T)} x", " */", "function f(x) {}")); } public void testThisVoid() { typeCheck(LINE_JOINER.join( "/**", " * @param {function(this:void)} x", " */", "function f(x) {}", "/** @constructor */", "function Foo() {}", "Foo.prototype.method = function() {};", "f(Foo.prototype.method);", "function g() {}", "f(g);"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testThisOrNewWithUnions() { typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {function(new:(T|number))} x", " */", "function f(x) {}"), JSTypeCreatorFromJSDoc.NEW_EXPECTS_OBJECT_OR_TYPEVAR); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @this {T|Object}", " */", "function f() {}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " this.p = 123;", "}", "/** @interface */", "function Bar() {}", "Bar.prototype.p;", "/** @this {Foo|Bar} */", "function f() {", " return this.p - 1;", "}"), NewTypeInference.NULLABLE_DEREFERENCE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {number} */", " this.p = 123;", "}", "/** @interface */", "function Bar() {}", "/** @type {number} */", "Bar.prototype.p;", "/** @this {!Foo|!Bar} */", "function f() {", " var /** string */ n = this.p;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testThisOverridesPrototype() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {number} */", " this.prop = 123;", "}", "/** @type {function(this:{prop:number})} */", "Foo.prototype.method = function() {};", "Foo.prototype.method.call({prop:234});")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {number} */", " this.prop = 123;", "}", "/** @this {{prop:number}} */", "Foo.prototype.method = function() {};", "Foo.prototype.method.call({prop:234});")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Obj() {}", "/** @this {?} */", "Obj.prototype.toString = function() { return ''; };", "Obj.prototype.toString.call(123);")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Obj() {}", "/** @this {?} */", "Obj.prototype.toString1 = function() { return ''; };", "Obj.prototype.toString1.call(123);")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Arr() {}", "/** @this {{length: number}|string} // same as {length: number} */", "Arr.prototype.join = function() {};", "Arr.prototype.join.call('asdf');")); typeCheck(LINE_JOINER.join( "/** @this {!Array|{length:number}} */", "Array.prototype.method = function() {};", "function f() {", " Array.prototype.method.call(arguments);", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {}", "/**", " * @template T", " * @this {T}", " * @param {T} x", " */", "Foo.prototype.f = function(x) {};", "(new Foo).f(new Foo);")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @this {{length:number}|Array<T>}", " */", "Array.prototype.g = function(x) {};", "Array.prototype.g.call({}, 2);"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testCreatingSeveralQmarkFunInstances() { typeCheck(LINE_JOINER.join( "/** @type {!Function} */", "function qmarkFunDeclared() {}", "/** @type {function(new:Object)} */", "var x = qmarkFunDeclared;")); } public void testNotAConstructor() { typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "(new Foo);"), NewTypeInference.NOT_A_CONSTRUCTOR); } public void testDontSpecializeKnownFunctions() { typeCheck(LINE_JOINER.join( "function g(x) { return x; }", "/** @type {function(?):number} */", "var z = g;", "/** @type {function(?):string} */", "var w = g;")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "ns.g = function(x) { return x; }", "/** @type {function(?):number} */", "var z = ns.g;", "/** @type {function(?):string} */", "var w = ns.g;")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @return {void} */", "ns.nullFunction = function() {};", "/** @constructor */", "function Foo() {}", "Foo.prototype.m = ns.nullFunction;", "/** @constructor */", "function Bar() {}", "Bar.prototype.m = ns.nullFunction;")); } public void testDontSpecializeInToDict() { typeCheck(LINE_JOINER.join( "var obj = {};", "function f(x) {", " var z = x || obj;", " if (('asdf' in z) && z.prop) {}", "}")); } public void testQmarkFunctionAsNamespace() { typeCheck(LINE_JOINER.join( "/** @type {!Function} */", "var a = function() {};", "a.b = {};")); typeCheck(LINE_JOINER.join( "/** @type {(function(number)|function(string))} */", "var a = function() {};", "a.b = {};"), JSTypeCreatorFromJSDoc.UNION_IS_UNINHABITABLE); } public void testWindowAsNamespace() { typeCheckCustomExterns(LINE_JOINER.join( DEFAULT_EXTERNS, "/** @type {string} */", "window.prop;"), "123 - window.prop;", NewTypeInference.INVALID_OPERAND_TYPE); typeCheckCustomExterns(LINE_JOINER.join( DEFAULT_EXTERNS, "var window;", "/** @type {string} */", "window.prop;"), "123 - window.prop;", NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f() {", " /** @type {string} */", " window.prop = 'asdf';", "}", "function g() {", " window.prop - 123;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "/** @constructor */", "window.Foo = function() {};", "/** @constructor */", "window.Bar = function() {};"), LINE_JOINER.join( "/** @type {window.Foo} */", "var x = new window.Bar;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @type {string} */", "var x = window.closed;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "/** @type {number} */", "window.n;"), LINE_JOINER.join( "/** @type {string} */", "var x;", "x = window.n;", "x = window.closed;"), NewTypeInference.MISTYPED_ASSIGN_RHS, NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "/** @type {number} */", "window.mynum;"), LINE_JOINER.join( "function f(/** !Window */ w) {", " var /** string */ s = w.mynum;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "/** @type {number} */", "var mynum;"), LINE_JOINER.join( "function f(/** !Window */ w) {", " var /** string */ s = w.mynum;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); // When Window is shadowed, don't copy properties to the wrong class typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "/** @type {number} */", "window.mynum;"), LINE_JOINER.join( "function f() {", " /** @constructor */", " function Window() {}", " var x = (new Window).mynum;", "}"), NewTypeInference.INEXISTENT_PROPERTY); // When the externs add properties to window before defining "var window;", // we still infer that window has nominal type Window, not Object. typeCheckCustomExterns( LINE_JOINER.join( "/** @const */ var ns = {};", "window.ns = ns;", DEFAULT_EXTERNS), LINE_JOINER.join( "var /** !Window */ n = window;", // ns is present on window "var x = window.ns;")); typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "/** @constructor */", "function Foo() {}", "/** @type {number} */", "Foo.prototype.prop;"), LINE_JOINER.join( "function f(/** !window.Foo */ x) {", " var /** string */ s = x.prop;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testInstantiateToTheSuperType() { typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E = { A: 1 };", "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "f(123, E.A);")); typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E = { A: 1 };", "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "f(E.A, 123);")); typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E = { A: 1 };", "/**", " * @template T", " * @param {T} x", " * @param {T} y", " * @return {T}", " */", "function f(x, y) {", " return x;", "}", "/** @type {E} */", "var foo = f(123, E.A);"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testUseThisForTypeInstantiation() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {}", "/**", " * @template T", " * @this {T}", " * @param {T} x", " */", "Foo.prototype.f = function(x) {};", "(new Foo).f(new Bar);"), NewTypeInference.NOT_UNIQUE_INSTANTIATION); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @this {T|number}", " */", "function f() {};", "/** @return {*} */", "function g() { return null; }", "f.bind(g());")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {}", "/**", " * @template T", " * @this {T}", " * @param {T} x", " */", "function f(x) {}", "f.bind(new Foo, new Bar);"), NewTypeInference.NOT_UNIQUE_INSTANTIATION); // We miss the incompatibility between MyArray<number> and MyArray<string>. // We don't catch it because our heuristic for using the receiver type to // calculate the instantiation is not enough here. typeCheck(LINE_JOINER.join( "/**", " * @interface", " * @template T", " */", "function MyArrayLike() {}", "/** @type {number} */", "MyArrayLike.length;", "/**", " * @constructor", " * @implements {MyArrayLike<T>}", " * @param {T} x", " * @template T", " */", "function MyArray(x) {}", "MyArray.prototype.length = 123;", "/**", " * @this {!MyArrayLike<T>}", " * @param {!MyArrayLike<T>} x", " * @template T", " */", "MyArray.prototype.m = function(x) {};", "(new MyArray(123)).m(new MyArray('asdf'));")); } public void testDontCrashWhenShadowingANamespace() { typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "function f() {", " /** @const */", " var ns = ns || {};", " /** @constructor */", " ns.Foo = function() {};", "}")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "function f() {", " function ns() {};", " /** @constructor */", " ns.Foo = function() {};", "}")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "function f() {", " var ns = {};", " /** @constructor */", " ns.Foo = function() {};", "}")); typeCheck(LINE_JOINER.join( "function ns() {};", "/** @type {number} */", "ns.prop = 123;", "function f() {", " var ns = {};", " /** @constructor */", " ns.Foo = function() {};", "}")); } public void testConstInPrototypeMethods() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prototype.m = function() {", " /** @const */", " this.prop = 123;", "};", "(new Foo).prop = 234;"), NewTypeInference.CONST_PROPERTY_REASSIGNED); // We don't catch the reassignment because both assignments happen at // the same program point and we can't detect that. typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prototype.m = function(x) {", " /** @const {number} */", " this.prop = x;", "};", "var x = new Foo;", "x.m(1); x.m(2);")); } public void testUnificationWithOptionalProperties() { typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {{foo: (T|undefined)}} x", " */", "function g(x) {}", "g({bar:1});")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {{foo: (T)}} x", " */", "function g(x) {}", "g({bar:1});"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testFunctionUnificationWithSubtyping() { typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {function(number,T)} x", " */", "function g(x) {}", "function h(/** number|string */ x, /** number */ y) {}", "g(h);")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {function(T, ...number)} x", " */", "function g(x) {}", "/** @type {function(string, ...(number|string))} */", "function h(x, var_args) {}", "g(h);")); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {function(T):(number|string)} x", " */", "function g(x) {}", "/** @type {function(string):string} */", "function h(x) { return x; }", "g(h);")); // This could unify without warnings; we'd have to implement // a unifyWithSupertype function. // Overkill for now. typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Parent() {}", "/**", " * @constructor", " * @template T", " * @extends {Parent<T>}", " */", "function Child() {}", "/**", " * @template T", " * @param {function(!Child<T>)} x", " */", "function g(x) {}", "/** @type {function(!Parent<number>)} */", "function h(x) {}", "g(h);"), NewTypeInference.FAILED_TO_UNIFY); } public void testNamespaceDefinitionInExternsWithoutConst() { typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "var ns = {};"), LINE_JOINER.join( "/** @constructor */ ns.Foo = function() {};", "var /** !ns.Foo */ x;")); typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "var ns = {};", "ns.subns = {};"), LINE_JOINER.join( "/** @constructor */ ns.subns.Foo = function() {};", "var /** !ns.subns.Foo */ x = 123;"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testAllowComparisonBetweenEnumAndCorrespondingType() { typeCheck(LINE_JOINER.join( "/** @enum {number} */", "var E = { A:1, B:2 };", "123 < E.A;")); typeCheck(LINE_JOINER.join( "/** @enum {string} */", "var E = { A:'a', B:'b' };", "'c' < E.A;")); } public void testDeclaredFunctionOnNamespace() { typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @return {number} */", "ns.retNum = function() { return 123; };", "function f(x) {", " var x0 = ns.retNum();", " var x1 = ns.retNum();", " if (x0 <= x1) {", " x1 - 123;", " }", "}")); } public void testNoSpuriousWarningBecauseOfTopScalarInComparison() { typeCheck(LINE_JOINER.join( "function retNum() { return 123; };", "function f(x) {", " var x0 = retNum();", " var x1 = retNum();", " if (x0 <= x1) {", " x1 - 123;", " }", "}")); } public void testDontCrashOnMistypedWindow() { // deliberately not adding the default externs here, to avoid the definition // of window. typeCheckCustomExterns(LINE_JOINER.join( "/** @constructor */ function Object() {}", "/**", " * @constructor", " * @param {...*} var_args", " */", "function Function(var_args) {}", "/** @type {?} */", "var window;", "/** @constructor */", "window.Foo = function() {};"), ""); } public void testSubtypingBetweenScalarsAndLooseTypes() { typeCheck(LINE_JOINER.join( "function f(x) {", " var y = x.match;", " y(/asdf/);", "}", "f('asdf');")); typeCheck(LINE_JOINER.join( "Object.prototype.prop;", "function f(x, y) {", " if (y) {", " var /** string */ s = x;", " } else {", " var z = x.prop - 123;", " }", "}", "f('asdf', true);")); typeCheck(LINE_JOINER.join( "function f(x, y) {", " var z = x.match;", " y(x);", " return y;", "}", "function g(/** string */ s) {}", "f('asdf', g);")); typeCheck(LINE_JOINER.join( "function f(x) { x - 5; }", "function g(x) { x.match(/asdf/); return x.match; }", "var /** function(number) */ tmp = g({match: f});"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testInstantiatingWithLooseTypes() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {number} */ this.prop = 123;", "}", "function f(x) {", " x.prop = 234;", " return x;", "}", "/**", " * @template T", " * @param {T} x", " * @param {T} y", " * @return {T}", " */", "function g(x, y) { return x; }", "var /** !Foo */ obj = g(new Foo, f(new Foo));")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {number} */ this.prop = 123;", "}", "/** @constructor */", "function Bar() {", " /** @type {number} */ this.prop = 456;", "}", "function f(x) {", " x.prop = 234;", " return x;", "}", "/**", " * @template T", " * @param {T} x", " * @param {T} y", " * @return {T}", " */", "function g(x, y) { return x; }", "var /** !Bar */ obj = g(new Foo, f(new Foo));"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "/**", " * @param {!Array<!Function|string>|!Function} f1", " * @param {!Array<!Function|string>|!Function} f2", " */", "function g(f1, f2) {", " f(f1, f2);", "}")); typeCheck(LINE_JOINER.join( "Object.prototype.prop;", "/** @constructor @struct */ function Foo() {}", "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "function g(x) {", " var z;", " if (1 < 2) {", " var /** number */ n = x.prop - 1;", " z = x;", " } else {", " z = new Foo;", " }", " f(z, z);", "}")); } public void testDontCrashOnBottomRettypeFromLooseFun() { typeCheck(LINE_JOINER.join( "Object.prototype.getParams;", "function f(obj) {", " var params = obj.getParams();", " for (var p in params) {}", "}")); } public void testConstructorInitializedWithCall() { typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "var TestCase;"), LINE_JOINER.join( "/** @constructor */", "var Foo = TestCase('asdf');", "Foo.prototype.method = function() {", " /** @type {number} */", " this.prop = 123;", "}", "var /** string */ s = (new Foo).prop;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "var TestCase;"), LINE_JOINER.join( "/** @constructor */", "var Foo = TestCase('asdf');", "function f() { var /** !Foo */ obj = 123; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "var TestCase;"), LINE_JOINER.join( "/** @const */ var ns = {}", "/** @constructor */", "ns.Foo = TestCase('asdf');", "ns.Foo.prototype.method = function() {", " /** @type {number} */", " this.prop = 123;", "}", "var /** string */ s = (new ns.Foo).prop;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "var TestCase;"), LINE_JOINER.join( "/** @const */ var ns = {}", "/** @constructor */", "ns.Foo = TestCase('asdf');", "function f() { var /** !ns.Foo */ obj = 123; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "Foo.Bar = Foo.bind(undefined, function() {});"), NewTypeInference.CANNOT_BIND_CTOR); } public void testLocalWithCallableObjectType() { typeCheck(LINE_JOINER.join( "Object.prototype.prop;", "function f(z) {", " var x = z;", " if (x) {", " var y = x.prop;", " x();", " }", "};")); } public void testSpecializeUnknownToLooseObject() { typeCheck(LINE_JOINER.join( "Object.prototype.prop1;", "Object.prototype.prop2;", "function f(/** ? */ x) {", " var y = x.prop1;", " var /** {prop2:number} */ z = x.prop2;", "}")); } public void testGenericsWithUnknownMapNoCrash() { typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template CLASS", " */", "function EventHandler() {}", "/**", " * @param {function(this:CLASS, FUN)} fn", " * @template FUN", " */", "EventHandler.prototype.listen = function(fn) {};", "/**", " * @param {function(FUN)} fn", " * @template FUN", " */", "EventHandler.prototype.unlisten = function(fn) {};", "/**", " * @template T", " * @this {T}", " * @return {!EventHandler<T>}", " */", "function getHandler() {", " return new EventHandler;", "};", "var z = {getHandler: getHandler};", "var handler = z.getHandler();", "var method = (1 < 2) ? handler.listen : handler.unlisten;")); } public void testInterfacesInheritFromObject() { typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "function /** boolean */ f(/** !Foo */ x) {", " return x.hasOwnProperty('asdf');", "}")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @interface */", "function Foo() {}", "Foo.prototype.toString = goog.abstractMethod;")); } public void testTrickySpecializationOfNamespaceProperties() { typeCheck(LINE_JOINER.join( "function foo() {}", "/** @type {Array<Object>} */ foo.arr;", "function f() {", " if (foo.arr.length) {}", "}"), NewTypeInference.NULLABLE_DEREFERENCE); typeCheck(LINE_JOINER.join( "function foo() {}", "/** @type {Array<Object>} */ foo.arr;", "function f() {", " if (foo.arr.length) {", " var z = foo.arr.shift();", " }", "}"), NewTypeInference.NULLABLE_DEREFERENCE); } public void testMethodTypeParameterDoesntShadowClassTypeParameter() { typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template CLASST", " * @param {CLASST} x", " */", "function Foo(x) {}", "/**", " * @template FUNT", " * @param {CLASST} x", " * @param {FUNT} y", " * @return {CLASST}", " */", "Foo.prototype.method = function(x, y) { return x; };", "/**", " * @template FUNT", " * @param {FUNT} x", " * @param {!Foo<FUNT>} afoo", " */", "function f(x, afoo) {", " var /** string */ s = afoo.method(x, 123);", "}", "f(123, new Foo(123));"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testDontCrashWhenExtendingFunction() { // We still don't handle this completely, since we give a non-callable // warning even though Spy extends Function. But the unit test ensures that // we at least don't crash. typeCheck(LINE_JOINER.join( "/** @const */ var jasmine = {};", "/**", " * @constructor", " * @extends {Function}", " */", "jasmine.Spy = function() {};", "var x = (new jasmine.Spy).length;", "var /** null */ n = (new jasmine.Spy)();"), NewTypeInference.NOT_CALLABLE); } public void testDontCrashOnInheritedMethodsWithIncompatibleReturns() { typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "/** @return {number} */", "Foo.prototype.m = function() {};", "/** @interface */", "function Bar() {}", "/** @return {string} */", "Bar.prototype.m = function() {};", "/** @constructor @implements {Foo} @implements {Bar} */", "function Baz() {}", "Baz.prototype.m = function() { return 123; };"), GlobalTypeInfo.SUPER_INTERFACES_HAVE_INCOMPATIBLE_PROPERTIES); } public void testStructuralInterfaces() { typeCheck(LINE_JOINER.join( "/** @record */", "function I1() {}", "/** @record */", "function I2() {}", "function f(/** !I1 */ x, /** !I2 */ y) {", " x = y;", "}")); typeCheck(LINE_JOINER.join( "/** @record */", "function Foo() {}", "/** @type {number} */", "Foo.prototype.p1;", "var /** !Foo */ x = { p1: 123, p2: 'asdf' };")); typeCheck(LINE_JOINER.join( "/** @record */", "function Foo() {}", "/** @type {number} */", "Foo.prototype.p1;", "var /** !Foo */ x = { p1: true };"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @record */", "function Foo() {}", "/** @type {number} */", "Foo.prototype.p1;", "/** @record */", "function Bar() {}", "/** @type {number} */", "Bar.prototype.p1;", "/** @type {number} */", "Bar.prototype.p2;", "function f(/** !Bar */ x) {", " var /** !Foo */ y = x;", "}")); typeCheck(LINE_JOINER.join( "/** @record */", "function I1() {}", "/** @record */", "function I2() {}", "/** @type {number} */", "I2.prototype.prop;", "function f(/** !I1 */ x, /** !I2 */ y) {", " x = y;", "}")); typeCheck(LINE_JOINER.join( "/** @record */", "function Foo() {}", "/** @type {number|undefined} */", "Foo.prototype.p1;", "var /** !Foo */ x = {};")); // TODO(dimvar): spurious warning; must recognize optional properties // of type ? on interfaces. typeCheck(LINE_JOINER.join( "/** @record */", "function Foo() {}", "/** @type {?|undefined} */", "Foo.prototype.p1;", "var /** !Foo */ x = {};"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @record */", "function Foo() {}", "/** @type {number} */", "Foo.prototype.p1;", "/** @constructor */", "function Bar() {}", "/** @type {number} */", "Bar.prototype.p1;", "var /** !Foo */ x = new Bar;")); typeCheck(LINE_JOINER.join( "/** @record */", "function Foo() {}", "/** @type {number|string} */", "Foo.prototype.p1;", "/** @constructor */", "function Bar() {}", "/** @type {number} */", "Bar.prototype.p1;", "var /** !Foo */ x = new Bar;")); typeCheck(LINE_JOINER.join( "/** @record */", "function Foo() {}", "/** @type {number} */", "Foo.prototype.p1;", "/** @constructor */", "function Bar() {}", "/** @type {number|undefined} */", "Bar.prototype.p1;", "var /** !Foo */ x = new Bar;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @record */", "function I3() {}", "/** @type {number} */", "I3.prototype.length;", "/** ", " * @record", " * @extends I3", " */", "function I4() {}", "/** @type {boolean} */", "I4.prototype.prop;", "/** @constructor */", "function C4() {}", "/** @type {number} */", "C4.prototype.length;", "/** @type {boolean} */", "C4.prototype.prop;", "var /** !I4 */ x = new C4;")); typeCheck(LINE_JOINER.join( "/** @record */ function I() {}", "/** @type {!Function} */ I.prototype.removeEventListener;", "/** @type {!Function} */ I.prototype.addEventListener;", "/** @constructor */ function C() {}", "/** @type {!Function} */ C.prototype.addEventListener;", "/** @param {!C|!I} x */", "function f(x) { x.addEventListener(); }", "f(new C());")); typeCheck(LINE_JOINER.join( "/** @record */ function WithProp() {}", "/** @type {number} */", "WithProp.prototype.prop;", "function f() {}", "/** @type {number} */", "f.prop = 123;", "var /** !WithProp */ x = f;")); typeCheck(LINE_JOINER.join( "/** @record */ function WithProp() {}", "/** @type {number} */", "WithProp.prototype.prop;", "function f() {}", "var /** !WithProp */ x = f;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @record */ function WithProp() {}", "/** @type {number} */", "WithProp.prototype.prop;", "function f() {}", "/** @type {string} */", "f.prop = 'asdf';", "var /** !WithProp */ x = f;"), NewTypeInference.MISTYPED_ASSIGN_RHS); // Don't crash during GlobalTypeInfo when normalizing unions that contain // structural types typeCheck(LINE_JOINER.join( "/** @record */", "function Foo() {}", "Foo.prototype.prop;", "/** @record */", "function Bar() {}", "Bar.prototype.prop2;", "/** @param {!Array<!Foo>|!Array<!Bar>} x */", "function f(x) {}")); } public void testGenericStructuralInterfaces() { typeCheck(LINE_JOINER.join( "/** @record */", "function WithPropT() {}", "/** @type {number} */", "WithPropT.prototype.prop;", "function f(/** !WithPropT */ x){}", "/** @constructor */", "function Foo() {}", "/** @type {number} */", "Foo.prototype.prop;", "f(new Foo);")); typeCheck(LINE_JOINER.join( "/** @record @template T */", "function WithPropT() {}", "/** @type {T} */", "WithPropT.prototype.prop;", "function f(/** !WithPropT<number> */ x){}", "/** @constructor */", "function Foo() {}", "/** @type {number} */", "Foo.prototype.prop;", "f(new Foo);")); typeCheck(LINE_JOINER.join( "/** @record @template T */", "function WithPropT() {}", "/** @type {T} */", "WithPropT.prototype.prop;", "function f(/** !WithPropT<number> */ x){}", "/** @constructor @template U */", "function Foo() {}", "/** @type {number} */", "Foo.prototype.prop;", "f(new Foo);")); typeCheck(LINE_JOINER.join( "/** @record @template T */", "function WithPropT() {}", "/** @type {T} */", "WithPropT.prototype.prop;", "function f(/** !WithPropT<number> */ x){}", "/** @constructor @template U */", "function Foo() {}", "/** @type {U} */", "Foo.prototype.prop;", "f(new Foo);")); typeCheck(LINE_JOINER.join( "/** @record @template T */", "function WithPropT() {}", "/** @type {T} */", "WithPropT.prototype.prop;", "function f(/** !WithPropT<number> */ x){}", "/** @constructor */", "function Foo() {}", "/** @type {string} */", "Foo.prototype.prop;", "f(new Foo);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @record @template T */", "function WithPropT() {}", "/** @type {T} */", "WithPropT.prototype.prop;", "function f(/** !WithPropT<number> */ x){}", "/**", " * @constructor", " * @template U", " * @param {U} x", " */", "function Foo(x) {}", "/** @type {U} */", "Foo.prototype.prop;", "f(new Foo('asdf'));"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @record @template T */", "function WithProp() {}", "/** @type {T} */", "WithProp.prototype.prop;", "/** @constructor */", "function Foo() {", " /** @type {number} */ this.prop = 4;", "}", "/**", " * @template U", " * @param {!WithProp<U>} x", " */", "function f(x){}", "f(new Foo);")); typeCheck(LINE_JOINER.join( "/** @record @template T */", "function WithProp() {}", "/** @type {T} */", "WithProp.prototype.prop;", "/** @constructor */", "function Foo() {", " /** @type {number} */ this.prop = 4;", "}", "/**", " * @template U", " * @param {!WithProp<U>} x", " * @param {U} y", " */", "function f(x, y){}", "f(new Foo, 'str');"), NewTypeInference.NOT_UNIQUE_INSTANTIATION); typeCheck(LINE_JOINER.join( "/** @record @template T */", "function WithProp() {}", "/** @type {T} */", "WithProp.prototype.prop;", "/** @constructor */", "function Foo() {", " /** @type {number} */ this.prop = 4;", "}", "/** @constructor */", "function Bar() {", " /** @type {string} */ this.prop = 'str';", "}", "/**", " * @template U", " * @param {!WithProp<U>} x", " * @param {!WithProp<U>} y", " */", "function f(x, y){}", "f(new Foo, new Bar);"), NewTypeInference.NOT_UNIQUE_INSTANTIATION); } public void testRecursiveStructuralInterfaces() { typeCheck(LINE_JOINER.join( "/** @record */", "function Rec1() {}", "/** @type {!Rec1} */", "Rec1.prototype.p1;", "/** @record */", "function Rec2() {}", "/** @type {!Rec2} */", "Rec2.prototype.p1;", "function f(/** !Rec1 */ x, /** !Rec2 */ y) {", " x = y;", "}")); typeCheck(LINE_JOINER.join( "/** @record */", "function Rec1() {}", "/** @type {!Rec2} */", "Rec1.prototype.p1;", "/** @record */", "function Rec2() {}", "/** @type {!Rec1} */", "Rec2.prototype.p1;", "function f(/** !Rec1 */ x, /** !Rec2 */ y) {", " x = y;", "}")); typeCheck(LINE_JOINER.join( "/** @record */", "function Foo() {}", "/** @type {function(?Foo)} */", "Foo.prototype.p1;", "/** @record */", "function Bar() {}", "/** @type {function(?Bar)} */", "Bar.prototype.p1;", "function f(/** !Bar */ x) {", " var /** !Foo */ y = x;", "}")); typeCheck(LINE_JOINER.join( "/** @record */", "function Foo() {}", "/** @type {function():?Foo} */", "Foo.prototype.p1;", "/** @record */", "function Bar() {}", "/** @type {function():?Bar} */", "Bar.prototype.p1;", "function f(/** !Bar */ x) {", " var /** !Foo */ y = x;", "}")); typeCheck(LINE_JOINER.join( "/** @record */", "function Rec() {}", "/** @type {number} */", "Rec.prototype.num;", "/** @type {!Rec} */", "Rec.prototype.recur;", "function f(/** !Rec */ x) {}", "var lit = { num: 123 };", "lit.recur = lit;", "f(lit);")); // Rec1 and Rec2 are not subtypes of each other. When checking if Baz is a // subtype of {prop1:!Rec1}|{prop2:!Rec1}, make sure to not falsely say // that Rec2 <: Rec1 because of wrong caching. typeCheck(LINE_JOINER.join( "/** @record */", "function Rec1() {}", "/** @type {!Rec1} */", "Rec1.prototype.p1;", "/** @type {number} */", "Rec1.prototype.p2;", "/** @record */", "function Rec2() {}", "/** @type {!Rec2} */", "Rec2.prototype.p1;", "/** @type {string} */", "Rec2.prototype.p2;", "/** @record */", "function Baz() {}", "/** @type {!Rec2} */", "Baz.prototype.prop1;", "/** @type {!Rec2} */", "Baz.prototype.prop2;", "function f(/** {prop1:!Rec1}|{prop2:!Rec1} */ x, /** !Baz */ y) {", " x = y;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @record */", "function Foo() {};", "/** @return {!MutableFoo} */", "Foo.prototype.toMutable;", "/** @record */", "function MutableFoo() {};", "/** @param {!Foo} from */", "MutableFoo.prototype.copyFrom = function(from) {};", "/** @record */ function Bar() {};", "/** @return {!MutableBar} */", "Bar.prototype.toMutable;", "/** @record */", "function MutableBar() {};", "/** @param {!Bar} from */", "MutableBar.prototype.copyFrom = function(from) {};", "/** @constructor @implements {MutableBar} */", "function MutableBarImpl() {};", "MutableBarImpl.prototype.copyFrom = function(from) {};", "/** @constructor @implements {MutableFoo} */", "function MutableFooImpl() {};", "MutableFooImpl.prototype.copyFrom = function(from) {};")); typeCheck(LINE_JOINER.join( "/**", " * @record", " * @template T", " */", "function GenericRec() {}", "/** @type {?GenericRec<T>} */", "GenericRec.prototype.recur;", "/** @record */", "function Rec() {}", "/** @type {?Rec} */", "Rec.prototype.recur;", "/**", " * @template T", " * @param {!GenericRec<T>} x", " */", "function f(x) {}", "/** @param {!Rec} x */", "function g(x) {", " f(x);", "}")); typeCheck(LINE_JOINER.join( "/**", " * @record", " * @template T", " */", "function GenericRec() {}", "/** @type {?GenericRec<T>} */", "GenericRec.prototype.recur;", "/**", " * @template T", " * @param {!GenericRec<T>} x", " */", "function f(x) {}", "/** @param {{recur:?GenericRec<number>}} x */", "function g(x) {", " f(x);", "}")); } public void testIObjectBracketAccesses() { typeCheck(LINE_JOINER.join( "function f(/** !IObject<number,string> */ x) {", " return x['asdf'];", "}"), NewTypeInference.INVALID_INDEX_TYPE); typeCheck(LINE_JOINER.join( "function f(/** !IObject<number, number> */ x) {", " x['asdf'] = 123;", "}"), NewTypeInference.INVALID_INDEX_TYPE); typeCheck(LINE_JOINER.join( "function f(/** !IObject<number, string> */ x, /** number */ i) {", " x[i] - 123;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f(/** !Foo|!IObject<number,number> */ x, /** string */ s) {", " x[s];", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f(/** !Foo|!IObject<number,number> */ x, /** string */ s) {", " s = x[0];", "}")); typeCheck(LINE_JOINER.join( "/** @type {!Array<number>|number} */", "var x = [1,2,3];", "x[0] = 'asdf';"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @type {!Object} */", "var x = [1,2,3];", "x[0] = 'asdf';")); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @implements {IObject<number,number>}", " */", "function Foo() {}", "/**", " * @constructor", " * @implements {IObject<string,number>}", " */", "function Bar() {}", "function f(/** !Foo|!Bar */ x) {", " x[123];", " x['asdf'];", "}"), NewTypeInference.BOTTOM_INDEX_TYPE, NewTypeInference.BOTTOM_INDEX_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @implements {IObject<number,(number|string)>}", " */", "function Foo() {}", "/**", " * @constructor", " * @implements {IObject<number,(number|boolean)>}", " */", "function Bar() {}", "function f(/** !Foo|!Bar */ x) {", " var /** string */ s = x[123];", " var /** boolean */ b = x[123];", " var /** number */ n = x[123];", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS, NewTypeInference.MISTYPED_ASSIGN_RHS, NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** !IObject<*,string> */ x) {", " return x[123];", "}")); typeCheck(LINE_JOINER.join( "function f(/** !IObject<string,number> */ x, s) {", " x[s];", " var /** number */ n = s;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @param {!Array<number>|!IObject<string, number>} x */", "function f(x, y) {", " return x[0] + x['a' + 'b'];", "}"), NewTypeInference.BOTTOM_INDEX_TYPE, NewTypeInference.BOTTOM_INDEX_TYPE); } // A dot access on IObject<K, V> is not typed as V. // It is either declared separately with its own type (eg, when Foo implements IObject and // has some extra properties), or it is a type error. public void testIObjectDotAccesses() { typeCheck(LINE_JOINER.join( "function f(/** !IObject<?, ?> */ x) {", " return x.hasOwnProperty('test');", "}")); typeCheck(LINE_JOINER.join( "function g(x) {", " x.foobar = 123;", "}", "function f(/** !IObject<?, ?> */ x) {", " return x.foobar;", "}"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " this.foobar = 123;", "}", "function f(/** !IObject<?, ?> */ x) {", " return x.foobar;", "}"), NewTypeInference.INEXISTENT_PROPERTY); } public void testIObjectSubtyping() { typeCheck(LINE_JOINER.join( "function f(/** !IObject */ x) {}", "f({});")); typeCheck("var /** !IObject<number, string> */ x = {};"); typeCheck(LINE_JOINER.join( "/**", " * @param {!IObject<number, number>} x", " * @param {!IObject<string, number>} y", " */", "function f(x, y) {", " x = y;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @param {!IObject<number, number>} x", " * @param {!IObject<(number|string), number>} y", " */", "function f(x, y) {", " x = y;", "}")); typeCheck(LINE_JOINER.join( "/**", " * @param {!IObject<(number|string), number>} x", " * @param {!IObject<number, number>} y", " */", "function f(x, y) {", " x = y;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @implements {IObject<(string|number), string>}", " * @implements {IArrayLike<string>}", " */", "function Foo() {", " this.length = 123;", "}", "/** @type {!IArrayLike<string>} */", "var x = new Foo;")); typeCheck(LINE_JOINER.join( "/**", " * @param {!IObject<number, (number|string)>} x", " * @param {!IObject<number, number>} y", " */", "function f(x, y) {", " x = y;", "}")); typeCheck(LINE_JOINER.join( "/**", " * @param {!IObject<number, number>} x", " * @param {{ '1': number, '2': number }} y", " */", "function f(x, y) {", " x = y;", "}")); typeCheck(LINE_JOINER.join( "/** @type {!IObject<string, number>} */", "var x = { 'a': 123, 'b': 234 };")); typeCheck(LINE_JOINER.join( "/** @type {!IObject<string, number>} */", "var x = { 'a': 123, 'b': true };"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @param {!IObject<string, number>} x", " * @param {{a: number, b: boolean}} y", " */", "function f(x, y) {", " x = y;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); // We check unquoted properties against IObject, which is not ideal. // See comment in ObjectType#compareRecordTypeToIObject. typeCheck(LINE_JOINER.join( "/** @type {!IObject<string, number>} */", "var x = { a: 123, b: true };"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @type {!IObject<number, number>} */", "var x = { '1': 1, 'b': 2 };"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @type {!IObject<!Function, string>} */", "var x = { 'a': 'asdf' };"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @record", " * @extends {IObject<string, string>}", " */", "function Foo() {}", "/** @type {number} */", "Foo.prototype.prop;", "/** @param {!Foo} x */", "function f(x) {}", "f({ prop: 123, 'a': 'asdf', 'b': 'asdf' });")); typeCheck("var /** !IObject<?, ?> */ x = { 'abs': '', '%': ''};"); typeCheck("var /** !IObject<string, number> */ x = { a: 1, b: 2, c: undefined };"); } public void testIArrayLikeSubtyping() { typeCheck(LINE_JOINER.join( "/**", " * @param {!IArrayLike<number>} x", " * @param {!IObject<?, number>} y", " */", "function f(x, y) {", " y.length = 0;", " x = y;", "}")); typeCheck(LINE_JOINER.join( "/**", " * @param {!IArrayLike<number>} x", " * @param {!IArrayLike<string>} y", " */", "function f(x, y) {", " x = y;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testIObjectExtraProperties() { typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @implements {IObject<string, number>}", " */", "function Foo() {", " /** @type {boolean} */", " this.prop = true;", "}", "var /** number */ n = (new Foo).prop;"), NewTypeInference.MISTYPED_ASSIGN_RHS); // Bracket access on IObjects always uses the indexed type unsoundly. // Same as in OTI. typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @implements {IObject<string, number>}", " */", "function Foo() {", " /** @type {boolean} */", " this.prop = true;", "}", "var /** boolean */ b = (new Foo)['prop'];"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testIObjectInheritance() { typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @implements {IObject<number,number>}", " */", "function Foo() {}", "function f(/** !Foo */ x, /** string */ s) {", " x[s];", "}"), NewTypeInference.INVALID_INDEX_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @interface", " * @extends {IObject<number,number>}", " */", "function Bar() {}", "/**", " * @constructor", " * @implements {Bar}", " */", "function Foo() {}", "function f(/** !Foo */ x, /** string */ s) {", " x[s];", "}"), NewTypeInference.INVALID_INDEX_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "/**", " * @constructor", " * @implements {Foo}", " * @implements {IObject<number, number>}", " */", "function Bar() {}", "(new Bar)['asdf'];"), NewTypeInference.INVALID_INDEX_TYPE); // OTI has a bug here and gives different warnings depending on the order // of the @implements annotations. typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @implements {IObject<string, number>}", " * @implements {IArrayLike<number>}", " */", "function Foo() {", " this.length = 0;", "}", "(new Foo)['asdf'];", "(new Foo)[123];", "(new Foo)[true];"), NewTypeInference.INVALID_INDEX_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @interface", " * @extends {IObject<number, (number|string)>}", " */", "function Foo() {}", "/**", " * @interface", " * @extends {IObject<number, (number|boolean)>}", " */", "function Bar() {}", "/**", " * @constructor", " * @implements {Foo}", " * @implements {Bar}", " */", "function Baz() {}", "var /** string */ s = (new Baz)[123];", "var /** boolean */ b = (new Baz)[123];", "var /** number */ n = (new Baz)[123];"), NewTypeInference.MISTYPED_ASSIGN_RHS, NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @implements {IObject<*,number>}", " */", "function Foo() {}", "function f(/** !Foo */ x, /** string */ s) {", " x[s];", "}")); typeCheck(LINE_JOINER.join( "/** @interface @extends {IObject<string, string>} */", "function Int1() {}", "/** @interface @extends {IObject<string, number>} */", "function Int2() {}", "/**", " * @constructor", " * @implements {Int1}", " * @implements {Int2}", " */", "function Foo() {}"), GlobalTypeInfo.SUPER_INTERFACES_HAVE_INCOMPATIBLE_PROPERTIES); typeCheck(LINE_JOINER.join( "/** @interface @extends {IObject<string, string>} */", "function Int1() {}", "/** @interface @extends {IObject<string, number>} */", "function Int2() {}", "/**", " * @constructor", " * @implements {Int1}", " * @implements {Int2}", " */", "function Foo() {}", // Tests that we don't crash on property accesses of bad IObjects "var /** null */ n = (new Foo)['asdf'+'asdf'];"), GlobalTypeInfo.SUPER_INTERFACES_HAVE_INCOMPATIBLE_PROPERTIES); typeCheck(LINE_JOINER.join( "/** @interface @extends {IObject<function(number), number>} */", "function Int1() {}", "/** @interface @extends {IObject<function(string), number>} */", "function Int2() {}", "/**", " * @constructor", " * @implements {Int1}", " * @implements {Int2}", " */", "function Foo() {}"), GlobalTypeInfo.SUPER_INTERFACES_HAVE_INCOMPATIBLE_PROPERTIES); } public void testDontWarnForMissingReturnOnInfiniteLoop() { typeCheck(LINE_JOINER.join( "/** @return {number} */", "function f(g, i) {", " while (true) {", " if (g(i) == 0) {", " return i;", " }", " i++;", " }", "}")); typeCheck(LINE_JOINER.join( "/** @return {number} */", "function f(g, i) {", " do {", " if (g(i) == 0) {", " return i;", " }", " i++;", " } while (true);", "}")); } public void testMethodsOnClassProperties() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {number} */", " this.prop = 123;", " this.m = function() {", " var /** string */ s = this.prop;", " };", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " this.m = function(/** number */ x) {};", "}", "(new Foo).m(123);", "(new Foo).m('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " this.m = function(/** number */ x) {};", "}", "/** @constructor @extends {Foo} */", "function Bar() {}", "(new Bar).m(123);", "(new Bar).m('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @interface */", "function Bar() {}", "/** @param {number} x */", "Bar.prototype.m = function(x) {};", "/** @constructor @implements {Bar} */", "function Foo() {", " this.m = function(/** number */ x) {};", "}")); typeCheck(LINE_JOINER.join( "/** @interface */", "function Bar() {}", "/** @param {number} x */", "Bar.prototype.m = function(x) {};", "/** @constructor @implements {Bar} */", "function Foo() {", " this.m = function(/** string */ x) {};", "}"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {function(number)} */", " this.m = function(x) {};", "}", "(new Foo).m('asdf');"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {null|function(number)} */", " this.m = function(x) {};", "}", "(new Foo).m = null;")); } public void testNamespacesSubtyping() { typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @type {number} */", "ns.prop = 123;", "function f() {", " var /** {prop:string} */ obj = ns;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testInstantiationWithNamespaces() { typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @const */", "var ns2 = {};", "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "f(ns, ns2);")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @type {number} */", "ns.prop = 123;", "/** @const */", "var ns2 = {};", "/** @type {string} */", "ns2.prop = 'asdf';", "/**", " * @template T", " * @param {T} x", " * @param {T} y", " */", "function f(x, y) {}", "f(ns, ns2);"), NewTypeInference.NOT_UNIQUE_INSTANTIATION); } public void testSpecializeNamespaceProperties() { typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @type {?number} */", "ns.prop = null;", "function f() {", " if (ns.prop !== null) {", " return ns.prop - 5;", " }", "}")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @type {?number} */", "ns.prop = null;", "/** @return {number} */", "function f() {", " if (ns.prop === null) {", " return 123;", " }", " return ns.prop;", "}")); typeCheckCustomExterns( LINE_JOINER.join( DEFAULT_EXTERNS, "/** @type {?number} */", "window.prop;"), LINE_JOINER.join( "function f() {", " if (window.prop !== null) {", " return window.prop - 5;", " }", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {};", "/** @type {?number} */", "Foo.prop = null;", "function f() {", " if (Foo.prop !== null) {", " return Foo.prop - 5;", " }", "}")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @constructor */", "ns.Foo = function() {};", "/** @type {?number} */", "ns.Foo.prop = null;", "function f() {", " if (ns.Foo.prop !== null) {", " return ns.Foo.prop - 5;", " }", "}")); typeCheck(LINE_JOINER.join( "function f() {};", "/** @type {?number} */", "f.prop = null;", "function f() {", " if (f.prop !== null) {", " return f.prop - 5;", " }", "}")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @type {?Object|undefined} */", "ns.mutableProp = null;", "function f() {", " if (ns.mutableProp != null) {", " var /** !Object */ x = ns.mutableProp;", " }", "}")); } public void testInferScalarInsteadOfLooseObject() { typeCheck(LINE_JOINER.join( "function h(x) {", " return x.name.toLowerCase().startsWith('a');", "}", "h({name: 'asdf'});")); typeCheck(LINE_JOINER.join( "function h(x) {", " return x.name.toLowerCase().startsWith('a');", "}", "h({name: {}});"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "function h(x) {", " return x.name.toString();", "}", "h({name: 'asdf'});", "h({name: {}});")); typeCheck(LINE_JOINER.join( "function h(x) {", " return x.name.length;", "}", "h({name: 'asdf'});", "h({name: {}});")); typeCheck(LINE_JOINER.join( "function h(x) {", " return x.num.toExponential();", "}", "h({num: 123});")); typeCheck(LINE_JOINER.join( "function h(x) {", " return x.num.toExponential();", "}", "h({num: {}});"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "Foo.prototype.toLowerCase = function() {};", "Foo.prototype.getProp = function() {};", "var foo = new Foo;", "foo.f = function() {", " if (foo.toLowerCase() > 'asdf') { throw new Error; }", " foo.getProp();", "};"), NewTypeInference.INEXISTENT_PROPERTY, // spurious b/c foo is inferred as string in the inner scope NewTypeInference.CROSS_SCOPE_GOTCHA); } public void testFixCrashWhenUnannotatedPrototypeMethod() { typeCheck(LINE_JOINER.join( "var a = {};", "a.prototype.b = function() {", " this.c = function() {};", "};"), NewTypeInference.INEXISTENT_PROPERTY, NewTypeInference.GLOBAL_THIS); } public void testSimpleInferPrototypeProperties() { typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @const */ ns.prop = Object.prototype.hasOwnProperty;")); typeCheck(LINE_JOINER.join( "/** @const */ var ns = {};", "/** @const */ ns.prop = Foobar.prototype.randomProp;"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prototype.method = function(x, y) { return x + y + 1 };", "/** @const */", "var ns = {};", "/** @const */", "ns.prop = Foo.prototype.method;"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); } public void testReportUknownTypes() { compilerOptions.setWarningLevel(DiagnosticGroups.REPORT_UNKNOWN_TYPES, CheckLevel.WARNING); typeCheck( "var x = globalvar;", NewTypeInference.UNKNOWN_EXPR_TYPE); typeCheck( "function f(/** ? */ x) { return x; }", NewTypeInference.UNKNOWN_EXPR_TYPE); typeCheck( "var x = ({})['asdf'];", NewTypeInference.UNKNOWN_EXPR_TYPE); typeCheck(LINE_JOINER.join( "function f(/** !Object */ x) {", " x['prop' + 'asdf'] = 123;", "}")); typeCheck(LINE_JOINER.join( "function f(/** !Object */ x) {", " x['asdf'] = 123;", "}"), NewTypeInference.UNKNOWN_EXPR_TYPE); typeCheck(LINE_JOINER.join( "/** @struct @constructor */", "var Foo = function() {};", "/**", " * @struct", " * @constructor", " * @extends {Foo}", " */", "var Bar = function() {", " Foo.call(this);", "};")); } public void testPinpointTypeDiffWhenMismatch() { typeCheckMessageContents(LINE_JOINER.join( "function f(/** {a:number, b:string} */ x) {}", "f({a: 123, b: 123});"), NewTypeInference.INVALID_ARGUMENT_TYPE, LINE_JOINER.join( "Invalid type for parameter 1 of function f.", "Expected : {a:number, b:string}", "Found : {a:number, b:number}", "More details:", "Incompatible types for property b.", "Expected : string", "Found : number")); typeCheckMessageContents(LINE_JOINER.join( "function f(/** {a:number, b:(string|undefined)} */ x) {}", "f({a: 123, b: 123});"), NewTypeInference.INVALID_ARGUMENT_TYPE, LINE_JOINER.join( "Invalid type for parameter 1 of function f.", "Expected : {a:number, b:string|undefined=}", "Found : {a:number, b:number}", "More details:", "Incompatible types for property b.", "Expected : string|undefined", "Found : number")); typeCheckMessageContents(LINE_JOINER.join( "function f(/** {a:number, b:string} */ x) {}", "f({a: 123});"), NewTypeInference.INVALID_ARGUMENT_TYPE, LINE_JOINER.join( "Invalid type for parameter 1 of function f.", "Expected : {a:number, b:string}", "Found : {a:number}", "More details:", "The found type is missing property b")); typeCheckMessageContents(LINE_JOINER.join( "function f(/** {a:number, b:string} */ x) {}", "function g(/** {a:number, b:(string|undefined)} */ x) { f(x); }"), NewTypeInference.INVALID_ARGUMENT_TYPE, LINE_JOINER.join( "Invalid type for parameter 1 of function f.", "Expected : {a:number, b:string}", "Found : {a:number, b:string|undefined=}", "More details:", "In found type, property b is optional but should be required.")); typeCheckMessageContents(LINE_JOINER.join( "function f(/** function((number|string)) */ x) {}", "function g(/** number */ x) {}", "f(g);"), NewTypeInference.INVALID_ARGUMENT_TYPE, LINE_JOINER.join( "Invalid type for parameter 1 of function f.", "Expected : function(number|string):?", "Found : function(number):undefined", "More details:", "The expected and found types are functions which have incompatible" + " types for argument 1.", "Expected a supertype of : number|string", "but found : number")); typeCheckMessageContents(LINE_JOINER.join( "function f(/** function():number */ x) {}", "/** @return {string} */ function g() { return 'asdf'; }", "f(g)"), NewTypeInference.INVALID_ARGUMENT_TYPE, LINE_JOINER.join( "Invalid type for parameter 1 of function f.", "Expected : function():number", "Found : function():string", "More details:", "The expected and found types are functions which have incompatible" + " return types.", "Expected a subtype of : number", "but found : string")); typeCheckMessageContents(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @constructor */ function Bar() {}", "function f(/** !Foo */ x) {}", "function g(/** (!Foo|!Bar) */ x) {", " f(x);", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE, LINE_JOINER.join( "Invalid type for parameter 1 of function f.", "Expected : Foo", "Found : Bar|Foo", "More details:", "The found type is a union that includes an unexpected type: Bar")); typeCheckMessageContents(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @constructor @extends {Foo} */ function Bar() {}", "function f(/** !Foo */ x) {}", "function g(/** ?Bar */ x) {", " f(x);", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE, LINE_JOINER.join( "Invalid type for parameter 1 of function f.", "Expected : Foo", "Found : Bar|null", "More details:", "The found type is a union that " + "includes an unexpected type: null")); typeCheckMessageContents(LINE_JOINER.join( "function f(/** number */ x) {}", "function g(/** number|string */ x) {", " f(x);", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE, LINE_JOINER.join( "Invalid type for parameter 1 of function f.", "Expected : number", "Found : number|string", "More details:", "The found type is a union that " + "includes an unexpected type: string")); typeCheckMessageContents( "var /** {a:number} */ x; x = { a: 'asdf' }", NewTypeInference.MISTYPED_ASSIGN_RHS, LINE_JOINER.join( "The right side in the assignment is not a subtype of the left side.", "Expected : {a:number}", "Found : {a:string}", "More details:", "Incompatible types for property a.", "Expected : number", "Found : string")); } public void testSpecializeAfterPropertyTest() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {", " this.prop = 123;", "}", "function f(/** !Foo|!Bar */ x) {", " if (x.prop) {", " var /** !Bar */ n = x;", " }", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {", " this.prop = 123;", "}", "function f(/** {a: (!Foo|!Bar)} */ x) {", " if (x.a.prop) {", " var /** !Bar */ n = x.a;", " }", "}")); // Here, we spuriously specialize to a Bar. Foo instances passed to g can // have prop, but we don't see it. To avoid this unsoundness, one could // restrict the specialization only to @struct instances. // But the main motivation of specializing at property tests is to handle // DOM elements better, which are not structs. typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {", " this.prop = 123;", "}", "function g(x) { x.prop = 234; }", "function f(/** !Foo|!Bar */ x) {", " if (x.prop) {", " var /** !Bar */ n = x;", " }", "}", "var obj = new Foo;", "g(obj);", "f(obj);")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {", " this.prop = 123;", "}", "/** @constructor */", "function Baz() {", " this.prop = 345;", "}", "function f(/** !Foo|!Bar|!Baz */ x) {", " if (x.prop) {", " var /** (!Bar|!Baz) */ n = x;", " }", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {", " /** @type {number} */", " this.prop = 123;", "}", "function f(/** !Foo|!Bar */ x) {", " if (x.prop) {", " var /** string */ s = x.prop;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); // For simplicity, we don't specialize here typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() { this.startsWith = function(s) {}; }", "function f(/** !Foo|!Bar|string */ x) {", " if (x.startsWith) {", " var /** !Bar|string */ y = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testAllowCastingToFromInterface() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @interface */", "function Bar() {}", "function f(/** !Foo */ x) {", " return /** @type {!Bar} */ (x);", "}")); typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "/** @constructor */", "function Bar() {}", "function f(/** !Foo */ x) {", " return /** @type {!Bar} */ (x);", "}")); typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "/** @interface */", "function Bar() {}", "function f(/** !Foo */ x) {", " return /** @type {!Bar} */ (x);", "}")); } public void testInferConstGetelem() { typeCheck(LINE_JOINER.join( "function f(/** number */ i) {", " /** @const {!Array<number>} */", " var cells = [1];", " /** @const */", " var cell = cells[i];", " return function() { var /** null */ n = cell; };", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** number */ i) {", " /** @const {!IObject<number,string>} */", " var cells = ['asdf'];", " /** @const */", " var cell = cells[i];", " return function() { var /** null */ n = cell; };", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** number */ i, /** !IObject<string,string> */ cells) {", " /** @const */", " var cell = cells[i];", " return function() { cell; };", "}"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE, NewTypeInference.INVALID_INDEX_TYPE); typeCheck(LINE_JOINER.join( "function f(/** {prop: number} */ obj) {", " /** @const */", " var x = obj['prop'];", " return function() { var /** null */ n = x; };", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @interface */", "function Bar() {}", "/** @constructor @implements {Bar} */", "function Foo() {", " /** @type {number} */", " this.prop = 123;", "}", "function f(/** !Foo */ obj) {", " /** @const */", " var x = obj['prop'];", " return function() { var /** null */ n = x; };", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @interface */", "function Bar() {}", "/**", " * @constructor", " * @implements {Bar}", " * @implements {IObject<number, string>}", " */", "function Foo() {}", "function f(/** number */ i, /** !Foo */ obj) {", " /** @const */", " var x = obj[i];", " return function() { var /** null */ n = x; };", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** !IObject<string, number> */ obj) {", " /** @const */", " var x = obj['someString'];", " return function() { var /** null */ n = x; };", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testInferConstHook() { typeCheck(LINE_JOINER.join( "function f(/** number */ x, /** string */ y, z) {", " /** @const */", " var c = z ? x : y;", " return function() { var /** null */ w = c; };", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** number */ x, y, z) {", " /** @const */", " var c = z ? x : y;", " return function() { var /** null */ w = c; };", "}"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); } public void testPrototypeTemplateFunctionWithAtTypeJsdoc() { typeCheck(LINE_JOINER.join( "/**", " * @interface", " * @template T", " */", "function Foo() {};", "/** @type {function(T)} */", "Foo.prototype.m;")); } public void testConditionalNonnull() { typeCheck(LINE_JOINER.join( "function f(y) {", " var x = y;", " if (x && typeof x === 'object')", " var /** !Object */ n = x;", "}")); typeCheck(LINE_JOINER.join( "/** @param {*} x */", "function f(x) {", " if (x && typeof x === 'object')", " var /** !Object */ n = x;", "}")); typeCheck(LINE_JOINER.join( "function f(x) {", " if (x) {", // do nothing " } else {", " var /** !Object */ n = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testDontCrashOnBadPrototypeMethodDecl() { typeCheck(LINE_JOINER.join( // Forgot @const "var ns = {};", "/** @constructor */", "ns.Foo = function() {};", "(/** @lends {ns.Foo.prototype} */ { sayHi:function() {} });"), GlobalTypeInfo.LENDS_ON_BAD_TYPE); typeCheck(LINE_JOINER.join( // Forgot @const "var ns = {};", "(function() {", " /** @constructor */", " ns.Z = function() {};", " ns.Z = Polymer(/** @lends {ns.Foo.prototype} */ {", " is:'x-element',", " sayHi:function() {}", " });", "})();"), GlobalTypeInfo.LENDS_ON_BAD_TYPE); typeCheck(LINE_JOINER.join( "function Foo() {}", "Foo.prototype = {", " sayHi:function() {}", "};")); } public void testAnalyzeDeadCodeAndLiveCodeThatFollowsIt() { typeCheck(LINE_JOINER.join( "try {", " true;", "} catch (e) {", "} finally {", " var x = 1 - 'asdf';", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(x) {", " if (x) {", " 4567;", " return 123;", " 1 - 2;", " } else {", " }", " var /** null */ n = 123;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "for (;;) {", " break;", " 1 - 'asdf';", "}", "var x = 123;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "try {", " throw 123;", " 1 - 'asdf';", "} catch (e) {}"), NewTypeInference.INVALID_OPERAND_TYPE); // When the TRY can throw, only use the TRY's type env in the CATCH typeCheck(LINE_JOINER.join( "function f(/** (number|string) */ x) {", " try {", " x = 123;", " throw 'asdf';", " } catch (e) {", " var /** number */ n = x;", " }", "}")); } public void testGetterSetterPrototypeProperties() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() { this.prop = 123; }", "Foo.prototype = {", " set a(x) { this.prop = x; },", " get a() { return this.prop + 1; }", "};")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() { this.prop = 123; }", "Foo.prototype = {", " set a(x) { this.prop = x; },", " /** @return {number} */", " get a() { return this.prop + 1; }", "};", "var /** string */ s = (new Foo).a;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() { this.prop = 123; }", "Foo.prototype = {", " /** @param {number} x*/", " set a(x) { this.prop = x; },", " get a() { return this.prop + 1; }", "};", "(new Foo).a = 'asdf';"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testWith() { typeCheck(LINE_JOINER.join( "var a;", "with (a) {}")); typeCheck(LINE_JOINER.join( "var a;", "with (a) {", " var x = b;", "}")); } public void testMeetingWithTruthyFalsy() { typeCheck( LINE_JOINER.join( "function f(x) {", " if (!x) return null;", " return /** @type {number} */ (x);", "}")); typeCheck( "function f(x) { if (!x) { return /** @type {number} */ (x); } }"); } public void testUsingTruthy() { typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f(/** !Function */ x) {", " return x.superClass_ ? x.superClass_.constructor : null;", "}")); typeCheck(LINE_JOINER.join( "function f(/** !Function */ x) {", " if (x.superClass_) { x.superClass_.constructor = null; }", "}")); typeCheck(LINE_JOINER.join( "function g(x) { var /** !Object */ y = x; }", "function f(x) {", " if (!x) return null;", " g(x);", " return x instanceof Array;", "}")); typeCheck(LINE_JOINER.join( "function f(x) {", " if (!x) return null;", " forEach(x, function(y){});", "}", "/**", " * @param {!Array<T>} arr", " * @param {function(T)} cb", " * @template T", " */", "function forEach(arr, cb) {}")); typeCheck(LINE_JOINER.join( "/** @param {!Function} x */", "function g(x) {", " if (!x.foobar) {", " return;", " }", " for (var prop in x.foobar) {}", "}")); } public void testIObjectExternMissing() { // For old projects that are missing IObject in their externs typeCheckCustomExterns( LINE_JOINER.join( "/** @constructor */", "function Object() {}", "/** @constructor */", "function Function() {}", "/** @constructor */", "function Symbol() {}"), "var x = {};"); } public void testUnionWithStructNoSpuriousWarning() { typeCheck(LINE_JOINER.join( "/**", " * @record", " * @struct", " */", "var MyType = function() {};", "/** @type {(number|undefined)} */", "MyType.prototype.a;", "/**", " * @param {?MyType} t", " * @suppress {newCheckTypesAllChecks}", " */", "function MyFn(t) {", " if (!t) {", " t = {};", " }", " if (!t.a) {", " t.a = 1;", " }", "};")); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @struct", " */", "function Foo() { this.prop = 123; }", "/** @constructor */", "function Bar() {}", "function f(/** (!Foo|!Bar) */ x) {", " x.prop = 123;", "}")); } public void testRandomPropDefsDontShadowConstDefinition() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Bar() {", " this.CONST = 100;", "}", "/**", " * @constructor", " * @extends {Bar}", " */", "function Foo() {}", "/** @type {!Foo} */", "var foo = new Foo();", "function f(x) {", " x = new Foo;", " x.CONST = 123;", "}"), NewTypeInference.CONST_PROPERTY_REASSIGNED); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Bar() {", " this.CONST = 100;", "}", "/**", " * @constructor", " * @extends {Bar}", " */", "function Foo() {};", "/** @type {Foo} */", "var foo = new Foo();", "/** @type {number} */", "foo.CONST = 0;"), NewTypeInference.CONST_PROPERTY_REASSIGNED); } public void testConstantPropertyDeletion() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @const */", " this.bar = 3;", " delete this.bar;", "}"), NewTypeInference.CONST_PROPERTY_DELETED); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @const */", " this.bar = 3;", "}", "function f(/** ?Foo */ x) {", " delete x.bar;", "}"), NewTypeInference.NULLABLE_DEREFERENCE, NewTypeInference.CONST_PROPERTY_DELETED); } public void testSetterNotTreatedAsProp() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() { this.prop = 123; }", "Foo.prototype = {", " set a(x) { this.prop = x; }", "};", "var y = (new Foo).a;"), NewTypeInference.INEXISTENT_PROPERTY); } public void testNotUniqueInstantiationCompatibility() { String js = LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {T} y", " * @return {T}", " */", "function f(x, y) {", " return x;", "}", "var /** (number|boolean) */ x = f(123, 'asdf');"); typeCheck(js, NewTypeInference.NOT_UNIQUE_INSTANTIATION); compilerOptions.setWarningLevel( DiagnosticGroups.NEW_CHECK_TYPES_EXTRA_CHECKS, CheckLevel.OFF); typeCheck(js, NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testAnonymousCtorCompatibility() { String js = LINE_JOINER.join( "function f(x) {", " var c = x ?", " /** @constructor */", " function() { this.a = 123; } :", " /** @constructor */", " function() { this.b = 123; };", " var /** null */ n = new c();", "}"); typeCheck(js, GlobalTypeInfo.ANONYMOUS_NOMINAL_TYPE, GlobalTypeInfo.ANONYMOUS_NOMINAL_TYPE, NewTypeInference.MISTYPED_ASSIGN_RHS); compilerOptions.setWarningLevel( DiagnosticGroups.NEW_CHECK_TYPES_EXTRA_CHECKS, CheckLevel.OFF); typeCheck(js, NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testPrototypeMethodInDifferentScopeCompatibility() { String js = LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function g() {", " /** @return {number} */", " Foo.prototype.method = function() {", " return 123;", " };", "}", "var /** string */ s = (new Foo).method();"); typeCheck(js, GlobalTypeInfo.CTOR_IN_DIFFERENT_SCOPE, NewTypeInference.MISTYPED_ASSIGN_RHS); compilerOptions.setWarningLevel( DiagnosticGroups.NEW_CHECK_TYPES_EXTRA_CHECKS, CheckLevel.OFF); typeCheck(js, NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testPropertiesNotDefinedAnywhere() { typeCheck( "var x = (/** @type {?} */ (null)).undefinedProp;", NewTypeInference.INEXISTENT_PROPERTY); typeCheck( LINE_JOINER.join( "function f(/** ? */ x) {", " x.bar.baz = 123;", "}"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "function f(/** ? */ x) {", " if (x.bar === undefined) {", " return 123;", " }", "}")); typeCheck(LINE_JOINER.join( "function f(/** ? */ x) {", " if (x.bar !== undefined) {", " return x.bar();", " }", "}")); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {", " FOO: 123", "};", "function f(/** ? */ x) {", " return x.FOO;", "}")); typeCheckCustomExterns( DEFAULT_EXTERNS + "/** @enum */ var myenum = { FOO: 123 };", "function f(x) { return x.FOO; }"); typeCheck(LINE_JOINER.join( "/** @enum */ var myenum = { FOO: 123 };", "function f(x) { return x.FOO; }")); typeCheckCustomExterns( DEFAULT_EXTERNS + LINE_JOINER.join( "/** @param {{ myprop: number }} x */", "function f(x) {}"), "function g(x) { return x.myprop; }"); } public void testInferConstTypeWeirdOrderDontCrash() { typeCheck(LINE_JOINER.join( "function f() {", " /** @const */ var h = g;", " function g(/** number */ x) {}", " return function() { h(); }", "}"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); } public void testDontCrashWithPolymerJsdoc() { typeCheck(LINE_JOINER.join( "/** @constructor */ function Polymer() {}", "Polymer.prototype = {", " /** @event @param {number} x */", " prop: 'asdf'", "};")); } public void testConstInferenceArbitraryReceivers() { typeCheck(LINE_JOINER.join( "/** @const */", "var c = (new Number(123)).toString();", "function f(x) {", " var /** number */ n = c;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testConstInferenceAutoboxScalars() { typeCheck(LINE_JOINER.join( "/** @const */", "var c = (123).toString();", "function f(x) {", " var /** number */ n = c;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testPropertyCheckingCompatibility() { compilerOptions.setWarningLevel( DiagnosticGroups.NEW_CHECK_TYPES_EXTRA_CHECKS, CheckLevel.OFF); typeCheck(LINE_JOINER.join( "function f(/** !Object */ x) {", " return x.length + 1;", "}")); typeCheck(LINE_JOINER.join( "var x = { a: 123 };", "var y = { b: 234 };", "var z = y.a + 5;"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "function f(/** !Object */ x) {", " return x.prop + 1;", "}", "/** @constructor */", "function Bar() {", " this.prop = 123;", "}")); typeCheck(LINE_JOINER.join( "function f(/** !IObject<?, ?> */ x) {", " return x.prop + 1;", "}", "/** @constructor */", "function Bar() {", " this.prop = 123;", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor @extends {Foo} */", "function Bar() {", " this.prop = 123;", "}", "function f(/** !Foo */ x) {", " return x.prop;", "}")); typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "/** @constructor @implements {Foo} */", "function Bar() {", " this.prop = 123;", "}", "function f(/** !Foo */ x) {", " return x.prop;", "}")); typeCheck(LINE_JOINER.join( "/** @constructor @struct */", "function Foo() {}", "/** @constructor @extends {Foo} */", "function Bar() {", " this.prop = 123;", "}", "function f(/** !Foo */ x) {", " return x.prop;", "}"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @enum {!Foo} */", "var E = {", " A: new Foo,", " B: new Foo", "};", "/** @constructor */", "function Foo() {}", "/** @constructor @extends {Foo} */", "function Bar() {", " this.prop = 123;", "}", "function f(/** !E */ x) {", " return x.prop;", "}", "f(E.A);")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f(/** (!Foo | { a: number }) */ x) {", " return x.myprop;", "}"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor @extends {Foo} */", "function Bar() {}", "/** @constructor @extends {Bar} */", "function Baz() {", " this.prop = 123;", "}", "function f(/** !Foo */ x) {", " return x.prop + 123;", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/**", " * @constructor", " * @template T", " * @extends {Foo}", " */", "function Bar() {}", "/**", " * @constructor", " * @extends {Bar<number>}", " */", "function Baz() {}", "/**", " * @constructor", " * @extends {Bar<string>}", " */", "function Qux() {", " this.prop = 123;", "}", "function f(/** !Foo */ x) {", " return x.prop + 234;", "}")); } public void testConstructorDeclWithoutFunctionLiteral() { typeCheckCustomExterns( DEFAULT_EXTERNS + "/** @constructor */ var Foo;", "var x = new Foo;"); typeCheckCustomExterns( DEFAULT_EXTERNS + LINE_JOINER.join( "/** @const */ var ns = {};", "/**", " * @constructor", " * @param {number} x", " */", "ns.Foo;"), "var x = new ns.Foo('asdf');", NewTypeInference.INVALID_ARGUMENT_TYPE); // ns is not declared; don't crash typeCheckCustomExterns( DEFAULT_EXTERNS + "/** @constructor */ ns.Foo;", ""); typeCheck(LINE_JOINER.join( "/** @constructor */ var Foo;", "var x = new Foo;"), NewTypeInference.NOT_CALLABLE); } public void testAssignmentsThatArentExprResultsDeclareProperties() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " this.a = this.b = 123;", "}", "function f(/** !Foo */ x) {", " return x.b + 1;", "}")); } public void testOnlyOneInstanceOfEachClassInUnion() { typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " * @param {T} x", " */", "function Foo(x) {", " /** @type {T} */", " this.prop = x;", "}", "function f(x) {", " var y = x ? new Foo(123) : new Foo('asdf');", " var /** null */ n = y.prop;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " * @param {T} x", " */", "function Foo(x) {", " /** @type {T} */", " this.prop = x;", "}", "function f(x) {", " var y = x ? new Foo(123) : new Foo('asdf');", " var /** (number|string) */ n = y.prop;", "}")); typeCheck(LINE_JOINER.join( "/** @interface */", "function I1() {}", "/** @interface */", "function I2() {}", "/**", " * @constructor", " * @implements {I1}", " * @implements {I2}", " */", "function C() {}", "function f(/** !I1 */ x, /** !I2 */ y) {", " var z;", " if (1<2) {", " x.p1 = 123;", " z = x;", " } else {", " y.p2 = 234;", " z = y;", " }", " if (z instanceof C) {", " var /** (number|undefined) */ n = z.p2;", " var /** !C */ w = z;", " }", "}")); } public void testLooseTypeVariablesInCompatibilityMode() { compilerOptions.setWarningLevel( DiagnosticGroups.NEW_CHECK_TYPES_EXTRA_CHECKS, CheckLevel.OFF); typeCheck(LINE_JOINER.join( "/**", " * @param {T} x", " * @return {T}", " * @template T", " */", "function f(x) {", " var /** null */ n = x;", " return x;", "}", "var /** number */ n = f('asdf');"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @param {function(this:THIS, number)} x", " * @template THIS", " */", "function f(x) {", " x.call(null, 123);", " x.call(undefined, 'asdf');", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @param {T} x", " * @template T", " */", "function Foo(x) {", " /** @type {T} */", " this.prop = x;", "}", "/** @return {T} */", "Foo.prototype.getProp = function() {", " var /** number */ n = this.prop;", " return this.prop;", "};", "var /** number */ n = (new Foo('asdf')).getProp();"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testFunctionSubtypingInCompatibilityMode() { compilerOptions.setWarningLevel( DiagnosticGroups.NEW_CHECK_TYPES_EXTRA_CHECKS, CheckLevel.OFF); typeCheck(LINE_JOINER.join( "/**", " * @this {!Object}", " * @param {number} x", " */", "function f(x) {}", "/** @param {function(number)} x */", "function g(x) {}", "g(f);")); } public void testDontCrashWhenDeclaringFunctionOnScalar() { typeCheck(LINE_JOINER.join( "/** @type {boolean} */", "var b = true;", "if (b) {", " b.onstatechange = function() {};", "}"), NewTypeInference.ADDING_PROPERTY_TO_NON_OBJECT); } public void testDontTypeGlobalThisAsUknown() { typeCheck( "var /** null */ x = this;", NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck("var /** !Window */ x = this;"); typeCheck( "var y = this.toString();", NewTypeInference.GLOBAL_THIS); } public void testDontPropagateRhsInferenceToLhs() { typeCheck(LINE_JOINER.join( "function f(x) {", " if (!x || h(x)) { return 123; }", "}", "function h(/** null */ x) {}"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testLooseTypingOfLooseObjectsInCompatibilityMode() { compilerOptions.setWarningLevel( DiagnosticGroups.NEW_CHECK_TYPES_EXTRA_CHECKS, CheckLevel.OFF); typeCheck(LINE_JOINER.join( "/** @interface */", "function Super() {}", "/** @constructor @implements {Super} */", "function Sub() {}", "function f(x) {", " var /** !Sub */ y = x.prop;", "}", "function g(/** !Super */ obj) {", " f({ prop: obj });", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @type {(number|string)} */", "Foo.prototype.prop;", "/** @type {function(!Foo):?} */", "var f;", "f = function(x) {", " var /** number */ y = x.prop;", "};")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {}", "/** @type {?Object} */", "Bar.prototype.prop;", "/** @type {function(?Bar):?} */", "var f;", "f = function(x) {", " var /** !Foo */ y = x.prop;", "};")); } public void testBivariantArrayGenericsInCompatibilityMode() { compilerOptions.setWarningLevel( DiagnosticGroups.NEW_CHECK_TYPES_EXTRA_CHECKS, CheckLevel.OFF); typeCheck(LINE_JOINER.join( "/**", " * @param {!Array<number>} lhs", " * @param {!Array<(number|string)>} rhs", " */", "function f(lhs, rhs) {", " lhs = rhs;", "}")); typeCheck(LINE_JOINER.join( "/**", " * @param {!Array<(number|string)>} lhs", " * @param {!Array<number>} rhs", " */", "function f(lhs, rhs) {", " lhs = rhs;", "}")); typeCheck(LINE_JOINER.join( "/**", " * @param {!Array<!Object>} lhs", " * @param {!Array<?Function>} rhs", " */", "function f(lhs, rhs) {", " lhs = rhs;", "}")); } public void testMaybeSpecializeInCast() { // In this case, we're explicitly using the cast to "protect" arr from the // context, so it's not correct to infer that arr has type !Array<!Sub> // just because g takes a !Array<!Sub>. typeCheck(LINE_JOINER.join( "/** @constructor */", "function Super() {}", "/** @constructor @extends {Super} */", "function Sub() {}", "function g(/** !Array<!Sub> */ x) {}", "function f(/** function(!Array<!Super>) */ x) {}", "function h(arr) {", " g(/** @type {!Array<!Sub>} */ (arr));", "}", "f(h);")); typeCheck(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @return {!Foo} */", "function foo(/** !Object */ x) {", " return /** @type {?Foo} */ (x);", "}"), NewTypeInference.RETURN_NONDECLARED_TYPE); typeCheck(LINE_JOINER.join( "function foo(/** ?Object */ x) {", " var /** !Object */ n;", " return /** @type {*} */ (x) && (n = x);", "}")); } public void testGettersSettersAsNamespaceProperties() { typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {", " /** @return {string} */", " get a() { return 'asdf'; }", "};", "ns.a - 5;"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {", " /** @param {string} x */", " set a(x) {}", "};", "ns.a = 5;"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testAbstractDeclarations() { typeCheck(LINE_JOINER.join( "/** @abstract @constructor */", "function C() {}", "new C;"), NewTypeInference.CANNOT_INSTANTIATE_ABSTRACT_CLASS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @abstract */", "Foo.prototype.bar = function(x) {};"), GlobalTypeInfo.ABSTRACT_METHOD_IN_CONCRETE_CLASS); typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "/** @abstract */", "Foo.prototype.bar = function(x) {};"), GlobalTypeInfo.ABSTRACT_METHOD_IN_INTERFACE); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @abstract", " */", "function Foo() {}", "/** @abstract */", "Foo.prototype.f = function() {};", "/**", " * @constructor", " * @abstract", " * @extends {Foo}", " */", "function Bar() {}")); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @abstract", " */", "function Foo() {}", "/** @abstract */", "Foo.prototype.f = function() {};", "/**", " * @constructor", " * @extends {Foo}", " */", "function Bar() {}"), GlobalTypeInfo.ABSTRACT_METHOD_NOT_IMPLEMENTED_IN_CONCRETE_CLASS); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @abstract", " */", "function Foo() {}", "/** @abstract */", "Foo.prototype.f = function() {};", "/**", " * @constructor", " * @abstract", " * @extends {Foo}", " */", "function Bar() {}", "/**", " * @constructor", " * @extends {Bar}", " */", "function Baz() {}"), GlobalTypeInfo.ABSTRACT_METHOD_NOT_IMPLEMENTED_IN_CONCRETE_CLASS); // TODO(dimvar): this warning is wrong. // But to remove it, we should check that a concrete class C that inherits from an // abstract class A that implements an interface I, implements all methods of I. // Currently, we only check classes that directly implement an interface. typeCheck(LINE_JOINER.join( "/** @interface */", "function I() {}", "/** @param {number} x */", "I.prototype.method = function(x) {};", "/** @constructor @abstract @implements{I} */", "function C() {}"), GlobalTypeInfo.INTERFACE_METHOD_NOT_IMPLEMENTED); typeCheck(LINE_JOINER.join( "/** @constructor @abstract */", "function Foo() {}", "/** @abstract @return {number} */", "Foo.prototype.bar = function(x) {};")); typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "/** @return {number} */", "Foo.prototype.prop = function() {};", "/**", " * @constructor", " * @abstract", " * @implements {Foo}", " */", "function Bar() {}", "/** @abstract */", "Bar.prototype.prop = function() {};")); } public void testAbstractMethodCalls() { // Converted from Closure style "goog.base" super call typeCheck( LINE_JOINER.join( "/** @const */ var ns = {};", "/** @constructor @abstract */ ns.A = function() {};", "/** @abstract */ ns.A.prototype.foo = function() {};", "/** @constructor @extends {ns.A} */ ns.B = function() {};", "ns.B.superClass_ = ns.A.prototype", "/** @override */ ns.B.prototype.foo = function() {", " ns.B.superClass_.foo.call(this);", "};"), NewTypeInference.ABSTRACT_SUPER_METHOD_NOT_CALLABLE); typeCheck( LINE_JOINER.join( "/** @const */ var ns = {};", "/** @constructor @abstract */ ns.A = function() {};", "/** @abstract */ ns.A.prototype.foo = function() {};", "/** @constructor @extends {ns.A} */ ns.B = function() {};", "ns.B.superClass_ = ns.A.prototype", "/** @override */ ns.B.prototype.foo = function() {", " ns.B.superClass_.foo.apply(this);", "};"), NewTypeInference.ABSTRACT_SUPER_METHOD_NOT_CALLABLE); typeCheck( LINE_JOINER.join( "/** @constructor @abstract */ var A = function() {};", "/** @abstract */", "A.prototype.foo = function() {};", "/** @constructor @extends {A} */ var B = function() {};", "/** @override */ B.prototype.foo = function() { A.prototype.foo['call'](this); };"), NewTypeInference.ABSTRACT_SUPER_METHOD_NOT_CALLABLE); typeCheck( LINE_JOINER.join( "/** @struct @constructor */ var A = function() {};", "A.prototype.foo = function() {};", "/** @struct @constructor @extends {A} */ var B = function() {};", "/** @override */ B.prototype.foo = function() {", " (function() {", " return A.prototype.foo.call($jscomp$this);", " })();", "};")); typeCheck( LINE_JOINER.join( "/** @constructor @abstract */ function A() {};", "/** @abstract */ A.prototype.foo = function() {};", "/** @constructor @extends {A} */ function B() {};", "/** @override */ B.prototype.foo = function() {};", "var abstractMethod = A.prototype.foo;", "abstractMethod.call(new B);"), NewTypeInference.ABSTRACT_SUPER_METHOD_NOT_CALLABLE); typeCheck( LINE_JOINER.join( "/** @constructor @abstract */ var A = function() {};", "/** @constructor @extends {A} */ var B = function() { A.call(this); };")); typeCheck( LINE_JOINER.join( "/** @constructor @abstract */ function A() {};", "/** @abstract */ A.prototype.foo = function() {};", "/** @constructor @extends {A} */ function B() {};", "/** @override */ B.prototype.foo = function() {};", "var abstractMethod = A.prototype.foo;", "(0, abstractMethod).call(new B);"), NewTypeInference.ABSTRACT_SUPER_METHOD_NOT_CALLABLE); } public void testDontRemoveLooseObjects() { typeCheck(LINE_JOINER.join( "function f(x) {", " if (Object(x) === x && !Array.isArray(x)) {", " for (var p in x) {}", " }", "}")); } public void testFunctionDeclsWithoutFunctionLiteralOnNamespaces() { typeCheck( "window.setTimeout(123);", NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheckCustomExterns( DEFAULT_EXTERNS + LINE_JOINER.join( "/** @const */ var ns = {};", "/** @param {number} x */", "ns.foobar;"), "ns.foobar('asdf');", NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testGoogJsonStyleWeirdFunctionDeclaration() { typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", // NOTE(dimvar): this jsdoc is not used to typecheck the function literals, // they are considered unannotated functions. "/** @param {string} s */", "ns.jsonparse = 1 < 2 ? function(s) {} : function(s) {};", "function f() {", " ns.jsonparse(123);", "}"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testWorkaroundWayToDeclareFunctionsWithProperties() { typeCheckCustomExterns( DEFAULT_EXTERNS + LINE_JOINER.join( "/** @typedef {function(number)} */", "var Fun1;", "/** @type {Fun1} */", "var fun1_;", "/** @type {number} */", "fun1_.extraProp;"), LINE_JOINER.join( "function f(/** !Fun1 */ x) {", " var /** string */ s = x.extraProp;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheckCustomExterns( DEFAULT_EXTERNS + LINE_JOINER.join( "/** @typedef {function(number)} */", "var Fun1;", "/** @type {Fun1} */", "var fun1_;", "/** @type {number} */", "fun1_.extraProp;", "/** @typedef {function(string)} */", "var Fun2;", "/** @type {Fun2} */", "var fun2_;", "/** @type {string} */", "fun2_.extraProp;"), ""); // Only be loose for stray prop defs, not defs on the Function type. typeCheckCustomExterns( DEFAULT_EXTERNS + LINE_JOINER.join( "/** @type {number} */", "Function.prototype.foo;", "/** @type {string} */", "Function.prototype.foo;"), "", GlobalTypeInfo.REDECLARED_PROPERTY); // The types of extraProp join to bottom; make it unknown. typeCheckCustomExterns( DEFAULT_EXTERNS + LINE_JOINER.join( "/** @typedef {function(number)} */", "var Fun1;", "/** @type {Fun1} */", "var fun1_;", "/** @type {function(number)} */", "fun1_.extraProp;", "/** @typedef {function(string)} */", "var Fun2;", "/** @type {Fun2} */", "var fun2_;", "/** @type {function(string)} */", "fun2_.extraProp;"), LINE_JOINER.join( "function f(/** !Fun1 */ x) {", " x.extraProp = function(y) {};", "}")); } public void testDontCrashWhenInferringConstWithTopFunction() { typeCheck("/** @const */ var bar = new Function('')();"); } // Documenting that inferring @const types depends on the order in which // we traverse scopes during GlobalTypeInfo. This seems like an inherent // issue which cannot be avoided. public void testConstInferenceDependsOnOrderOfScopeTraversing() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {number} */", " this.prop = 123;", "};", "/** @type {!Foo} */", "var foo = new Foo;", "/** @const */", "var c = foo.prop;", "function g() {", " return c;", "}"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {number} */", " this.prop = 123;", "};", "function f() {", " /** @type {!Foo} */", " var foo = new Foo;", " /** @const */", " var c = foo.prop;", " function g() {", " return c;", " }", "}")); } // We currently consider Object<K,V> to be the same as IObject<K,V>. // Since we may change that in the future, they are tested separately. public void testParameterizedObject() { typeCheck(LINE_JOINER.join( "function f(/** !Object<?, ?> */ x) {", " return x.hasOwnProperty('test');", "}")); typeCheck(LINE_JOINER.join( "function g(x) {", " x.foobar = 123;", "}", "function f(/** !Object<?, ?> */ x) {", " return x.foobar;", "}"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " this.foobar = 123;", "}", "function f(/** !Object<?, ?> */ x) {", " return x.foobar;", "}"), NewTypeInference.INEXISTENT_PROPERTY); typeCheck("var /** !Object<?, ?> */ x = { 'abs': '', '%': ''};"); typeCheck("var /** !Object<string, number> */ x = { a: 1, b: 2, c: undefined };"); typeCheck(LINE_JOINER.join( "function f(/** !Object<number,string> */ x) {", " return x['asdf'];", "}"), NewTypeInference.INVALID_INDEX_TYPE); typeCheck(LINE_JOINER.join( "function f(/** !Object<number, number> */ x) {", " x['asdf'] = 123;", "}"), NewTypeInference.INVALID_INDEX_TYPE); typeCheck(LINE_JOINER.join( "function f(/** !Object<number, string> */ x, /** number */ i) {", " x[i] - 123;", "}"), NewTypeInference.INVALID_OPERAND_TYPE); typeCheck(LINE_JOINER.join( "function f(/** !Object<boolean> */ x) {", " var /** string */ s = x['asdf'];", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "function f(/** !Object<number> */ x) {", " return x[{a: 123}] + x['sadf'];", "}")); typeCheck(LINE_JOINER.join( "/**", " * @param {!Object<number, number>} x", " * @param {!Object<(number|string), number>} y", " */", "function f(x, y) {", " x = y;", "}")); typeCheck(LINE_JOINER.join( "/**", " * @param {!Object<(number|string), number>} x", " * @param {!Object<number, number>} y", " */", "function f(x, y) {", " x = y;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testUseConstructorTypeInConstInference() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/**", " * @template T", " * @param {function(new:T)} x", " * @return {T}", " */", "function appContextGet(x) {", " return new x;", "}", "/** @const */", "var c = appContextGet(Foo);", "function f() {", " var /** null */ n = c;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "function appContextGet(x) {", " return x;", "}", "/** @const */", "var c = appContextGet(Foo);", "function f() {", " var /** null */ n = c;", "}"), GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE); // Test to show that in convoluted cases the constructor type leaks // into the const inference result even though it shouldn't. // Correct behavior would be a COULD_NOT_INFER_CONST_TYPE warning. // Currently, the const is inferred to a type that includes the marker property, // which then is not present during NTI and we get a spurious warning. typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/**", " * @template T", " * @param {T} x", " * @return {T}", " */", "function appContextGet(x) {", " return x;", "}", "/** @const */", "var c = 0 || { myprop: appContextGet(Foo) };", "function f() {", " return c;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testDontCrashWhenUnifyingNominalTypeWithEmptyTypemap() { typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template SCOPE", " */", "function MyEventHandler() {}", "/**", " * @template T", " * @this {T}", " * @return {!MyEventHandler<T>}", " */", "MyEventHandler.prototype.getHandler = function() {", " return /** @type {!MyEventHandler<T>} */ (new MyEventHandler);", "};", "/**", " * @param {function(this: SCOPE, EVENTOBJ)} x", " * @template EVENTOBJ", " */", "MyEventHandler.prototype.listen = function(x) {};", "MyEventHandler.prototype.foobar = function() {", " this.getHandler().listen(MyEventHandler.prototype.listen);", "};")); } public void testSpecializeFunctionReturnType() { typeCheck(LINE_JOINER.join( "/** @return {?Object} */", "function f() { return {}; }", "function g(/** ?Object */ x) {", " if ((x = f()) !== null) {", " var /** !Object */ y = x;", " }", "}")); } public void testDontWarnAboutUnknownExtends() { typeCheck(LINE_JOINER.join( "function f(/** function(new: Object) */ clazz) {", " /**", " * @constructor", " * @extends {clazz}", " */", " function Foo() {}", "}")); typeCheck(LINE_JOINER.join( "function f(/** function(new: ?) */ clazz) {", " /**", " * @constructor", " * @extends {clazz}", " */", " function Foo() {}", "}")); typeCheck(LINE_JOINER.join( "function f(clazz) {", " /**", " * @constructor", " * @extends {clazz}", " */", " function Foo() {}", "}")); typeCheck(LINE_JOINER.join( "function f(/** !Function */ clazz) {", " /**", " * @constructor", " * @extends {clazz}", " */", " function Foo() {}", "}"), JSTypeCreatorFromJSDoc.EXTENDS_NON_OBJECT); typeCheck(LINE_JOINER.join( "function f(/** number */ clazz) {", " /**", " * @constructor", " * @extends {clazz}", " */", " function Foo() {}", "}"), JSTypeCreatorFromJSDoc.EXTENDS_NON_OBJECT); typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {function(new: T)} clazz", " */", "function f(clazz) {", " /**", " * @constructor", " * @extends {clazz}", " */", " function Foo() {}", "}")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {", " /** @type {number} */", " this.prop = 123;", "}", "function f(/** function(new: Foo) */ clazz) {", " /**", " * @constructor", " * @extends {clazz}", " */", " function Bar() {}", " var /** string */ s = (new Bar).prop;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testMixinApplication() { String defs = LINE_JOINER.join( "/** @constructor */", "function MyElement() {", " /** @type {string} */", " this.elemprop = 'asdf';", "}", "/** @record */", "function Toggle() {}", "/**", " * @param {string} x", " * @return {string}", " */", "Toggle.prototype.foobar = function(x) {};", "/**", " * @template T", " * @param {function(new:T)} superclass", " */", "function addToggle(superclass) {", " /**", " * @constructor", " * @extends {superclass}", " * @implements {Toggle}", " */", " function Clazz() {", " Superclass.apply(this, arguments);;", " }", " Clazz.prototype = Object.create(Superclass.prototype);", " /** @override */", " Clazz.prototype.foobar = function(x) { return 'foobar ' + x; };", " return Clazz;", "}"); typeCheck(LINE_JOINER.join( defs, "/**", " * @constructor", " * @extends {MyElement}", " * @implements {Toggle}", " */", "var MyElementWithToogle = addToggle(MyElement);", "(new MyElementWithToogle).foobar(123);"), NewTypeInference.INVALID_ARGUMENT_TYPE); typeCheck(LINE_JOINER.join( defs, "/**", " * @constructor", " * @extends {MyElement}", " * @implements {Toggle}", " */", "var MyElementWithToogle = addToggle(MyElement);", "(new MyElementWithToogle).elemprop = 123;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( defs, "var MyElementWithToogle = addToggle(MyElement);", "(new MyElementWithToogle).foobar(123);", // The MyElementWithToogle type is unknown "/** @type {MyElementWithToogle} */ var x = 123;"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testSpecializeAfterPropAbsenceTest() { typeCheck(LINE_JOINER.join( "/**", " * @param {{foo:(number|undefined)}} x", " * @return {number}", " */", "function f(x) {", " return (x.foo === undefined ? 123 : x.foo);", "}")); } public void testMixedClassInheritance() { typeCheck(LINE_JOINER.join( "/** @record */", "function IToggle() {}", "/** @return {number} */", "IToggle.prototype.foo = function() {};", "/**", " * @param {function(new:T)} superClass", " * @template T", " */", "function addToggle(superClass) {", " /**", " * @constructor", " * @extends {superClass}", " * @implements {IToggle}", " */", " var Toggle = function() {};", " Toggle.prototype.foo = function() { return 5; };", " return Toggle;", "}", "/** @struct @constructor */", "var Bar = function() {};", "/**", " * @constructor", " * @extends {Bar}", " * @implements {IToggle}", " */", "var ToggleBar = addToggle(Bar);", "/**", " * @constructor", " * @extends {ToggleBar}", " */", "var Foo = function() {};", "/** @override @return {string} */", "Foo.prototype.foo = function() { return ''; };"), GlobalTypeInfo.INVALID_PROP_OVERRIDE); } public void testConstructorProperty() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "var /** !Foo */ x = new Foo.prototype.constructor;")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "var /** null */ x = new Foo.prototype.constructor;"), NewTypeInference.MISTYPED_ASSIGN_RHS); // Resolves to the Object.prototype.constructor property, not to Foo.prototype.constructor typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "var ctor = (new Foo).constructor;", "if (ctor != null) {", " var /** null */ n = new ctor;", "}")); } public void testDontSpecializeTopInInstanceofElseBranch() { typeCheck(LINE_JOINER.join( "/** @param {*} x */", "function f(x) {", " if (x instanceof Function) {", " } else {", " var /** !Object */ obj = x;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testMethodInheritedFromGrandparentType() { typeCheck(LINE_JOINER.join( "/**", " * @interface", " * @template T", " */", "function Foo() {}", "/** @return {T} */", "Foo.prototype.foo = function() {};", "/**", " * @interface", " * @extends {Foo<T>}", " * @template T", " */", "function Bar() {}", "/**", " * @constructor", " * @implements {Bar<T>}", " * @param {T} x", " * @template T", " */", "function Baz(x) {", " this.foo_ = x;", "};", "Baz.prototype.foo = function() { return this.foo_; };", "function f(/** !Baz<number> */ x) {", " var /** number */ n = x.foo();", "}")); typeCheck(LINE_JOINER.join( "/**", " * @interface", " * @template T", " */", "function Foo() {}", "/** @return {T} */", "Foo.prototype.foo = function() {};", "/**", " * @interface", " * @extends {Foo<T>}", " * @template T", " */", "function Bar() {}", "/**", " * @constructor", " * @implements {Bar<T>}", " * @param {T} x", " * @template T", " */", "function Baz(x) {", " this.foo_ = x;", "};", "Baz.prototype.foo = function() { return this.foo_; };", "function f(/** !Baz<number> */ x) {", " var /** string */ n = x.foo();", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() { this.prop_; }", "/** @return {T} */", "Foo.prototype.method = function() { return this.prop_; }", "/**", " * @constructor", " * @extends {Foo<T>}", " * @template T", " */", "function Bar() {}", "/**", " * @constructor", " * @extends {Bar<T>}", " * @template T", " */", "function Baz() {}", "function f(/** !Baz<number> */ x) {", " var /** number */ n = x.method();", "}")); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() { this.prop_; }", "/** @return {T} */", "Foo.prototype.method = function() { return this.prop_; }", "/**", " * @constructor", " * @extends {Foo<T>}", " * @template T", " */", "function Bar() {}", "/**", " * @constructor", " * @extends {Bar<T>}", " * @template T", " */", "function Baz() {}", "function f(/** !Baz<number> */ x) {", " var /** string */ n = x.method();", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testDiamondInheritance() { typeCheck(LINE_JOINER.join( "/**", " * @interface", " * @template T", " */", "function Foo() {}", "/** @return {T} */", "Foo.prototype.foo = function() {};", "/**", " * @interface", " * @extends {Foo<T>}", " * @template T", " */", "function Bar() {}", "/**", " * @constructor", " * @abstract", " * @implements {Foo<T>}", " * @template T", " */", "function AbstractBaz() {}", "/** @abstract */", "AbstractBaz.prototype.foo = function() {};", "/**", " * @constructor", " * @implements {Bar<T>}", " * @extends {AbstractBaz<T>}", " * @param {T} x", " * @template T", " */", "function Baz(x) {", " this.foo_ = x;", "};", "Baz.prototype.foo = function() { return this.foo_; };")); } public void testInstantiatePrototypeObjectWithUnknowns() { // When accessing a supertype property from an instance of the subclass, // generics instantiation works as expected. typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() {", " /** @type {T} */", " this.prop;", "}", "/**", " * @constructor", " * @extends {Foo<T>}", " * @template T", " */", "function Bar() {}", "function f(/** !Bar<number> */ x) {", " var /** string */ s = x.prop;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); // But all instances of the subclass share a prototype object, // which is instantiated with unknowns. typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() {", " /** @type {T} */", " this.prop;", "}", "/**", " * @constructor", " * @extends {Foo<T>}", " * @template T", " */", "function Bar() {}", "function f(/** !Bar<number> */ x) {", " var /** string */ s = Bar.prototype.prop;", "}")); } public void testSuppressMissingProperties() { typeCheck(LINE_JOINER.join( "/**", " * @param {!Object} x", " * @suppress {missingProperties}", " */", "function f(x) {", " return x.asdf;", "}")); } public void testGoogGlobalAsWindow() { typeCheck(LINE_JOINER.join( CLOSURE_BASE, "goog.global = this;", "/** @type {!Object<string, string>|undefined} */", "goog.global.foo;", "if (goog.global.foo) {", " var z = goog.global.foo;", "}")); } public void testSuppressInvalidCasts() { typeCheck(LINE_JOINER.join( "/** @suppress {invalidCasts} */", "function f() {", " /** @type {number} */ ('asdf');", "}")); } public void testRegisterStrayPropertyOnReceiverWithUndefined() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f(/** (!Foo|undefined) */ x) {", " x.myprop;", "}", "function g(/** !Foo */ x) {", " return x.myprop;", "}"), NewTypeInference.NULLABLE_DEREFERENCE); } public void testGettingSignatureForCallbackFromGenericCallee() { typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {function(T)} y", " * @return {T}", " */", "function f(x, y) { return x; }", "/** @const */", "var c = f(123, function(x) { var /** string */ s = x; });", "function g() { c; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); // U is unknown; callback is typed function(number) typeCheck(LINE_JOINER.join( "/**", " * @template T, U", " * @param {T} x", " * @param {U} y", " * @param {function(T)} fun", " */", "function f(x, y, fun) {}", "f(123, globalvar, function(x) { var /** null */ n = x; });"), NewTypeInference.MISTYPED_ASSIGN_RHS); // U is unknown; callback is typed function(?) typeCheck(LINE_JOINER.join( "/**", " * @template T, U", " * @param {T} x", " * @param {U} y", " * @param {function(U)} fun", " */", "function f(x, y, fun) {}", "f(123, globalvar, function(x) { var /** null */ n = x; });")); // Can't get declared type for callback during GTI. It's inferred as function(null) during NTI. typeCheck(LINE_JOINER.join( "/**", " * @template T", " * @param {T} x", " * @param {T} y", " * @param {function(T)} fun", " */", "function f(x, y, fun) {}", "f(123, 'asdf', function(x) { var /** null */ n = x; });"), NewTypeInference.NOT_UNIQUE_INSTANTIATION); typeCheck(LINE_JOINER.join( "/**", " * @this {?IArrayLike<T>|string}", " * @param {?function(T, number)} callback", " * @template T", " */", "function myforeach(callback) {}", "function f(/** !IArrayLike<number> */ a) {", " Array.prototype.forEach.call(a, function(item) {", " var /** string */ s = item;", " });", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testInferConstCallApply() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @return {number} */", "Foo.prototype.method = function() { return 123; };", "/** @const */", "var c = Foo.prototype.method.call(new Foo);", "function f() { var /** string */ s = c; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @return {number} */", "Foo.prototype.method = function() { return 123; };", "/** @const */", "var c = Foo.prototype.method.apply(new Foo);", "function f() { var /** string */ s = c; }"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testTryGettingSignatureFromCalleeDontCrash() { typeCheck( "(123)(function() {});", NewTypeInference.NOT_CALLABLE); } public void testPromoteTrueAndFalseToBooleanWhenInstantiatingGenerics() { typeCheck(LINE_JOINER.join( "/**", " * @param {...T} var_args", " * @template T", " */", "function x(var_args) {}", "x(true, false);")); } public void testExtendWindowDontCrash() { typeCheck(LINE_JOINER.join( "/** @type {number} */", "var globalvar;", "/**", " * @constructor", " * @extends {Window}", " */", "function FakeWindow() {}", "function f(/** !Window */ w) {", " return w.globalvar;", "}", "f(new FakeWindow);"), NewTypeInference.INEXISTENT_PROPERTY); } public void testInferUndeclaredPrototypeProperty() { typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @constructor */", "ns.Foo = function() {};", "ns.Foo.prototype.a = 123;", "function f() {", " var /** null */ n = ns.Foo.prototype.a;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @constructor */", "ns.Foo = function() {};", "ns.Foo.prototype.a = 'asdf';", "ns.Foo.prototype.a = 123;", "function f() {", " var /** string */ s = ns.Foo.prototype.a;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testDontWarnForBreakAfterReturn() { typeCheck(LINE_JOINER.join( "/**", " * @param {number} value", " * @return {number}", " */", "function check(value) {", " switch (value) {", " case 2:", " return 2;", " break;", " case 4:", " return 4;", " break;", " default:", " return 42;", " }", "}", "check(12);")); typeCheck(LINE_JOINER.join( "/**", " * @param {number} value", " * @return {number}", " */", "function check(value) {", " switch (value) {", " case 2:", " return 2;", " break;", " case 4:", " break;", " default:", " return 42;", " }", "}", "check(12);"), NewTypeInference.MISSING_RETURN_STATEMENT); } public void testDontSpecializeLooseObjectToLiteralType() { typeCheck(LINE_JOINER.join( "/**", " * @param {T} x", " * @param {T} y", " * @return {T}", " * @template T", " */", "function f(x, y) { return y; }", "function g(x) {", " x.myprop = 123;", // WAI: we drop the loose type during instantiation " return f({a: 123}, x).myprop;", "}"), NewTypeInference.INEXISTENT_PROPERTY); } public void testGoogReflectObject() { typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */", "function Foo() {}", "/** @type {number} */", "Foo.prototype.myprop;", "goog.reflect.object(Foo, {myprop: 'asdf'});", "var /** string */ s = (new Foo).myprop;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */", "function Foo() {}", "goog.reflect.object(Foo, 123);"), ClosureCodingConvention.OBJECTLIT_EXPECTED); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "function f() {}", "goog.reflect.object(f, {myprop: 123});"), NewTypeInference.REFLECT_CONSTRUCTOR_EXPECTED); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */", "function Foo() {}", "var /** !Object */ n = goog.reflect.object(Foo, {myprop: 'asdf'});")); typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */", "function Foo() {}", "var /** null */ n = goog.reflect.object(Foo, {myprop: 'asdf'});"), NewTypeInference.MISTYPED_ASSIGN_RHS); // Test that the object literal itself is analyzed typeCheck(LINE_JOINER.join( CLOSURE_BASE, "/** @constructor */", "function Foo() {}", "goog.reflect.object(Foo, {myprop: 1 - 'asdf'});"), NewTypeInference.INVALID_OPERAND_TYPE); } public void testDontCrashWhenGettingDeclaredTypeFromCallee() { typeCheck(LINE_JOINER.join( "function f(/** number */ x) {}", "f(123, function(x) { return x + 1; });"), NewTypeInference.WRONG_ARGUMENT_COUNT); } public void testDontCrashWithDuplicateObjectDefinition() { typeCheckCustomExterns(LINE_JOINER.join( "/** @constructor */", "function Object() {}", "/** @constructor */", "function Object() {}", "/** @constructor */", "function Function() {}"), ""); } public void testDontCrashOnWeirdPrototypePropertyDeclaration() { typeCheck(LINE_JOINER.join( "function f(arr, i) {", " arr[i].prototype.bar = function(asdf) { return asdf; };", "}")); } // TODO(dimvar): we skip the backwards analysis of g (and thus the creation of deferred checks) // when the return type of a function is declared (see NTIScope#hasUndeclaredFormalsOrOuters). // Is it worth having a more accurate test for determining whether a DeclaredFunctionType is // missing information, to catch cases like this one? // It should be uncommon in practice to declare the return and not declare the parameters. public void testMissedWarningDueToNoDeferredCheck() { typeCheck(LINE_JOINER.join( "/** @return {number} */", "function f(x) {", " var /** string */ s = x;", " return 123;", "}", "function g() {", " f(123);", // missed warning "}")); typeCheck(LINE_JOINER.join( "/** @return {number} */", "function f(x) {", " var /** string */ s = x;", " return 123;", "}", "f(123);"), NewTypeInference.INVALID_ARGUMENT_TYPE); } public void testDontWarnForFirstClassInterfaceConstructors() { typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "var Bar = Foo;", "var x = new Bar;"), NewTypeInference.NOT_A_CONSTRUCTOR); typeCheck(LINE_JOINER.join( "/** @const */", "var ns = {};", "/** @interface */", "ns.Foo = function() {};", "var x = new ns.Foo;"), NewTypeInference.NOT_A_CONSTRUCTOR); typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "/** @constructor @implements {Foo} */", "function Bar() {}", "function f(/** function(new: Foo) */ ctor) {", " var x = new ctor;", "}", "f(Bar);")); typeCheck(LINE_JOINER.join( "/** @interface */", "function Foo() {}", "function f(/** { ctor: function(new: Foo) } */ ns) {", " var x = new ns.ctor;", "}")); } public void testDontTurnKnownObjectIntoLooseBecauseOfBottomSpecialization() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Bar(x) {}", "/** @constructor */", "function Foo(x) {", " /** @type {boolean} */", " this.mybool = false;", " if (this.mybool) {", " this.prop1 = 123;", " }", " var /** !Bar */ b = this;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f() {", " var /** number */ n;", " var obj = new Foo;", " obj.mybool = false;", " if (obj.mybool) {", " n = obj;", " }", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); // this.prop becomes non-null in the prototype method, and NTI doesn't see it. // So, at the IF test in the constructor, it thinks the property is null, and the truthy // test is always false, so the specialization goes to bottom. typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "/** @constructor */", "function Bar() {", " /** @type {?Foo} */", " this.prop = null;", " this.init();", " if (this.prop) {", " var /** !Foo */ x = this.prop;", " }", "}", "Bar.prototype.init = function() {", " this.prop = new Foo;", "}")); } public void testAllowConstructorWithReturnToBeTypedAsFunction() { typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @param {number} x", " * @return {number}", " */", "function Foo(x) { return x; }", "function f(/** function(number):number */ x) {", " x(123);", "}", "f(Foo);")); } public void testHandleClassGenericsWhenUsingCallApply() { typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() {}", "/** @return {!Foo<T>} */", "Foo.prototype.method = function() { return this; }", "function f(/** !Foo<number> */ x) {", " var /** !Foo<number> */ y = Foo.prototype.method.call(x);", "}")); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() {}", "/** @return {!Foo<T>} */", "Foo.prototype.method = function() { return this; }", "function f(/** !Foo<number> */ x) {", " var /** !Foo<number> */ y = Foo.prototype.method.apply(x);", "}")); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() {}", "/** @return {!Foo<T>} */", "Foo.prototype.method = function() { return this; }", "function f(/** !Foo<number> */ x) {", " var /** !Foo<string> */ y = Foo.prototype.method.call(x);", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() {}", "/** @return {!Foo<T>} */", "Foo.prototype.method = function() { return this; }", "function f(/** !Foo<number> */ x) {", " var /** !Foo<string> */ y = Foo.prototype.method.apply(x);", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() {}", "/**", " * @template U", " * @param {U} x", " * @return {U}", " */", "Foo.prototype.f = function(x) { return x; }", "var /** number */ n = Foo.prototype.f.call(new Foo, 'asdf');"), NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testInferTypeOfUndeclaredConstructorProperties() { typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prop = 1;", "function f() {", " var /** string */ s = Foo.prop;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prop = 1;", "Foo.prop = true;")); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prop = 'asdf';", "function f() {", " var /** string */ s = Foo.prop;", "}", "Foo.prop = 1;"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prop = 'asdf';", "function f() {", " var /** string */ s = Foo.prop;", "}", "Foo.prop = globalVar;")); } public void testAvoidSpuriousWarningOnBooleanNamespaceProperty() { typeCheck(LINE_JOINER.join( "/** @return {{valid: boolean}} */", "function f() {", " /** @const */", " var ns = {};", " if (1<2) {", " ns.valid = true;", " } else {", " ns.valid = false;", " }", // We used to infer incorrectly that .valid is an optional property here. // Test that it doesn't happen again. " return ns;", "}")); } // This style of declarations is not used in ES5; it's transpiled from ES6 classes. public void testInterfacePropertiesInConstructor() { typeCheck(LINE_JOINER.join( "/**", " * @struct", " * @record", " */", "var Foo = function() {", " /** @type {number} */", " this.myprop;", "};", "function f(/** !Foo */ x) {", " var /** number */ y = x.myprop;", "}")); typeCheck(LINE_JOINER.join( "/**", " * @struct", " * @record", " */", "var Foo = function() {", " /** @type {number} */", " this.myprop;", "};", "function f(/** !Foo */ x) {", " var /** string */ y = x.myprop;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); typeCheck(LINE_JOINER.join( "/**", " * @record", " * @template T", " */", "var Foo = function() {", " /** @type {T} */", " this.myprop;", "};", "function f(/** !Foo<number> */ x) {", " var /** string */ y = x.myprop;", "}"), NewTypeInference.MISTYPED_ASSIGN_RHS); } }