From a636e0805feb75f73b3d7e4f36aecc0c7bd5a884 Mon Sep 17 00:00:00 2001 From: Tsuf Cohen <39455181+tsuf239@users.noreply.github.com> Date: Mon, 12 Aug 2024 22:00:33 +0300 Subject: [PATCH] fix(compiler): using `super()` in an inflight constructor compiles but fails at runtime (#7007) fixes: #6940 Compiled output of the `SuperClass` (slightly different class than the one in the example): image Test results: image ## Checklist - [ ] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted) - [ ] Description explains motivation and solution - [ ] Tests added (always) - [ ] Docs updated (only required for features) - [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing *By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*. --- .../tests/valid/super_inflight_class.test.w | 28 +++ libs/wingc/src/jsify.rs | 34 +++- .../inflight_init.test.w_compile_tf-aws.md | 1 + ...er_inflight_class.test.w_compile_tf-aws.md | 175 ++++++++++++++++++ .../super_inflight_class.test.w_test_sim.md | 14 ++ 5 files changed, 243 insertions(+), 9 deletions(-) create mode 100644 examples/tests/valid/super_inflight_class.test.w create mode 100644 tools/hangar/__snapshots__/test_corpus/valid/super_inflight_class.test.w_compile_tf-aws.md create mode 100644 tools/hangar/__snapshots__/test_corpus/valid/super_inflight_class.test.w_test_sim.md diff --git a/examples/tests/valid/super_inflight_class.test.w b/examples/tests/valid/super_inflight_class.test.w new file mode 100644 index 00000000000..e1509909e4f --- /dev/null +++ b/examples/tests/valid/super_inflight_class.test.w @@ -0,0 +1,28 @@ +bring expect; + +class BaseClass { + pub inflight x: num; + inflight new() { + log("BaseClass.inflight new"); + this.x = 2; + } + + pub inflight add(a: num, b: num): num { + return a + b; + } +} + +class SuperClass extends BaseClass { + inflight new() { + super(); + this.x += 1; + log("SuperClass.inflight new"); + } +} + +let c = new SuperClass(); + +test "counter works" { + expect.equal(c.add(2, 3), 5); + expect.equal(c.x, 3); +} \ No newline at end of file diff --git a/libs/wingc/src/jsify.rs b/libs/wingc/src/jsify.rs index cf009abb4fd..6357e33a2f9 100644 --- a/libs/wingc/src/jsify.rs +++ b/libs/wingc/src/jsify.rs @@ -1027,15 +1027,31 @@ impl<'a> JSifier<'a> { }, StmtKind::SuperConstructor { arg_list } => { let args = self.jsify_arg_list(&arg_list, None, None, ctx); - match parent_class_phase(ctx) { - Phase::Inflight => code.line(new_code!( - &arg_list.span, - "await this.super_", - CLASS_INFLIGHT_INIT_NAME, - "?.(", - args, - ");" - )), + match ctx.visit_ctx.current_phase() { + // does class have any inflight constructor? + Phase::Inflight => { + // yes- and it's an inflight class + if parent_class_phase(ctx) == Phase::Inflight { + code.line(new_code!( + &arg_list.span, + "await this.", + SUPER_CLASS_INFLIGHT_INIT_NAME, + "?.(", + args, + ");" + )) + } else { + // yes- and it's a preflight class + code.line(new_code!( + &arg_list.span, + "await super.", + CLASS_INFLIGHT_INIT_NAME, + "?.(", + args, + ");" + )) + } + } Phase::Preflight => code.line(new_code!( &arg_list.span, format!("super({SCOPE_PARAM}, $id, "), diff --git a/tools/hangar/__snapshots__/test_corpus/valid/inflight_init.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/inflight_init.test.w_compile_tf-aws.md index 3b4f5c3e732..95e90f3f55a 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/inflight_init.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/inflight_init.test.w_compile_tf-aws.md @@ -105,6 +105,7 @@ module.exports = function({ $jsii_fixture_JsiiClass }) { constructor(x, y){ super(x); this.$inflight_init = async () => { + await super.$inflight_init?.(x); this.foo_str = String.raw({ raw: ["", " ", ""] }, y, x); this.foo_num = (await this.get_six()); } diff --git a/tools/hangar/__snapshots__/test_corpus/valid/super_inflight_class.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/super_inflight_class.test.w_compile_tf-aws.md new file mode 100644 index 00000000000..defdb9e55d5 --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/valid/super_inflight_class.test.w_compile_tf-aws.md @@ -0,0 +1,175 @@ +# [super_inflight_class.test.w](../../../../../examples/tests/valid/super_inflight_class.test.w) | compile | tf-aws + +## inflight.$Closure1-1.cjs +```cjs +"use strict"; +const $helpers = require("@winglang/sdk/lib/helpers"); +const $macros = require("@winglang/sdk/lib/macros"); +module.exports = function({ $c, $expect_Util }) { + class $Closure1 { + constructor($args) { + const { } = $args; + const $obj = (...args) => this.handle(...args); + Object.setPrototypeOf($obj, this); + return $obj; + } + async handle() { + (await $expect_Util.equal((await $c.add(2, 3)), 5)); + (await $expect_Util.equal($c.x, 3)); + } + } + return $Closure1; +} +//# sourceMappingURL=inflight.$Closure1-1.cjs.map +``` + +## inflight.BaseClass-1.cjs +```cjs +"use strict"; +const $helpers = require("@winglang/sdk/lib/helpers"); +const $macros = require("@winglang/sdk/lib/macros"); +module.exports = function({ }) { + class BaseClass { + async add(a, b) { + return (a + b); + } + async $inflight_init() { + console.log("BaseClass.inflight new"); + this.x = 2; + } + } + return BaseClass; +} +//# sourceMappingURL=inflight.BaseClass-1.cjs.map +``` + +## inflight.SuperClass-1.cjs +```cjs +"use strict"; +const $helpers = require("@winglang/sdk/lib/helpers"); +const $macros = require("@winglang/sdk/lib/macros"); +module.exports = function({ $BaseClass }) { + class SuperClass extends $BaseClass { + async $inflight_init() { + await super.$inflight_init?.(); + this.x += 1; + console.log("SuperClass.inflight new"); + } + } + return SuperClass; +} +//# sourceMappingURL=inflight.SuperClass-1.cjs.map +``` + +## main.tf.json +```json +{ + "//": { + "metadata": { + "backend": "local", + "stackName": "root" + }, + "outputs": {} + }, + "provider": { + "aws": [ + {} + ] + } +} +``` + +## preflight.cjs +```cjs +"use strict"; +const $stdlib = require('@winglang/sdk'); +const $macros = require("@winglang/sdk/lib/macros"); +const $platforms = ((s) => !s ? [] : s.split(';'))(process.env.WING_PLATFORMS); +const $outdir = process.env.WING_SYNTH_DIR ?? "."; +const $wing_is_test = process.env.WING_IS_TEST === "true"; +const std = $stdlib.std; +const $helpers = $stdlib.helpers; +const $extern = $helpers.createExternRequire(__dirname); +const $PlatformManager = new $stdlib.platform.PlatformManager({platformPaths: $platforms}); +class $Root extends $stdlib.std.Resource { + constructor($scope, $id) { + super($scope, $id); + $helpers.nodeof(this).root.$preflightTypesMap = { }; + let $preflightTypesMap = {}; + const expect = $stdlib.expect; + $helpers.nodeof(this).root.$preflightTypesMap = $preflightTypesMap; + class BaseClass extends $stdlib.std.Resource { + constructor($scope, $id, ) { + super($scope, $id); + } + static _toInflightType() { + return ` + require("${$helpers.normalPath(__dirname)}/inflight.BaseClass-1.cjs")({ + }) + `; + } + get _liftMap() { + return ({ + "add": [ + ], + "$inflight_init": [ + ], + "x": [ + ], + }); + } + } + class SuperClass extends BaseClass { + constructor($scope, $id, ) { + super($scope, $id); + } + static _toInflightType() { + return ` + require("${$helpers.normalPath(__dirname)}/inflight.SuperClass-1.cjs")({ + $BaseClass: ${$stdlib.core.liftObject(BaseClass)}, + }) + `; + } + get _liftMap() { + return $stdlib.core.mergeLiftDeps(super._liftMap, { + "$inflight_init": [ + ], + }); + } + } + class $Closure1 extends $stdlib.std.AutoIdResource { + _id = $stdlib.core.closureId(); + constructor($scope, $id, ) { + super($scope, $id); + $helpers.nodeof(this).hidden = true; + } + static _toInflightType() { + return ` + require("${$helpers.normalPath(__dirname)}/inflight.$Closure1-1.cjs")({ + $c: ${$stdlib.core.liftObject(c)}, + $expect_Util: ${$stdlib.core.liftObject($stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("@winglang/sdk.expect.Util") ?? expect.Util, "@winglang/sdk/expect", "Util"))}, + }) + `; + } + get _liftMap() { + return ({ + "handle": [ + [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("@winglang/sdk.expect.Util") ?? expect.Util, "@winglang/sdk/expect", "Util"), ["equal"]], + [c, [].concat(["add"], ["x"])], + ], + "$inflight_init": [ + [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("@winglang/sdk.expect.Util") ?? expect.Util, "@winglang/sdk/expect", "Util"), []], + [c, []], + ], + }); + } + } + const c = new SuperClass(this, "SuperClass"); + globalThis.$ClassFactory.new("@winglang/sdk.std.Test", std.Test, this, "test:counter works", new $Closure1(this, "$Closure1")); + } +} +const $APP = $PlatformManager.createApp({ outdir: $outdir, name: "super_inflight_class.test", rootConstruct: $Root, isTestEnvironment: $wing_is_test, entrypointDir: process.env['WING_SOURCE_DIR'], rootId: process.env['WING_ROOT_ID'] }); +$APP.synth(); +//# sourceMappingURL=preflight.cjs.map +``` + diff --git a/tools/hangar/__snapshots__/test_corpus/valid/super_inflight_class.test.w_test_sim.md b/tools/hangar/__snapshots__/test_corpus/valid/super_inflight_class.test.w_test_sim.md new file mode 100644 index 00000000000..7501ea8bb45 --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/valid/super_inflight_class.test.w_test_sim.md @@ -0,0 +1,14 @@ +# [super_inflight_class.test.w](../../../../../examples/tests/valid/super_inflight_class.test.w) | test | sim + +## stdout.log +```log +[INFO] counter works | BaseClass.inflight new +[INFO] counter works | SuperClass.inflight new +pass ─ super_inflight_class.test.wsim » root/Default/test:counter works + +Tests 1 passed (1) +Snapshots 1 skipped +Test Files 1 passed (1) +Duration +``` +