Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(compiler): unable to lift @inflight args without struct expansion #6925

Merged
merged 10 commits into from
Jul 23, 2024
46 changes: 46 additions & 0 deletions examples/tests/valid/inflight_ts/.example3.inflight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/// This file is generated by Wing.
export interface Lifts {
example: Example$Inflight
};
type HandlerFunction<T> = T extends { handle: (...args: any[]) => any } ? T['handle'] : T;
type ExpectedFunction = HandlerFunction<() => Promise<string>>;
export type Handler = ((ctx: Lifts, ...args: Parameters<ExpectedFunction>) => ReturnType<ExpectedFunction>) & {};
export function inflight(handler: Handler): Handler { return handler; }
export default inflight;
/** Trait marker for classes that can be depended upon.
The presence of this interface indicates that an object has
an `IDependable` implementation.

This interface can be used to take an (ordering) dependency on a set of
constructs. An ordering dependency implies that the resources represented by
those constructs are deployed before the resources depending ON them are
deployed. */
export interface IDependable$Inflight {
}
/** Represents a construct. */
export interface IConstruct$Inflight extends IDependable$Inflight {
}
/** Represents the building block of the construct graph.
All constructs besides the root construct must be created within the scope of
another construct. */
export class Construct$Inflight implements IConstruct$Inflight {
}
/** Data that can be lifted into inflight. */
export interface ILiftable$Inflight {
}
/** A liftable object that needs to be registered on the host as part of the lifting process.
This is generally used so the host can set up permissions
to access the lifted object inflight. */
export interface IHostedLiftable$Inflight extends ILiftable$Inflight {
}
/** Abstract interface for `Resource`. */
export interface IResource$Inflight extends IConstruct$Inflight, IHostedLiftable$Inflight {
}
/** Shared behavior between all Wing SDK resources. */
export class Resource$Inflight extends Construct$Inflight implements IResource$Inflight {
}
export class Example$Inflight extends Resource$Inflight {
readonly $inflight_init: () => Promise<Example$Inflight>;
readonly done: () => Promise<void>;
readonly getMessage: () => Promise<string>;
}
5 changes: 5 additions & 0 deletions examples/tests/valid/inflight_ts/example3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import inflight from "./.example3.inflight";

export default inflight(async ({ example }) => {
return await example.getMessage()
});
13 changes: 11 additions & 2 deletions examples/tests/valid/intrinsics.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,25 @@ let funcFunction = @inflight("./inflight_ts/example2.ts",
{ obj: example },
{ obj: example, alias: "exampleCopy", ops: ["getMessage"]},
{ obj: [1, 2, 3], alias: "numbers" },
]
],
);
let func = new cloud.Function(funcFunction);

let defaultMessage: inflight (): str = @inflight("./inflight_ts/example3.ts",
// Intentionally use { } instead of struct expansion
{ lifts: [{ obj: example }] }
);


test "invoke default function" {
assert(echo("message") == "message");
expect.equal(echo("message"), "message");
}

test "invoke inflight function" {
funcFunction("message");
func.invoke("message");
}

