diff --git a/examples/tests/invalid/capture_mutables.w b/examples/tests/invalid/capture_mutables.w deleted file mode 100644 index 5ccd12c88f9..00000000000 --- a/examples/tests/invalid/capture_mutables.w +++ /dev/null @@ -1,16 +0,0 @@ -let a = MutArray["hello"]; -let s = MutSet{12}; -let m = MutMap{"hello": true}; - -let aCloned = (Array["hello"]).copyMut(); - -let i = inflight () => { - assert(a.length == 1); - // ^ Cannot reference 'a' of type 'MutArray' from an inflight context - assert(s.size == 1); - // ^ Cannot reference 's' of type 'MutSet' from an inflight context - assert(m.size() == 1); - // ^ Cannot reference 'm' of type 'MutMap' from an inflight context - assert(aCloned.length == 1); - // ^ Cannot reference 'aCloned' of type 'MutArray' from an inflight context -}; \ No newline at end of file diff --git a/examples/tests/invalid/capture_reassignable.w b/examples/tests/invalid/capture_reassignable.w deleted file mode 100644 index 8cc5b47ba6d..00000000000 --- a/examples/tests/invalid/capture_reassignable.w +++ /dev/null @@ -1,8 +0,0 @@ -bring cloud; - -let var x = 5; - -let handler = inflight (m: str): str => { - log("x: ${x}"); - // ^ error: cannot capture reassignable variable "x" -}; diff --git a/examples/tests/valid/capture_mutables.w b/examples/tests/valid/capture_mutables.w new file mode 100644 index 00000000000..e5848a609f1 --- /dev/null +++ b/examples/tests/valid/capture_mutables.w @@ -0,0 +1,16 @@ +let a = MutArray["hello"]; +let s = MutSet{12}; +let m = MutMap{"hello": true}; + +let aCloned = (Array["hello"]).copyMut(); + +let handler = inflight () => { + assert(a.length == 1); + assert(s.size == 1); + assert(m.size() == 1); + assert(aCloned.length == 1); +}; + +test "main" { + handler(); +} \ No newline at end of file diff --git a/examples/tests/valid/capture_reassigable_class_field.w b/examples/tests/valid/capture_reassigable_class_field.w new file mode 100644 index 00000000000..f81ad31889c --- /dev/null +++ b/examples/tests/valid/capture_reassigable_class_field.w @@ -0,0 +1,49 @@ +bring cloud; +bring util; + +class KeyValueStore { + bucket: cloud.Bucket; + var onUpdateCallback : inflight (str): void; + init() { + this.bucket = new cloud.Bucket(); + this.onUpdateCallback = inflight (k: str) => {}; + } + + onUpdate(fn: inflight (str):void){ + this.onUpdateCallback = fn; + } + + inflight get(key: str): Json { + this.onUpdateCallback(key); + return this.bucket.getJson(key); + } + inflight set(key: str, value: Json): void { + this.bucket.putJson(key, value); + } +} + +let kv = new KeyValueStore(); +let counter = new cloud.Counter() as "sasa"; +kv.onUpdate(inflight (key: str): void => { + counter.inc(1, key); +}); + +test "main" { + kv.set("k", Json { + value: "v" + }); + kv.set("k2", Json { + value: "v" + }); + kv.get("k"); + kv.get("k"); + kv.get("k2"); + + assert(util.waitUntil((): bool => { + return counter.peek("k") == 2; + })); + + assert(util.waitUntil((): bool => { + return counter.peek("k2") == 1; + })); +} diff --git a/examples/tests/valid/capture_reassignable.w b/examples/tests/valid/capture_reassignable.w new file mode 100644 index 00000000000..6c07ba36847 --- /dev/null +++ b/examples/tests/valid/capture_reassignable.w @@ -0,0 +1,12 @@ +bring cloud; + +let var x = 5; + +let handler = inflight (): void => { + assert(x == 5); + +}; + +test "main" { + handler(); +} diff --git a/libs/wingc/src/jsify.rs b/libs/wingc/src/jsify.rs index 7734255eab6..ae2ec8792c4 100644 --- a/libs/wingc/src/jsify.rs +++ b/libs/wingc/src/jsify.rs @@ -1359,7 +1359,7 @@ impl<'a> JSifier<'a> { .filter(|(_, kind, _)| { let var = kind.as_variable().unwrap(); // We capture preflight non-reassignable fields - var.phase != Phase::Inflight && !var.reassignable && var.type_.is_capturable() + var.phase != Phase::Inflight && var.type_.is_capturable() }) .map(|(name, ..)| name) .collect_vec() @@ -1541,19 +1541,8 @@ impl<'ast> Visit<'ast> for FieldReferenceVisitor<'ast> { } // now we need to verify that the component can be captured. - // (1) non-reassignable // (2) capturable type (immutable/resource). - // if the variable is reassignable, bail out - if variable.reassignable { - report_diagnostic(Diagnostic { - message: format!("Cannot capture reassignable field '{curr}'"), - span: Some(curr.span.clone()), - }); - - return; - } - // if this type is not capturable, bail out if !variable.type_.is_capturable() { report_diagnostic(Diagnostic { diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index 3718f9cc818..33e415f2d65 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -894,10 +894,10 @@ impl TypeRef { Type::Anything => false, Type::Unresolved => false, Type::Void => false, - Type::MutJson => false, - Type::MutArray(_) => false, - Type::MutMap(_) => false, - Type::MutSet(_) => false, + Type::MutJson => true, + Type::MutArray(v) => v.is_capturable(), + Type::MutMap(v) => v.is_capturable(), + Type::MutSet(v) => v.is_capturable(), Type::Function(sig) => sig.phase == Phase::Inflight, // only preflight classes can be captured diff --git a/tools/hangar/__snapshots__/invalid.ts.snap b/tools/hangar/__snapshots__/invalid.ts.snap index 4a582b4ce49..885ae644fa4 100644 --- a/tools/hangar/__snapshots__/invalid.ts.snap +++ b/tools/hangar/__snapshots__/invalid.ts.snap @@ -116,63 +116,6 @@ error: Cannot find module \\"foobar\\" in source directory: Unable to load \\"fo -Tests 1 failed (1) -Duration -" -`; - -exports[`capture_mutables.w 1`] = ` -"error: Cannot capture field 'a' with non-capturable type 'MutArray' - --> ../../../examples/tests/invalid/capture_mutables.w:8:10 - | -8 | assert(a.length == 1); - | ^ Cannot capture field 'a' with non-capturable type 'MutArray' - - -error: Cannot capture field 's' with non-capturable type 'MutSet' - --> ../../../examples/tests/invalid/capture_mutables.w:10:10 - | -10 | assert(s.size == 1); - | ^ Cannot capture field 's' with non-capturable type 'MutSet' - - -error: Cannot capture field 'm' with non-capturable type 'MutMap' - --> ../../../examples/tests/invalid/capture_mutables.w:12:10 - | -12 | assert(m.size() == 1); - | ^ Cannot capture field 'm' with non-capturable type 'MutMap' - - -error: Cannot capture field 'aCloned' with non-capturable type 'MutArray' - --> ../../../examples/tests/invalid/capture_mutables.w:14:10 - | -14 | assert(aCloned.length == 1); - | ^^^^^^^ Cannot capture field 'aCloned' with non-capturable type 'MutArray' - - - - - - - -Tests 1 failed (1) -Duration -" -`; - -exports[`capture_reassignable.w 1`] = ` -"error: Cannot capture reassignable field 'x' - --> ../../../examples/tests/invalid/capture_reassignable.w:6:15 - | -6 | log(\\"x: \${x}\\"); - | ^ Cannot capture reassignable field 'x' - - - - - - - Tests 1 failed (1) Duration " @@ -733,26 +676,13 @@ Duration `; exports[`inflight_class_capture_mutable.w 1`] = ` -"error: Cannot capture field 'x' with non-capturable type 'MutArray' - --> ../../../examples/tests/invalid/inflight_class_capture_mutable.w:5:9 - | -5 | log(x.at(0)); - | ^ Cannot capture field 'x' with non-capturable type 'MutArray' - - -error: Cannot capture reassignable field 'foo' - --> ../../../examples/tests/invalid/inflight_class_capture_mutable.w:14:12 - | -14 | return foo; - | ^^^ Cannot capture reassignable field 'foo' - - +"pass ─ inflight_class_capture_mutable.wsim (no tests) -Tests 1 failed (1) +Tests 1 passed (1) Duration " `; @@ -1503,21 +1433,7 @@ Duration `; exports[`resource_captures.w 1`] = ` -"error: Cannot capture reassignable field 'reassignable' - --> ../../../examples/tests/invalid/resource_captures.w:18:17 - | -18 | log(\\"\${this.reassignable}\\"); - | ^^^^^^^^^^^^ Cannot capture reassignable field 'reassignable' - - -error: Cannot capture field 'mutArray' with non-capturable type 'MutArray' - --> ../../../examples/tests/invalid/resource_captures.w:20:14 - | -20 | log(this.mutArray.at(0)); - | ^^^^^^^^ Cannot capture field 'mutArray' with non-capturable type 'MutArray' - - -error: Unable to qualify which operations are performed on 'this.bucket' of type 'Bucket'. This is not supported yet. +"error: Unable to qualify which operations are performed on 'this.bucket' of type 'Bucket'. This is not supported yet. --> ../../../examples/tests/invalid/resource_captures.w:23:13 | 23 | let b = this.bucket; diff --git a/tools/hangar/__snapshots__/test_corpus/valid/capture_mutables.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/capture_mutables.w_compile_tf-aws.md new file mode 100644 index 00000000000..2c52fdcda36 --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/valid/capture_mutables.w_compile_tf-aws.md @@ -0,0 +1,287 @@ +# [capture_mutables.w](../../../../../examples/tests/valid/capture_mutables.w) | compile | tf-aws + +## inflight.$Closure1.js +```js +module.exports = function({ a, s, m, aCloned }) { + class $Closure1 { + constructor({ }) { + const $obj = (...args) => this.handle(...args); + Object.setPrototypeOf($obj, this); + return $obj; + } + async $inflight_init() { + } + async handle() { + {((cond) => {if (!cond) throw new Error(`assertion failed: '(a.length === 1)'`)})((a.length === 1))}; + {((cond) => {if (!cond) throw new Error(`assertion failed: '(s.size === 1)'`)})((s.size === 1))}; + {((cond) => {if (!cond) throw new Error(`assertion failed: '(Object.keys(m).length === 1)'`)})((Object.keys(m).length === 1))}; + {((cond) => {if (!cond) throw new Error(`assertion failed: '(aCloned.length === 1)'`)})((aCloned.length === 1))}; + } + } + return $Closure1; +} + +``` + +## inflight.$Closure2.js +```js +module.exports = function({ handler }) { + class $Closure2 { + constructor({ }) { + const $obj = (...args) => this.handle(...args); + Object.setPrototypeOf($obj, this); + return $obj; + } + async $inflight_init() { + } + async handle() { + (await handler()); + } + } + return $Closure2; +} + +``` + +## main.tf.json +```json +{ + "//": { + "metadata": { + "backend": "local", + "stackName": "root", + "version": "0.15.2" + }, + "outputs": { + "root": { + "Default": { + "cloud.TestRunner": { + "TestFunctionArns": "WING_TEST_RUNNER_FUNCTION_ARNS" + } + } + } + } + }, + "output": { + "WING_TEST_RUNNER_FUNCTION_ARNS": { + "value": "[[\"root/Default/Default/test:main\",\"${aws_lambda_function.root_testmain_Handler_4ADAC335.arn}\"]]" + } + }, + "provider": { + "aws": [ + {} + ] + }, + "resource": { + "aws_iam_role": { + "root_testmain_Handler_IamRole_0300CAA5": { + "//": { + "metadata": { + "path": "root/Default/Default/test:main/Handler/IamRole", + "uniqueId": "root_testmain_Handler_IamRole_0300CAA5" + } + }, + "assume_role_policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"},\"Effect\":\"Allow\"}]}" + } + }, + "aws_iam_role_policy": { + "root_testmain_Handler_IamRolePolicy_184F2A46": { + "//": { + "metadata": { + "path": "root/Default/Default/test:main/Handler/IamRolePolicy", + "uniqueId": "root_testmain_Handler_IamRolePolicy_184F2A46" + } + }, + "policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":\"none:null\",\"Resource\":\"*\"}]}", + "role": "${aws_iam_role.root_testmain_Handler_IamRole_0300CAA5.name}" + } + }, + "aws_iam_role_policy_attachment": { + "root_testmain_Handler_IamRolePolicyAttachment_F254CEF9": { + "//": { + "metadata": { + "path": "root/Default/Default/test:main/Handler/IamRolePolicyAttachment", + "uniqueId": "root_testmain_Handler_IamRolePolicyAttachment_F254CEF9" + } + }, + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "${aws_iam_role.root_testmain_Handler_IamRole_0300CAA5.name}" + } + }, + "aws_lambda_function": { + "root_testmain_Handler_4ADAC335": { + "//": { + "metadata": { + "path": "root/Default/Default/test:main/Handler/Default", + "uniqueId": "root_testmain_Handler_4ADAC335" + } + }, + "environment": { + "variables": { + "WING_FUNCTION_NAME": "Handler-c8d10438", + "WING_TARGET": "tf-aws" + } + }, + "function_name": "Handler-c8d10438", + "handler": "index.handler", + "publish": true, + "role": "${aws_iam_role.root_testmain_Handler_IamRole_0300CAA5.arn}", + "runtime": "nodejs18.x", + "s3_bucket": "${aws_s3_bucket.root_Code_02F3C603.bucket}", + "s3_key": "${aws_s3_object.root_testmain_Handler_S3Object_2601AAE9.key}", + "timeout": 30, + "vpc_config": { + "security_group_ids": [], + "subnet_ids": [] + } + } + }, + "aws_s3_bucket": { + "root_Code_02F3C603": { + "//": { + "metadata": { + "path": "root/Default/Code", + "uniqueId": "root_Code_02F3C603" + } + }, + "bucket_prefix": "code-c84a50b1-" + } + }, + "aws_s3_object": { + "root_testmain_Handler_S3Object_2601AAE9": { + "//": { + "metadata": { + "path": "root/Default/Default/test:main/Handler/S3Object", + "uniqueId": "root_testmain_Handler_S3Object_2601AAE9" + } + }, + "bucket": "${aws_s3_bucket.root_Code_02F3C603.bucket}", + "key": "", + "source": "" + } + } + } +} +``` + +## preflight.js +```js +const $stdlib = require('@winglang/sdk'); +const $outdir = process.env.WING_SYNTH_DIR ?? "."; +const std = $stdlib.std; +const $wing_is_test = process.env.WING_IS_TEST === "true"; +const $AppBase = $stdlib.core.App.for(process.env.WING_TARGET); +class $Root extends $stdlib.std.Resource { + constructor(scope, id) { + super(scope, id); + class $Closure1 extends $stdlib.std.Resource { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("handle"); + this.display.hidden = true; + } + static _toInflightType(context) { + const self_client_path = "././inflight.$Closure1.js"; + const a_client = context._lift(a); + const s_client = context._lift(s); + const m_client = context._lift(m); + const aCloned_client = context._lift(aCloned); + return $stdlib.core.NodeJsCode.fromInline(` + require("${self_client_path}")({ + a: ${a_client}, + s: ${s_client}, + m: ${m_client}, + aCloned: ${aCloned_client}, + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const $Closure1Client = ${$Closure1._toInflightType(this).text}; + const client = new $Closure1Client({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + _registerBind(host, ops) { + if (ops.includes("$inflight_init")) { + $Closure1._registerBindObject(a, host, []); + $Closure1._registerBindObject(aCloned, host, []); + $Closure1._registerBindObject(m, host, []); + $Closure1._registerBindObject(s, host, []); + } + if (ops.includes("handle")) { + $Closure1._registerBindObject(a, host, ["length"]); + $Closure1._registerBindObject(aCloned, host, ["length"]); + $Closure1._registerBindObject(m, host, ["size"]); + $Closure1._registerBindObject(s, host, ["size"]); + } + super._registerBind(host, ops); + } + } + class $Closure2 extends $stdlib.std.Resource { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("handle"); + this.display.hidden = true; + } + static _toInflightType(context) { + const self_client_path = "././inflight.$Closure2.js"; + const handler_client = context._lift(handler); + return $stdlib.core.NodeJsCode.fromInline(` + require("${self_client_path}")({ + handler: ${handler_client}, + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const $Closure2Client = ${$Closure2._toInflightType(this).text}; + const client = new $Closure2Client({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + _registerBind(host, ops) { + if (ops.includes("$inflight_init")) { + $Closure2._registerBindObject(handler, host, []); + } + if (ops.includes("handle")) { + $Closure2._registerBindObject(handler, host, ["handle"]); + } + super._registerBind(host, ops); + } + } + const a = ["hello"]; + const s = new Set([12]); + const m = {"hello":true}; + const aCloned = [...(Object.freeze(["hello"]))]; + const handler = new $Closure1(this,"$Closure1"); + this.node.root.new("@winglang/sdk.std.Test",std.Test,this,"test:main",new $Closure2(this,"$Closure2")); + } +} +class $App extends $AppBase { + constructor() { + super({ outdir: $outdir, name: "capture_mutables", plugins: $plugins, isTestEnvironment: $wing_is_test }); + if ($wing_is_test) { + new $Root(this, "env0"); + const $test_runner = this.testRunner; + const $tests = $test_runner.findTests(); + for (let $i = 1; $i < $tests.length; $i++) { + new $Root(this, "env" + $i); + } + } else { + new $Root(this, "Default"); + } + } +} +new $App().synth(); + +``` + diff --git a/tools/hangar/__snapshots__/test_corpus/valid/capture_mutables.w_test_sim.md b/tools/hangar/__snapshots__/test_corpus/valid/capture_mutables.w_test_sim.md new file mode 100644 index 00000000000..909e1b82a7c --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/valid/capture_mutables.w_test_sim.md @@ -0,0 +1,15 @@ +# [capture_mutables.w](../../../../../examples/tests/valid/capture_mutables.w) | test | sim + +## stdout.log +```log +pass ─ capture_mutables.wsim » root/env0/test:main + + + + + +Tests 1 passed (1) +Duration + +``` + diff --git a/tools/hangar/__snapshots__/test_corpus/valid/capture_reassigable_class_field.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/capture_reassigable_class_field.w_compile_tf-aws.md new file mode 100644 index 00000000000..ee6b049b0ca --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/valid/capture_reassigable_class_field.w_compile_tf-aws.md @@ -0,0 +1,483 @@ +# [capture_reassigable_class_field.w](../../../../../examples/tests/valid/capture_reassigable_class_field.w) | compile | tf-aws + +## inflight.$Closure1.js +```js +module.exports = function({ }) { + class $Closure1 { + constructor({ }) { + const $obj = (...args) => this.handle(...args); + Object.setPrototypeOf($obj, this); + return $obj; + } + async $inflight_init() { + } + async handle(k) { + } + } + return $Closure1; +} + +``` + +## inflight.$Closure2.js +```js +module.exports = function({ counter }) { + class $Closure2 { + constructor({ }) { + const $obj = (...args) => this.handle(...args); + Object.setPrototypeOf($obj, this); + return $obj; + } + async $inflight_init() { + } + async handle(key) { + (await counter.inc(1,key)); + } + } + return $Closure2; +} + +``` + +## inflight.$Closure3.js +```js +module.exports = function({ kv, counter, util_Util }) { + class $Closure3 { + constructor({ }) { + const $obj = (...args) => this.handle(...args); + Object.setPrototypeOf($obj, this); + return $obj; + } + async $inflight_init() { + } + async handle() { + (await kv.set("k",Object.freeze({"value":"v"}))); + (await kv.set("k2",Object.freeze({"value":"v"}))); + (await kv.get("k")); + (await kv.get("k")); + (await kv.get("k2")); + {((cond) => {if (!cond) throw new Error(`assertion failed: '(await util_Util.waitUntil(async () => { + return ((await counter.peek("k")) === 2); + } + ))'`)})((await util_Util.waitUntil(async () => { + return ((await counter.peek("k")) === 2); + } + )))}; + {((cond) => {if (!cond) throw new Error(`assertion failed: '(await util_Util.waitUntil(async () => { + return ((await counter.peek("k2")) === 1); + } + ))'`)})((await util_Util.waitUntil(async () => { + return ((await counter.peek("k2")) === 1); + } + )))}; + } + } + return $Closure3; +} + +``` + +## inflight.KeyValueStore.js +```js +module.exports = function({ }) { + class KeyValueStore { + constructor({ bucket, onUpdateCallback }) { + this.bucket = bucket; + this.onUpdateCallback = onUpdateCallback; + } + async $inflight_init() { + } + async get(key) { + (await this.onUpdateCallback(key)); + return (await this.bucket.getJson(key)); + } + async set(key, value) { + (await this.bucket.putJson(key,value)); + } + } + return KeyValueStore; +} + +``` + +## main.tf.json +```json +{ + "//": { + "metadata": { + "backend": "local", + "stackName": "root", + "version": "0.15.2" + }, + "outputs": { + "root": { + "Default": { + "cloud.TestRunner": { + "TestFunctionArns": "WING_TEST_RUNNER_FUNCTION_ARNS" + } + } + } + } + }, + "output": { + "WING_TEST_RUNNER_FUNCTION_ARNS": { + "value": "[[\"root/Default/Default/test:main\",\"${aws_lambda_function.root_testmain_Handler_4ADAC335.arn}\"]]" + } + }, + "provider": { + "aws": [ + {} + ] + }, + "resource": { + "aws_dynamodb_table": { + "root_sasa_B91F09DA": { + "//": { + "metadata": { + "path": "root/Default/Default/sasa/Default", + "uniqueId": "root_sasa_B91F09DA" + } + }, + "attribute": [ + { + "name": "id", + "type": "S" + } + ], + "billing_mode": "PAY_PER_REQUEST", + "hash_key": "id", + "name": "wing-counter-sasa-c8fc4cc8" + } + }, + "aws_iam_role": { + "root_testmain_Handler_IamRole_0300CAA5": { + "//": { + "metadata": { + "path": "root/Default/Default/test:main/Handler/IamRole", + "uniqueId": "root_testmain_Handler_IamRole_0300CAA5" + } + }, + "assume_role_policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"},\"Effect\":\"Allow\"}]}" + } + }, + "aws_iam_role_policy": { + "root_testmain_Handler_IamRolePolicy_184F2A46": { + "//": { + "metadata": { + "path": "root/Default/Default/test:main/Handler/IamRolePolicy", + "uniqueId": "root_testmain_Handler_IamRolePolicy_184F2A46" + } + }, + "policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":[\"s3:PutObject*\",\"s3:Abort*\"],\"Resource\":[\"${aws_s3_bucket.root_KeyValueStore_cloudBucket_B6A49C6A.arn}\",\"${aws_s3_bucket.root_KeyValueStore_cloudBucket_B6A49C6A.arn}/*\"],\"Effect\":\"Allow\"},{\"Action\":[\"s3:GetObject*\",\"s3:GetBucket*\",\"s3:List*\"],\"Resource\":[\"${aws_s3_bucket.root_KeyValueStore_cloudBucket_B6A49C6A.arn}\",\"${aws_s3_bucket.root_KeyValueStore_cloudBucket_B6A49C6A.arn}/*\"],\"Effect\":\"Allow\"},{\"Action\":[\"dynamodb:UpdateItem\"],\"Resource\":[\"${aws_dynamodb_table.root_sasa_B91F09DA.arn}\"],\"Effect\":\"Allow\"},{\"Action\":[\"dynamodb:GetItem\"],\"Resource\":[\"${aws_dynamodb_table.root_sasa_B91F09DA.arn}\"],\"Effect\":\"Allow\"}]}", + "role": "${aws_iam_role.root_testmain_Handler_IamRole_0300CAA5.name}" + } + }, + "aws_iam_role_policy_attachment": { + "root_testmain_Handler_IamRolePolicyAttachment_F254CEF9": { + "//": { + "metadata": { + "path": "root/Default/Default/test:main/Handler/IamRolePolicyAttachment", + "uniqueId": "root_testmain_Handler_IamRolePolicyAttachment_F254CEF9" + } + }, + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "${aws_iam_role.root_testmain_Handler_IamRole_0300CAA5.name}" + } + }, + "aws_lambda_function": { + "root_testmain_Handler_4ADAC335": { + "//": { + "metadata": { + "path": "root/Default/Default/test:main/Handler/Default", + "uniqueId": "root_testmain_Handler_4ADAC335" + } + }, + "environment": { + "variables": { + "BUCKET_NAME_ce72b88b": "${aws_s3_bucket.root_KeyValueStore_cloudBucket_B6A49C6A.bucket}", + "BUCKET_NAME_ce72b88b_IS_PUBLIC": "false", + "DYNAMODB_TABLE_NAME_5a275103": "${aws_dynamodb_table.root_sasa_B91F09DA.name}", + "WING_FUNCTION_NAME": "Handler-c8d10438", + "WING_TARGET": "tf-aws" + } + }, + "function_name": "Handler-c8d10438", + "handler": "index.handler", + "publish": true, + "role": "${aws_iam_role.root_testmain_Handler_IamRole_0300CAA5.arn}", + "runtime": "nodejs18.x", + "s3_bucket": "${aws_s3_bucket.root_Code_02F3C603.bucket}", + "s3_key": "${aws_s3_object.root_testmain_Handler_S3Object_2601AAE9.key}", + "timeout": 30, + "vpc_config": { + "security_group_ids": [], + "subnet_ids": [] + } + } + }, + "aws_s3_bucket": { + "root_Code_02F3C603": { + "//": { + "metadata": { + "path": "root/Default/Code", + "uniqueId": "root_Code_02F3C603" + } + }, + "bucket_prefix": "code-c84a50b1-" + }, + "root_KeyValueStore_cloudBucket_B6A49C6A": { + "//": { + "metadata": { + "path": "root/Default/Default/KeyValueStore/cloud.Bucket/Default", + "uniqueId": "root_KeyValueStore_cloudBucket_B6A49C6A" + } + }, + "bucket_prefix": "cloud-bucket-c8a9ef69-", + "force_destroy": false + } + }, + "aws_s3_bucket_public_access_block": { + "root_KeyValueStore_cloudBucket_PublicAccessBlock_742F6520": { + "//": { + "metadata": { + "path": "root/Default/Default/KeyValueStore/cloud.Bucket/PublicAccessBlock", + "uniqueId": "root_KeyValueStore_cloudBucket_PublicAccessBlock_742F6520" + } + }, + "block_public_acls": true, + "block_public_policy": true, + "bucket": "${aws_s3_bucket.root_KeyValueStore_cloudBucket_B6A49C6A.bucket}", + "ignore_public_acls": true, + "restrict_public_buckets": true + } + }, + "aws_s3_bucket_server_side_encryption_configuration": { + "root_KeyValueStore_cloudBucket_Encryption_FDD09906": { + "//": { + "metadata": { + "path": "root/Default/Default/KeyValueStore/cloud.Bucket/Encryption", + "uniqueId": "root_KeyValueStore_cloudBucket_Encryption_FDD09906" + } + }, + "bucket": "${aws_s3_bucket.root_KeyValueStore_cloudBucket_B6A49C6A.bucket}", + "rule": [ + { + "apply_server_side_encryption_by_default": { + "sse_algorithm": "AES256" + } + } + ] + } + }, + "aws_s3_object": { + "root_testmain_Handler_S3Object_2601AAE9": { + "//": { + "metadata": { + "path": "root/Default/Default/test:main/Handler/S3Object", + "uniqueId": "root_testmain_Handler_S3Object_2601AAE9" + } + }, + "bucket": "${aws_s3_bucket.root_Code_02F3C603.bucket}", + "key": "", + "source": "" + } + } + } +} +``` + +## preflight.js +```js +const $stdlib = require('@winglang/sdk'); +const $outdir = process.env.WING_SYNTH_DIR ?? "."; +const std = $stdlib.std; +const $wing_is_test = process.env.WING_IS_TEST === "true"; +const $AppBase = $stdlib.core.App.for(process.env.WING_TARGET); +const cloud = require('@winglang/sdk').cloud; +const util = require('@winglang/sdk').util; +class $Root extends $stdlib.std.Resource { + constructor(scope, id) { + super(scope, id); + class KeyValueStore extends $stdlib.std.Resource { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("get", "set"); + this.bucket = this.node.root.newAbstract("@winglang/sdk.cloud.Bucket",this,"cloud.Bucket"); + const __parent_this_1 = this; + class $Closure1 extends $stdlib.std.Resource { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("handle"); + this.display.hidden = true; + } + static _toInflightType(context) { + const self_client_path = "././inflight.$Closure1.js"; + return $stdlib.core.NodeJsCode.fromInline(` + require("${self_client_path}")({ + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const $Closure1Client = ${$Closure1._toInflightType(this).text}; + const client = new $Closure1Client({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + _registerBind(host, ops) { + if (ops.includes("$inflight_init")) { + } + if (ops.includes("handle")) { + } + super._registerBind(host, ops); + } + } + this.onUpdateCallback = new $Closure1(this,"$Closure1"); + } + onUpdate(fn) { + this.onUpdateCallback = fn; + } + static _toInflightType(context) { + const self_client_path = "././inflight.KeyValueStore.js"; + return $stdlib.core.NodeJsCode.fromInline(` + require("${self_client_path}")({ + }) + `); + } + _toInflight() { + const bucket_client = this._lift(this.bucket); + const onUpdateCallback_client = this._lift(this.onUpdateCallback); + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const KeyValueStoreClient = ${KeyValueStore._toInflightType(this).text}; + const client = new KeyValueStoreClient({ + bucket: ${bucket_client}, + onUpdateCallback: ${onUpdateCallback_client}, + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + _registerBind(host, ops) { + if (ops.includes("$inflight_init")) { + KeyValueStore._registerBindObject(this.bucket, host, []); + KeyValueStore._registerBindObject(this.onUpdateCallback, host, []); + } + if (ops.includes("get")) { + KeyValueStore._registerBindObject(this.bucket, host, ["getJson"]); + KeyValueStore._registerBindObject(this.onUpdateCallback, host, ["handle"]); + } + if (ops.includes("set")) { + KeyValueStore._registerBindObject(this.bucket, host, ["putJson"]); + } + super._registerBind(host, ops); + } + } + class $Closure2 extends $stdlib.std.Resource { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("handle"); + this.display.hidden = true; + } + static _toInflightType(context) { + const self_client_path = "././inflight.$Closure2.js"; + const counter_client = context._lift(counter); + return $stdlib.core.NodeJsCode.fromInline(` + require("${self_client_path}")({ + counter: ${counter_client}, + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const $Closure2Client = ${$Closure2._toInflightType(this).text}; + const client = new $Closure2Client({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + _registerBind(host, ops) { + if (ops.includes("$inflight_init")) { + $Closure2._registerBindObject(counter, host, []); + } + if (ops.includes("handle")) { + $Closure2._registerBindObject(counter, host, ["inc"]); + } + super._registerBind(host, ops); + } + } + class $Closure3 extends $stdlib.std.Resource { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("handle"); + this.display.hidden = true; + } + static _toInflightType(context) { + const self_client_path = "././inflight.$Closure3.js"; + const kv_client = context._lift(kv); + const counter_client = context._lift(counter); + const util_UtilClient = util.Util._toInflightType(context); + return $stdlib.core.NodeJsCode.fromInline(` + require("${self_client_path}")({ + kv: ${kv_client}, + counter: ${counter_client}, + util_Util: ${util_UtilClient.text}, + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const $Closure3Client = ${$Closure3._toInflightType(this).text}; + const client = new $Closure3Client({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + _registerBind(host, ops) { + if (ops.includes("$inflight_init")) { + $Closure3._registerBindObject(counter, host, []); + $Closure3._registerBindObject(kv, host, []); + } + if (ops.includes("handle")) { + $Closure3._registerBindObject(counter, host, ["peek"]); + $Closure3._registerBindObject(kv, host, ["get", "set"]); + } + super._registerBind(host, ops); + } + } + const kv = new KeyValueStore(this,"KeyValueStore"); + const counter = this.node.root.newAbstract("@winglang/sdk.cloud.Counter",this,"sasa"); + (kv.onUpdate(new $Closure2(this,"$Closure2"))); + this.node.root.new("@winglang/sdk.std.Test",std.Test,this,"test:main",new $Closure3(this,"$Closure3")); + } +} +class $App extends $AppBase { + constructor() { + super({ outdir: $outdir, name: "capture_reassigable_class_field", plugins: $plugins, isTestEnvironment: $wing_is_test }); + if ($wing_is_test) { + new $Root(this, "env0"); + const $test_runner = this.testRunner; + const $tests = $test_runner.findTests(); + for (let $i = 1; $i < $tests.length; $i++) { + new $Root(this, "env" + $i); + } + } else { + new $Root(this, "Default"); + } + } +} +new $App().synth(); + +``` + diff --git a/tools/hangar/__snapshots__/test_corpus/valid/capture_reassigable_class_field.w_test_sim.md b/tools/hangar/__snapshots__/test_corpus/valid/capture_reassigable_class_field.w_test_sim.md new file mode 100644 index 00000000000..2f4366ac62b --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/valid/capture_reassigable_class_field.w_test_sim.md @@ -0,0 +1,15 @@ +# [capture_reassigable_class_field.w](../../../../../examples/tests/valid/capture_reassigable_class_field.w) | test | sim + +## stdout.log +```log +pass ─ capture_reassigable_class_field.wsim » root/env0/test:main + + + + + +Tests 1 passed (1) +Duration + +``` + diff --git a/tools/hangar/__snapshots__/test_corpus/valid/capture_reassignable.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/capture_reassignable.w_compile_tf-aws.md new file mode 100644 index 00000000000..7c2e0a77f41 --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/valid/capture_reassignable.w_compile_tf-aws.md @@ -0,0 +1,270 @@ +# [capture_reassignable.w](../../../../../examples/tests/valid/capture_reassignable.w) | compile | tf-aws + +## inflight.$Closure1.js +```js +module.exports = function({ x }) { + class $Closure1 { + constructor({ }) { + const $obj = (...args) => this.handle(...args); + Object.setPrototypeOf($obj, this); + return $obj; + } + async $inflight_init() { + } + async handle() { + {((cond) => {if (!cond) throw new Error(`assertion failed: '(x === 5)'`)})((x === 5))}; + } + } + return $Closure1; +} + +``` + +## inflight.$Closure2.js +```js +module.exports = function({ handler }) { + class $Closure2 { + constructor({ }) { + const $obj = (...args) => this.handle(...args); + Object.setPrototypeOf($obj, this); + return $obj; + } + async $inflight_init() { + } + async handle() { + (await handler()); + } + } + return $Closure2; +} + +``` + +## main.tf.json +```json +{ + "//": { + "metadata": { + "backend": "local", + "stackName": "root", + "version": "0.15.2" + }, + "outputs": { + "root": { + "Default": { + "cloud.TestRunner": { + "TestFunctionArns": "WING_TEST_RUNNER_FUNCTION_ARNS" + } + } + } + } + }, + "output": { + "WING_TEST_RUNNER_FUNCTION_ARNS": { + "value": "[[\"root/Default/Default/test:main\",\"${aws_lambda_function.root_testmain_Handler_4ADAC335.arn}\"]]" + } + }, + "provider": { + "aws": [ + {} + ] + }, + "resource": { + "aws_iam_role": { + "root_testmain_Handler_IamRole_0300CAA5": { + "//": { + "metadata": { + "path": "root/Default/Default/test:main/Handler/IamRole", + "uniqueId": "root_testmain_Handler_IamRole_0300CAA5" + } + }, + "assume_role_policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"},\"Effect\":\"Allow\"}]}" + } + }, + "aws_iam_role_policy": { + "root_testmain_Handler_IamRolePolicy_184F2A46": { + "//": { + "metadata": { + "path": "root/Default/Default/test:main/Handler/IamRolePolicy", + "uniqueId": "root_testmain_Handler_IamRolePolicy_184F2A46" + } + }, + "policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":\"none:null\",\"Resource\":\"*\"}]}", + "role": "${aws_iam_role.root_testmain_Handler_IamRole_0300CAA5.name}" + } + }, + "aws_iam_role_policy_attachment": { + "root_testmain_Handler_IamRolePolicyAttachment_F254CEF9": { + "//": { + "metadata": { + "path": "root/Default/Default/test:main/Handler/IamRolePolicyAttachment", + "uniqueId": "root_testmain_Handler_IamRolePolicyAttachment_F254CEF9" + } + }, + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "${aws_iam_role.root_testmain_Handler_IamRole_0300CAA5.name}" + } + }, + "aws_lambda_function": { + "root_testmain_Handler_4ADAC335": { + "//": { + "metadata": { + "path": "root/Default/Default/test:main/Handler/Default", + "uniqueId": "root_testmain_Handler_4ADAC335" + } + }, + "environment": { + "variables": { + "WING_FUNCTION_NAME": "Handler-c8d10438", + "WING_TARGET": "tf-aws" + } + }, + "function_name": "Handler-c8d10438", + "handler": "index.handler", + "publish": true, + "role": "${aws_iam_role.root_testmain_Handler_IamRole_0300CAA5.arn}", + "runtime": "nodejs18.x", + "s3_bucket": "${aws_s3_bucket.root_Code_02F3C603.bucket}", + "s3_key": "${aws_s3_object.root_testmain_Handler_S3Object_2601AAE9.key}", + "timeout": 30, + "vpc_config": { + "security_group_ids": [], + "subnet_ids": [] + } + } + }, + "aws_s3_bucket": { + "root_Code_02F3C603": { + "//": { + "metadata": { + "path": "root/Default/Code", + "uniqueId": "root_Code_02F3C603" + } + }, + "bucket_prefix": "code-c84a50b1-" + } + }, + "aws_s3_object": { + "root_testmain_Handler_S3Object_2601AAE9": { + "//": { + "metadata": { + "path": "root/Default/Default/test:main/Handler/S3Object", + "uniqueId": "root_testmain_Handler_S3Object_2601AAE9" + } + }, + "bucket": "${aws_s3_bucket.root_Code_02F3C603.bucket}", + "key": "", + "source": "" + } + } + } +} +``` + +## preflight.js +```js +const $stdlib = require('@winglang/sdk'); +const $outdir = process.env.WING_SYNTH_DIR ?? "."; +const std = $stdlib.std; +const $wing_is_test = process.env.WING_IS_TEST === "true"; +const $AppBase = $stdlib.core.App.for(process.env.WING_TARGET); +const cloud = require('@winglang/sdk').cloud; +class $Root extends $stdlib.std.Resource { + constructor(scope, id) { + super(scope, id); + class $Closure1 extends $stdlib.std.Resource { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("handle"); + this.display.hidden = true; + } + static _toInflightType(context) { + const self_client_path = "././inflight.$Closure1.js"; + const x_client = context._lift(x); + return $stdlib.core.NodeJsCode.fromInline(` + require("${self_client_path}")({ + x: ${x_client}, + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const $Closure1Client = ${$Closure1._toInflightType(this).text}; + const client = new $Closure1Client({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + _registerBind(host, ops) { + if (ops.includes("$inflight_init")) { + $Closure1._registerBindObject(x, host, []); + } + if (ops.includes("handle")) { + $Closure1._registerBindObject(x, host, []); + } + super._registerBind(host, ops); + } + } + class $Closure2 extends $stdlib.std.Resource { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("handle"); + this.display.hidden = true; + } + static _toInflightType(context) { + const self_client_path = "././inflight.$Closure2.js"; + const handler_client = context._lift(handler); + return $stdlib.core.NodeJsCode.fromInline(` + require("${self_client_path}")({ + handler: ${handler_client}, + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const $Closure2Client = ${$Closure2._toInflightType(this).text}; + const client = new $Closure2Client({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + _registerBind(host, ops) { + if (ops.includes("$inflight_init")) { + $Closure2._registerBindObject(handler, host, []); + } + if (ops.includes("handle")) { + $Closure2._registerBindObject(handler, host, ["handle"]); + } + super._registerBind(host, ops); + } + } + let x = 5; + const handler = new $Closure1(this,"$Closure1"); + this.node.root.new("@winglang/sdk.std.Test",std.Test,this,"test:main",new $Closure2(this,"$Closure2")); + } +} +class $App extends $AppBase { + constructor() { + super({ outdir: $outdir, name: "capture_reassignable", plugins: $plugins, isTestEnvironment: $wing_is_test }); + if ($wing_is_test) { + new $Root(this, "env0"); + const $test_runner = this.testRunner; + const $tests = $test_runner.findTests(); + for (let $i = 1; $i < $tests.length; $i++) { + new $Root(this, "env" + $i); + } + } else { + new $Root(this, "Default"); + } + } +} +new $App().synth(); + +``` + diff --git a/tools/hangar/__snapshots__/test_corpus/valid/capture_reassignable.w_test_sim.md b/tools/hangar/__snapshots__/test_corpus/valid/capture_reassignable.w_test_sim.md new file mode 100644 index 00000000000..54a981a3973 --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/valid/capture_reassignable.w_test_sim.md @@ -0,0 +1,15 @@ +# [capture_reassignable.w](../../../../../examples/tests/valid/capture_reassignable.w) | test | sim + +## stdout.log +```log +pass ─ capture_reassignable.wsim » root/env0/test:main + + + + + +Tests 1 passed (1) +Duration + +``` + diff --git a/tools/hangar/__snapshots__/test_corpus/valid/reassignment.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/reassignment.w_compile_tf-aws.md index a3a2dea6c4d..f243b162117 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/reassignment.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/reassignment.w_compile_tf-aws.md @@ -4,7 +4,8 @@ ```js module.exports = function({ }) { class R { - constructor({ f1 }) { + constructor({ f, f1 }) { + this.f = f; this.f1 = f1; } async $inflight_init() { @@ -76,11 +77,13 @@ class $Root extends $stdlib.std.Resource { `); } _toInflight() { + const f_client = this._lift(this.f); const f1_client = this._lift(this.f1); return $stdlib.core.NodeJsCode.fromInline(` (await (async () => { const RClient = ${R._toInflightType(this).text}; const client = new RClient({ + f: ${f_client}, f1: ${f1_client}, }); if (client.$inflight_init) { await client.$inflight_init(); } @@ -90,6 +93,7 @@ class $Root extends $stdlib.std.Resource { } _registerBind(host, ops) { if (ops.includes("$inflight_init")) { + R._registerBindObject(this.f, host, []); R._registerBindObject(this.f1, host, []); } super._registerBind(host, ops);