From 07abeac59dbcff036d0bfc54e11304d06bb8dbec Mon Sep 17 00:00:00 2001 From: bradfordcsmith Date: Fri, 27 Oct 2017 10:24:35 -0700 Subject: [PATCH] Enable hotswapping for 2 transpilation passes. These passes should always have been enabled for hot swapping. This fixes a bug that prevented async function transpilation from happening during dynamic debug loading. Also added definitions to the DEFAULT_EXTERNS used for testing and used them in hotswap tests. These are needed for transpiling advanced features like async functions. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=173687156 --- .../jscomp/TranspilationPasses.java | 8 +- .../jscomp/BaseReplaceScriptTestCase.java | 26 ++- .../javascript/jscomp/CompilerTestCase.java | 103 ++++++++++++ .../jscomp/NewTypeInferenceTestBase.java | 21 --- .../jscomp/SimpleReplaceScriptTest.java | 22 ++- .../javascript/jscomp/TypeCheckTest.java | 154 +++++++++++------- 6 files changed, 247 insertions(+), 87 deletions(-) diff --git a/src/com/google/javascript/jscomp/TranspilationPasses.java b/src/com/google/javascript/jscomp/TranspilationPasses.java index ed6667e9d02..4c0bc5c8a69 100644 --- a/src/com/google/javascript/jscomp/TranspilationPasses.java +++ b/src/com/google/javascript/jscomp/TranspilationPasses.java @@ -114,9 +114,9 @@ protected FeatureSet featureSet() { }; private static final PassFactory rewriteAsyncFunctions = - new PassFactory("rewriteAsyncFunctions", true) { + new HotSwapPassFactory("rewriteAsyncFunctions") { @Override - protected CompilerPass create(final AbstractCompiler compiler) { + protected HotSwapCompilerPass create(final AbstractCompiler compiler) { return new RewriteAsyncFunctions(compiler); } @@ -127,9 +127,9 @@ protected FeatureSet featureSet() { }; private static final PassFactory convertEs7ToEs6 = - new PassFactory("convertEs7ToEs6", true) { + new HotSwapPassFactory("convertEs7ToEs6") { @Override - protected CompilerPass create(final AbstractCompiler compiler) { + protected HotSwapCompilerPass create(final AbstractCompiler compiler) { return new Es7ToEs6Converter(compiler); } diff --git a/test/com/google/javascript/jscomp/BaseReplaceScriptTestCase.java b/test/com/google/javascript/jscomp/BaseReplaceScriptTestCase.java index db2213dd10c..38a0f3a45cc 100644 --- a/test/com/google/javascript/jscomp/BaseReplaceScriptTestCase.java +++ b/test/com/google/javascript/jscomp/BaseReplaceScriptTestCase.java @@ -36,9 +36,31 @@ public abstract class BaseReplaceScriptTestCase extends TestCase { "goog.require = function(x) {};", "goog.provide = function(x) {};"); - protected static final ImmutableList EXTERNS = + /** Externs used by most test cases and containing only a single definition. */ + protected static final ImmutableList EXTVAR_EXTERNS = ImmutableList.of(SourceFile.fromCode("externs", "var extVar = 3;")); + /** + * Default externs containing definitions needed for transpilation of async functions and other + * post-ES5 features. + */ + protected static final ImmutableList DEFAULT_EXTERNS = + ImmutableList.of(SourceFile.fromCode("default_externs", CompilerTestCase.DEFAULT_EXTERNS)); + + /** + * Test methods may set this variable to control the externs passed to the compiler. + * + *

Most test cases don't need externs at all or only need the one `extVar` variable defined in + * the EXTVAR_EXTERNS used here. + */ + protected ImmutableList testExterns = EXTVAR_EXTERNS; + + @Override + protected void setUp() throws Exception { + super.setUp(); + testExterns = EXTVAR_EXTERNS; + } + /** * In addition to the passed parameter adds a few options necessary options for * {@code replaceScript} and creates a {@code CompilerOptions}. @@ -145,7 +167,7 @@ protected Compiler runFullCompile( } Compiler compiler = new Compiler(); Compiler.setLoggingLevel(Level.INFO); - Result result = compiler.compile(EXTERNS, inputs, options); + Result result = compiler.compile(testExterns, inputs, options); if (expectedCompileErrors == 0) { assertThat(compiler.getErrors()).isEmpty(); assertThat(result.success).isTrue(); diff --git a/test/com/google/javascript/jscomp/CompilerTestCase.java b/test/com/google/javascript/jscomp/CompilerTestCase.java index 0a8e23c058c..a5f8229f96e 100644 --- a/test/com/google/javascript/jscomp/CompilerTestCase.java +++ b/test/com/google/javascript/jscomp/CompilerTestCase.java @@ -420,6 +420,109 @@ public abstract class CompilerTestCase extends TestCase { " * @override", " */", "Generator.prototype.next = function(opt_value) {};", + "/**", + " * @typedef {{then: ?}}", + " */", + "var Thenable;", + "/**", + " * @interface", + " * @template TYPE", + " */", + "function IThenable() {}", + "/**", + " * @param {?(function(TYPE):VALUE)=} opt_onFulfilled", + " * @param {?(function(*): *)=} opt_onRejected", + " * @return {RESULT}", + " * @template VALUE", + " * @template RESULT := type('IThenable',", + " * cond(isUnknown(VALUE), unknown(),", + " * mapunion(VALUE, (V) =>", + " * cond(isTemplatized(V) && sub(rawTypeOf(V), 'IThenable'),", + " * templateTypeOf(V, 0),", + " * cond(sub(V, 'Thenable'),", + " * unknown(),", + " * V)))))", + " * =:", + " */", + "IThenable.prototype.then = function(opt_onFulfilled, opt_onRejected) {};", + "/**", + " * @param {function(", + " * function((TYPE|IThenable|Thenable|null)=),", + " * function(*=))} resolver", + " * @constructor", + " * @implements {IThenable}", + " * @template TYPE", + " */", + "function Promise(resolver) {}", + "/**", + " * @param {VALUE=} opt_value", + " * @return {RESULT}", + " * @template VALUE", + " * @template RESULT := type('Promise',", + " * cond(isUnknown(VALUE), unknown(),", + " * mapunion(VALUE, (V) =>", + " * cond(isTemplatized(V) && sub(rawTypeOf(V), 'IThenable'),", + " * templateTypeOf(V, 0),", + " * cond(sub(V, 'Thenable'),", + " * unknown(),", + " * V)))))", + " * =:", + " */", + "Promise.resolve = function(opt_value) {};", + "/**", + " * @param {*=} opt_error", + " * @return {!Promise}", + " */", + "Promise.reject = function(opt_error) {};", + "/**", + " * @param {!Iterable} iterable", + " * @return {!Promise>}", + " * @template VALUE", + " * @template RESULT := mapunion(VALUE, (V) =>", + " * cond(isUnknown(V),", + " * unknown(),", + " * cond(isTemplatized(V) && sub(rawTypeOf(V), 'IThenable'),", + " * templateTypeOf(V, 0),", + " * cond(sub(V, 'Thenable'), unknown(), V))))", + " * =:", + " */", + "Promise.all = function(iterable) {};", + "/**", + " * @param {!Iterable} iterable", + " * @return {!Promise}", + " * @template VALUE", + " * @template RESULT := mapunion(VALUE, (V) =>", + " * cond(isUnknown(V),", + " * unknown(),", + " * cond(isTemplatized(V) && sub(rawTypeOf(V), 'IThenable'),", + " * templateTypeOf(V, 0),", + " * cond(sub(V, 'Thenable'), unknown(), V))))", + " * =:", + " */", + "Promise.race = function(iterable) {};", + "/**", + " * @param {?(function(this:void, TYPE):VALUE)=} opt_onFulfilled", + " * @param {?(function(this:void, *): *)=} opt_onRejected", + " * @return {RESULT}", + " * @template VALUE", + " * @template RESULT := type('Promise',", + " * cond(isUnknown(VALUE), unknown(),", + " * mapunion(VALUE, (V) =>", + " * cond(isTemplatized(V) && sub(rawTypeOf(V), 'IThenable'),", + " * templateTypeOf(V, 0),", + " * cond(sub(V, 'Thenable'),", + " * unknown(),", + " * V)))))", + " * =:", + " * @override", + " */", + "Promise.prototype.then = function(opt_onFulfilled, opt_onRejected) {};", + "/**", + " * @param {function(*): RESULT} onRejected", + " * @return {!Promise}", + " * @template RESULT", + " */", + "Promise.prototype.catch = function(onRejected) {};", ACTIVE_X_OBJECT_DEF); /** diff --git a/test/com/google/javascript/jscomp/NewTypeInferenceTestBase.java b/test/com/google/javascript/jscomp/NewTypeInferenceTestBase.java index b2b0a090ffc..6616ad4ceda 100644 --- a/test/com/google/javascript/jscomp/NewTypeInferenceTestBase.java +++ b/test/com/google/javascript/jscomp/NewTypeInferenceTestBase.java @@ -135,27 +135,6 @@ boolean checkTranspiled() { " * @return {!Array.}", " */", "Array.prototype.concat = function(var_args) {};", - "/** @interface */", - "function IThenable () {}", - "IThenable.prototype.then = function(onFulfilled) {};", - "/**", - " * @template T", - " * @constructor", - " * @implements {IThenable}", - " */", - "function Promise(resolver) {};", - "/**", - " * @param {VALUE} value", - " * @return {!Promise}", - " * @template VALUE", - " */", - "Promise.resolve = function(value) {};", - "/**", - " * @template RESULT", - " * @param {function(): RESULT} onFulfilled", - " * @return {RESULT}", - " */", - "Promise.prototype.then = function(onFulfilled) {};", "/**", " * @constructor", " * @param {*=} opt_message", diff --git a/test/com/google/javascript/jscomp/SimpleReplaceScriptTest.java b/test/com/google/javascript/jscomp/SimpleReplaceScriptTest.java index e9ca7597b19..d3f68375201 100644 --- a/test/com/google/javascript/jscomp/SimpleReplaceScriptTest.java +++ b/test/com/google/javascript/jscomp/SimpleReplaceScriptTest.java @@ -58,7 +58,7 @@ public void testInferWithModules() { List inputs = ImmutableList.of( SourceFile.fromCode("in", "")); - Result result = compiler.compile(EXTERNS, inputs, options); + Result result = compiler.compile(EXTVAR_EXTERNS, inputs, options); assertTrue(result.success); CompilerInput oldInput = compiler.getInput(new InputId("in")); @@ -83,7 +83,7 @@ public void testreplaceScript() { + "temp(10);\n"; List inputs = ImmutableList.of( SourceFile.fromCode("in", source)); - Result result = compiler.compile(EXTERNS, inputs, options); + Result result = compiler.compile(EXTVAR_EXTERNS, inputs, options); assertTrue(result.success); // Now try to re-infer with a modified version of source @@ -257,7 +257,7 @@ public void testParseErrorDoesntCrashCompilation() { List inputs = ImmutableList.of( SourceFile.fromCode("in", "bad!()")); try { - compiler.compile(EXTERNS, inputs, options); + compiler.compile(EXTVAR_EXTERNS, inputs, options); } catch (RuntimeException e) { fail("replaceScript threw a RuntimeException on a parse error."); } @@ -995,6 +995,22 @@ public void testNoErrorOnGoogProvide() { assertThat(result.warnings).isEmpty(); } + /** Check async functionality on replaceScript */ + public void testAsyncReplaceScript() { + CompilerOptions options = getOptions(); + options.setLanguageIn(CompilerOptions.LanguageMode.ECMASCRIPT_2017); + options.setLanguageOut(CompilerOptions.LanguageMode.ECMASCRIPT5_STRICT); + // async functions require iterables and Symbols from the default externs + testExterns = DEFAULT_EXTERNS; + String src0 = "async function foo() {}"; + Result result = + this.runReplaceScript(options, ImmutableList.of(src0), 0, 0, src0, 0, false).getResult(); + assertTrue(result.success); + + assertThat(result.errors).isEmpty(); + assertThat(result.warnings).isEmpty(); + } + public void testAddSimpleScript() { CompilerOptions options = getOptions(); options.setClosurePass(false); diff --git a/test/com/google/javascript/jscomp/TypeCheckTest.java b/test/com/google/javascript/jscomp/TypeCheckTest.java index f9325e73f51..6fe44e9fbd8 100644 --- a/test/com/google/javascript/jscomp/TypeCheckTest.java +++ b/test/com/google/javascript/jscomp/TypeCheckTest.java @@ -2183,10 +2183,13 @@ public void testFunctionInference20() throws Exception { "function(this:Date): ?"); } - public void testFunctionInference21() throws Exception { + public void testFunctionInference21a() throws Exception { testTypes( "var f = function() { throw 'x' };" + "/** @return {boolean} */ var g = f;"); + } + + public void testFunctionInference21b() throws Exception { testFunctionType( "var f = function() { throw 'x' };", "f", @@ -7523,13 +7526,19 @@ public void testBug592170() throws Exception { } /** - * Tests that undefined can be compared shallowly to a value of type - * (number,undefined) regardless of the side on which the undefined - * value is. + * Tests that undefined can be compared shallowly to a value of type (number,undefined) regardless + * of the side on which the undefined value is. */ - public void testBug901455() throws Exception { + public void testBug901455a() throws Exception { testTypes("/** @return {(number|undefined)} */ function a() { return 3; }" + "var b = undefined === a()"); + } + + /** + * Tests that undefined can be compared shallowly to a value of type (number,undefined) regardless + * of the side on which the undefined value is. + */ + public void testBug901455b() throws Exception { testTypes("/** @return {(number|undefined)} */ function a() { return 3; }" + "var b = a() === undefined"); } @@ -7553,15 +7562,21 @@ public void testBug908625() throws Exception { } /** - * Tests that assigning two untyped functions to a variable whose type is - * inferred and calling this variable is legal. + * Tests that assigning two untyped functions to a variable whose type is inferred and calling + * this variable is legal. */ - public void testBug911118() throws Exception { + public void testBug911118a() throws Exception { // verifying the type assigned to function expressions assigned variables TypedScope s = parseAndTypeCheckWithScope("var a = function(){};").scope; JSType type = s.getVar("a").getType(); assertEquals("function(): undefined", type.toString()); + } + /** + * Tests that assigning two untyped functions to a variable whose type is inferred and calling + * this variable is legal. + */ + public void testBug911118b() throws Exception { // verifying the bug example testTypes("function nullFunction() {};" + "var foo = nullFunction;" + @@ -8986,9 +9001,6 @@ public void testCast17a() throws Exception { // Mostly verifying that rhino actually understands these JsDocs. testTypes("/** @constructor */ function Foo() {} \n" + "/** @type {Foo} */ var x = /** @type {Foo} */ (y)"); - - testTypes("/** @constructor */ function Foo() {} \n" + - "/** @type {Foo} */ var x = /** @type {Foo} */ (y)"); } public void testCast17b() throws Exception { @@ -9151,25 +9163,40 @@ public void testCast32() throws Exception { "var y = /** @type {null|{length:number}} */(x);"); } - public void testCast33() throws Exception { + public void testCast33a() throws Exception { // null and void should be assignable to any type that accepts one or the // other or both. testTypes( "/** @constructor */ function C() {}\n" + "/** @type {null|undefined} */ var x ;\n" + "var y = /** @type {string?|undefined} */(x);"); + } + + public void testCast33b() throws Exception { + // null and void should be assignable to any type that accepts one or the + // other or both. testTypes( - "/** @constructor */ function C() {}\n" + - "/** @type {null|undefined} */ var x ;\n" + - "var y = /** @type {string|undefined} */(x);"); + "/** @constructor */ function C() {}\n" + + "/** @type {null|undefined} */ var x ;\n" + + "var y = /** @type {string|undefined} */(x);"); + } + + public void testCast33c() throws Exception { + // null and void should be assignable to any type that accepts one or the + // other or both. testTypes( - "/** @constructor */ function C() {}\n" + - "/** @type {null|undefined} */ var x ;\n" + - "var y = /** @type {string?} */(x);"); + "/** @constructor */ function C() {}\n" + + "/** @type {null|undefined} */ var x ;\n" + + "var y = /** @type {string?} */(x);"); + } + + public void testCast33d() throws Exception { + // null and void should be assignable to any type that accepts one or the + // other or both. testTypes( - "/** @constructor */ function C() {}\n" + - "/** @type {null|undefined} */ var x ;\n" + - "var y = /** @type {null} */(x);"); + "/** @constructor */ function C() {}\n" + + "/** @type {null|undefined} */ var x ;\n" + + "var y = /** @type {null} */(x);"); } public void testCast34a() throws Exception { @@ -11669,12 +11696,15 @@ public void testMissingProperty27() throws Exception { "}", null); } - public void testMissingProperty28() throws Exception { + public void testMissingProperty28a() throws Exception { testTypes( "function f(obj) {" + " /** @type {*} */ obj.foo;" + " return obj.foo;" + "}"); + } + + public void testMissingProperty28b() throws Exception { testTypes( "function f(obj) {" + " /** @type {*} */ obj.foo;" + @@ -13431,7 +13461,7 @@ public void testExtendedInterfacePropertiesCompatibilityNoError() throws Excepti + "function Int2() {};"); } - public void testGenerics1() throws Exception { + public void testGenerics1a() throws Exception { String fnDecl = "/** \n" + " * @param {T} x \n" + " * @param {function(T):T} y \n" + @@ -13445,24 +13475,23 @@ public void testGenerics1() throws Exception { "var out;" + "/** @type {string} */" + "var result = f('hi', function(x){ out = x; return x; });"); + } - testTypes( - fnDecl + - "/** @type {string} */" + - "var out;" + - "var result = f(0, function(x){ out = x; return x; });", - "assignment\n" + - "found : number\n" + - "required: string"); + public void testGenerics1b() throws Exception { + String fnDecl = + "/** \n" + + " * @param {T} x \n" + + " * @param {function(T):T} y \n" + + " * @template T\n" + + " */ \n" + + "function f(x,y) { return y(x); }\n"; testTypes( - fnDecl + - "var out;" + - "/** @type {string} */" + - "var result = f(0, function(x){ out = x; return x; });", - "assignment\n" + - "found : number\n" + - "required: string"); + fnDecl + + "/** @type {string} */" + + "var out;" + + "var result = f(0, function(x){ out = x; return x; });", + "assignment\n" + "found : number\n" + "required: string"); } public void testFilter0() throws Exception { @@ -14004,7 +14033,7 @@ public void testNonexistentPropertyAccessStructSubtype2() throws Exception { "Property x never defined on Foo.prototype", false); } - public void testIssue1024() throws Exception { + public void testIssue1024a() throws Exception { testTypes( "/** @param {Object} a */\n" + "function f(a) {\n" + @@ -14016,23 +14045,26 @@ public void testIssue1024() throws Exception { "function g(b) {\n" + " return b.prototype\n" + "}\n"); - /* TODO(blickly): Make this warning go away. - * This is old behavior, but it doesn't make sense to warn about since - * both assignments are inferred. - */ - testTypes( - "/** @param {Object} a */\n" + - "function f(a) {\n" + - " a.prototype = {foo:3};\n" + - "}\n" + - "/** @param {Object} b\n" + - " */\n" + - "function g(b) {\n" + - " b.prototype = function(){};\n" + - "}\n", - "assignment to property prototype of Object\n" + - "found : {foo: number}\n" + - "required: function(): undefined"); + } + + public void testIssue1024b() throws Exception { + /* TODO(blickly): Make this warning go away. + * This is old behavior, but it doesn't make sense to warn about since + * both assignments are inferred. + */ + testTypes( + "/** @param {Object} a */\n" + + "function f(a) {\n" + + " a.prototype = {foo:3};\n" + + "}\n" + + "/** @param {Object} b\n" + + " */\n" + + "function g(b) {\n" + + " b.prototype = function(){};\n" + + "}\n", + "assignment to property prototype of Object\n" + + "found : {foo: number}\n" + + "required: function(): undefined"); } public void testBug12722936() throws Exception { @@ -17609,26 +17641,32 @@ public void testDuplicateVariableDefinition8_7() throws Exception { + "original definition at [testcode]:5 with type (null|rec)"); } - public void testModuloNullUndefThatWorkedWithoutSpecialSubtypingRules() throws Exception { + public void testModuloNullUndefThatWorkedWithoutSpecialSubtypingRules1() throws Exception { testTypes(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f(/** function(?Foo, !Foo) */ x) {", " return /** @type {function(!Foo, ?Foo)} */ (x);", "}")); + } + public void testModuloNullUndefThatWorkedWithoutSpecialSubtypingRules2() throws Exception { testTypes(LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "function f(/** !Array */ to, /** !Array */ from) {", " to = from;", "}")); + } + public void testModuloNullUndefThatWorkedWithoutSpecialSubtypingRules3() throws Exception { testTypes(LINE_JOINER.join( "function f(/** ?Object */ x) {", " return {} instanceof x;", "}")); + } + public void testModuloNullUndefThatWorkedWithoutSpecialSubtypingRules4() throws Exception { testTypes(LINE_JOINER.join( "function f(/** ?Function */ x) {", " return x();", @@ -17929,6 +17967,8 @@ private TypeCheckResult parseAndTypeCheckWithScope(String js) { } private TypeCheckResult parseAndTypeCheckWithScope(String externs, String js) { + registry.clearNamedTypes(); + registry.clearTemplateTypeNames(); compiler.init( ImmutableList.of(SourceFile.fromCode("[externs]", externs)), ImmutableList.of(SourceFile.fromCode("[testcode]", js)),