test "invoke default with lift" {
expect.equal(defaultMessage(), "message");
}
23 changes: 19 additions & 4 deletions libs/wingc/src/jsify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -701,11 +701,26 @@ impl<'a> JSifier<'a> {
)
}
IntrinsicKind::Inflight => {
let arg_list = intrinsic.arg_list.as_ref().unwrap();

// Use statically known data to generate the type information needed
let mut export_name = new_code!(&expression.span, "\"default\"");
let mut lifts: IndexMap<String, (&Expr, Option<&Vec<Expr>>, CodeMaker)> = IndexMap::new();
for x in &arg_list.named_args {

let arg_list = intrinsic.arg_list.as_ref().unwrap();
let fields = if arg_list.named_args.is_empty() && arg_list.pos_args.len() == 2 {
// Trailing struct has been provided as a single positional argument, extract the named fields instead
let second_arg = arg_list.pos_args.get(1).unwrap();
match &second_arg.kind {
ExprKind::JsonLiteral { element, .. } => match &element.kind {
ExprKind::JsonMapLiteral { fields, .. } => fields,
_ => return CodeMaker::default(),
},
ExprKind::StructLiteral { fields, .. } => fields,
_ => return CodeMaker::default(),
}
} else {
&arg_list.named_args
};
for x in fields {
if x.0.name == "export" {
export_name = self.jsify_expression(&x.1, ctx);
} else if x.0.name == "lifts" {
Expand Down Expand Up @@ -852,7 +867,7 @@ impl<'a> JSifier<'a> {
lift_string.append("]`");
}

if arg_list.named_args.get("lifts").is_some() {
if !lifts.is_empty() {
let list = lifts
.iter()
.map(|(.., (.., expr_code))| expr_code.clone())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
"use strict";
const $helpers = require("@winglang/sdk/lib/helpers");
const $macros = require("@winglang/sdk/lib/macros");
module.exports = function({ $echo }) {
module.exports = function({ $echo, $expect_Util }) {
class $Closure1 {
constructor({ }) {
const $obj = (...args) => this.handle(...args);
Object.setPrototypeOf($obj, this);
return $obj;
}
async handle() {
$helpers.assert($helpers.eq((await $echo("message")), "message"), "echo(\"message\") == \"message\"");
(await $expect_Util.equal((await $echo("message")), "message"));
}
}
return $Closure1;
Expand Down Expand Up @@ -43,6 +43,27 @@ module.exports = function({ $func, $funcFunction }) {
//# sourceMappingURL=inflight.$Closure2-2.cjs.map
```

## inflight.$Closure3-2.cjs
```cjs
"use strict";
const $helpers = require("@winglang/sdk/lib/helpers");
const $macros = require("@winglang/sdk/lib/macros");
module.exports = function({ $defaultMessage, $expect_Util }) {
class $Closure3 {
constructor({ }) {
const $obj = (...args) => this.handle(...args);
Object.setPrototypeOf($obj, this);
return $obj;
}
async handle() {
(await $expect_Util.equal((await $defaultMessage()), "message"));
}
}
return $Closure3;
}
//# sourceMappingURL=inflight.$Closure3-2.cjs.map
```

## inflight.Bar-1.cjs
```cjs
"use strict";
Expand Down Expand Up @@ -384,6 +405,7 @@ class $Root extends $stdlib.std.Resource {
return `
require("${$helpers.normalPath(__dirname)}/inflight.$Closure1-2.cjs")({
$echo: ${$stdlib.core.liftObject(echo)},
$expect_Util: ${$stdlib.core.liftObject($stdlib.core.toLiftableModuleType(expect.Util, "@winglang/sdk/expect", "Util"))},
})
`;
}
Expand All @@ -401,9 +423,11 @@ class $Root extends $stdlib.std.Resource {
get _liftMap() {
return ({
"handle": [
[$stdlib.core.toLiftableModuleType(expect.Util, "@winglang/sdk/expect", "Util"), ["equal"]],
[echo, ["handle"]],
],
"$inflight_init": [
[$stdlib.core.toLiftableModuleType(expect.Util, "@winglang/sdk/expect", "Util"), []],
[echo, []],
],
});
Expand Down Expand Up @@ -447,6 +471,44 @@ class $Root extends $stdlib.std.Resource {
});
}
}
class $Closure3 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.$Closure3-2.cjs")({
$defaultMessage: ${$stdlib.core.liftObject(defaultMessage)},
$expect_Util: ${$stdlib.core.liftObject($stdlib.core.toLiftableModuleType(expect.Util, "@winglang/sdk/expect", "Util"))},
})
`;
}
_toInflight() {
return `
(await (async () => {
const $Closure3Client = ${$Closure3._toInflightType()};
const client = new $Closure3Client({
});
if (client.$inflight_init) { await client.$inflight_init(); }
return client;
})())
`;
}
get _liftMap() {
return ({
"handle": [
[$stdlib.core.toLiftableModuleType(expect.Util, "@winglang/sdk/expect", "Util"), ["equal"]],
[defaultMessage, ["handle"]],
],
"$inflight_init": [
[$stdlib.core.toLiftableModuleType(expect.Util, "@winglang/sdk/expect", "Util"), []],
[defaultMessage, []],
],
});
}
}
const path = "SHOULD_IGNORE";
const filename = "intrinsics.test.w";
const currentFile = (fs.Util.join($helpers.resolveDirname(__dirname, "../../.."), filename));
Expand All @@ -458,8 +520,10 @@ class $Root extends $stdlib.std.Resource {
const example = new Example(this, "Example");
const funcFunction = $stdlib.core.importInflight(`require('../../../inflight_ts/example2.ts')["main"]`, [({ obj: example, alias: "example" }), ({ obj: example, ops: ["getMessage", ], alias: "exampleCopy" }), ({ obj: [1, 2, 3], alias: "numbers" })]);
const func = globalThis.$ClassFactory.new("@winglang/sdk.cloud.Function", cloud.Function, this, "Function", funcFunction);
const defaultMessage = $stdlib.core.importInflight(`require('../../../inflight_ts/example3.ts')["default"]`, [({ obj: example, alias: "example" })]);
globalThis.$ClassFactory.new("@winglang/sdk.std.Test", std.Test, this, "test:invoke default function", new $Closure1(this, "$Closure1"));
globalThis.$ClassFactory.new("@winglang/sdk.std.Test", std.Test, this, "test:invoke inflight function", new $Closure2(this, "$Closure2"));
globalThis.$ClassFactory.new("@winglang/sdk.std.Test", std.Test, this, "test:invoke default with lift", new $Closure3(this, "$Closure3"));
}
}
const $APP = $PlatformManager.createApp({ outdir: $outdir, name: "intrinsics.test", rootConstruct: $Root, isTestEnvironment: $wing_is_test, entrypointDir: process.env['WING_SOURCE_DIR'], rootId: process.env['WING_ROOT_ID'] });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
[INFO] invoke inflight function | [ 1, 2, 3 ]
pass ─ intrinsics.test.wsim » root/env0/test:invoke default function
pass ─ intrinsics.test.wsim » root/env1/test:invoke inflight function
pass ─ intrinsics.test.wsim » root/env2/test:invoke default with lift

Tests 2 passed (2)
Tests 3 passed (3)
Snapshots 1 skipped
Test Files 1 passed (1)
Duration <DURATION>
Expand Down
Loading