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): cannot lift a class that extends a cloud resource #6490

Merged
merged 39 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f399d23
midwork
Chriscbr May 7, 2024
0cced6c
Merge branch 'main' into rybickic/extend-resources
Chriscbr May 15, 2024
4349634
midwork
Chriscbr May 15, 2024
9250ae6
remove
Chriscbr May 15, 2024
14a59cb
clean up codegen
Chriscbr May 15, 2024
8a48a59
fixes
Chriscbr May 15, 2024
6f78f33
nice cleanup
Chriscbr May 15, 2024
77195bf
Merge branch 'main' into rybickic/extend-resources
Chriscbr May 29, 2024
a591275
more work
Chriscbr May 29, 2024
5a1db9f
update azure resources
Chriscbr May 29, 2024
0e64e31
updating azure and gcp resources
Chriscbr May 29, 2024
2538b02
liftedFields -> liftedState
Chriscbr May 29, 2024
0f8a56a
update awscdk
Chriscbr May 29, 2024
154275d
update some snapshots - still some tests failing
Chriscbr May 29, 2024
ad67230
handle edge case
Chriscbr May 29, 2024
e5638bc
fix
Chriscbr May 29, 2024
f394186
fix docs
Chriscbr May 29, 2024
31a97f3
update more snaps
Chriscbr May 29, 2024
c6285e5
fix missing toInflightType
Chriscbr May 29, 2024
56110e8
minor fix
Chriscbr May 29, 2024
637c805
remove unused method
Chriscbr May 29, 2024
c925fbd
Merge branch 'main' into rybickic/extend-resources
Chriscbr Jul 23, 2024
cf6d676
fix service
Chriscbr Jul 23, 2024
5314925
fix snaps
Chriscbr Jul 23, 2024
0c26c14
midwork
Chriscbr Jul 23, 2024
f099099
rename method
Chriscbr Jul 23, 2024
7ddd9bd
undo change to SCOPE_PARAM
Chriscbr Jul 23, 2024
0393497
Merge branch 'main' into rybickic/extend-resources
monadabot Jul 23, 2024
7adab07
chore: self mutation (e2e-1of2.diff)
monadabot Jul 23, 2024
c93f676
chore: self mutation (e2e-2of2.diff)
monadabot Jul 23, 2024
6640669
improve codegen a bit
Chriscbr Jul 23, 2024
5ea7aa5
Merge branch 'main' into rybickic/extend-resources
monadabot Jul 23, 2024
03c7f3b
chore: self mutation (e2e-1of2.diff)
monadabot Jul 23, 2024
f68ca70
chore: self mutation (e2e-2of2.diff)
monadabot Jul 23, 2024
4745d04
fix bug
Chriscbr Jul 23, 2024
25ebeb8
Merge branch 'main' into rybickic/extend-resources
Chriscbr Jul 24, 2024
04b8181
skip emitting _liftedState if there are no lifts
Chriscbr Jul 24, 2024
385770b
reduce code gen a bit
Chriscbr Jul 24, 2024
39987a8
update snapshots
Chriscbr Jul 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions examples/tests/valid/extend_counter.test.w
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
bring cloud;
bring util;
bring expect;

let global_value = "yo";

class MyCounter extends cloud.Counter {
pub field1: num;
new() {
this.field1 = 5;
}

pub inflight inc() {
expect.equal(this.field1, 5);
expect.equal(global_value, "yo");
super.inc();
}

pub inflight extra1(): str {
return "extra1";
}
}

class MySuperCounter extends MyCounter {
pub field2: num;
new() {
this.field2 = 10;
}

pub inflight inc() {
expect.equal(this.field2, 10);
expect.equal(this.field1, 5);
expect.equal(global_value, "yo");
super.inc();
}

pub inflight extra2(): str {
return "extra2";
}
}
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved

let c = new MySuperCounter();

test "counter works" {
c.inc();
expect.equal(c.peek(), 1);
expect.equal(c.extra1(), "extra1");
expect.equal(c.extra2(), "extra2");
}
49 changes: 22 additions & 27 deletions libs/wingc/src/jsify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const __DIRNAME: &str = "__dirname";

const SUPER_CLASS_INFLIGHT_INIT_NAME: &str = formatcp!("super_{CLASS_INFLIGHT_INIT_NAME}");

const SCOPE_PARAM: &str = "$scope";
pub const SCOPE_PARAM: &str = "$scope";

pub struct JSifyContext<'a> {
pub lifts: Option<&'a Lifts>,
Expand Down Expand Up @@ -1649,7 +1649,8 @@ impl<'a> JSifier<'a> {
// emit the `_toInflight` and `_toInflightType` methods (TODO: renamed to `_liftObject` and
// `_liftType`).
code.add_code(self.jsify_to_inflight_type_method(&class, ctx));
code.add_code(self.jsify_to_inflight_method(&class.name, class_type));
code.add_code(self.jsify_to_inflight_method(&class.name));
code.add_code(self.jsify_lifted_fields(&class.name, class_type));

// emit `onLift` and `onLiftType` to bind permissions and environment variables to inflight hosts
code.add_code(self.jsify_register_bind_method(class, class_type, BindMethod::Instance, ctx));
Expand Down Expand Up @@ -1748,39 +1749,32 @@ impl<'a> JSifier<'a> {
res
}

fn jsify_to_inflight_method(&self, class_name: &Symbol, class_type: TypeRef) -> CodeMaker {
fn jsify_lifted_fields(&self, class_name: &Symbol, class_type: TypeRef) -> CodeMaker {
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
let mut code = CodeMaker::with_source(&class_name.span);

code.open("_toInflight() {");

code.open("return `");

code.open("(await (async () => {");

code.line(format!(
"const {}Client = ${{{}._toInflightType()}};",
class_name.name, class_name.name,
));

code.open(format!("const client = new {}Client({{", class_name.name));
code.open("_liftedFields() {");
code.open("return {");
code.line("...(super._liftedFields?.() ?? {}),");

// Get lifted fields from entire class ancestry
let lifts = Self::class_lifted_fields(class_type);

for (token, obj) in lifts {
code.line(format!("{token}: ${{{STDLIB_CORE}.liftObject({obj})}},"));
code.line(format!("{token}: {STDLIB_CORE}.liftObject({obj}),"));
}

code.close("});");
code.close("};");
code.close("}");
code
}

code.line(format!(
"if (client.{CLASS_INFLIGHT_INIT_NAME}) {{ await client.{CLASS_INFLIGHT_INIT_NAME}(); }}"
));
code.line("return client;");
fn jsify_to_inflight_method(&self, class_name: &Symbol) -> CodeMaker {
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
let mut code = CodeMaker::with_source(&class_name.span);

code.close("})())");
code.open("_toInflight() {");

code.close("`;");
code.line(format!(
"return {STDLIB_CORE}.InflightClient.forV2({class_name}, this._liftedFields());"
));

code.close("}");
code
Expand Down Expand Up @@ -1893,8 +1887,9 @@ impl<'a> JSifier<'a> {
IndexSet::new()
};

class_code.open(format!(
"{JS_CONSTRUCTOR}({{ {} }}) {{",
class_code.open(format!("{JS_CONSTRUCTOR}($args) {{",));
class_code.line(format!(
"const {{ {} }} = $args;",
lifted_fields
.union(&parent_fields)
.map(|token| { token.clone() })
Expand All @@ -1903,7 +1898,7 @@ impl<'a> JSifier<'a> {
));

if class.parent.is_some() {
class_code.line(format!("super({{ {} }});", parent_fields.iter().join(", ")));
class_code.line("super($args);");
}

for token in &lifted_fields {
Expand Down
24 changes: 22 additions & 2 deletions libs/wingc/src/lifting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
},
comp_ctx::{CompilationContext, CompilationPhase},
diagnostic::{report_diagnostic, Diagnostic},
jsify::{JSifier, JSifyContext},
jsify::{JSifier, JSifyContext, SCOPE_PARAM},
type_check::{
lifts::{Liftable, Lifts},
resolve_user_defined_type,
Expand Down Expand Up @@ -142,13 +142,33 @@ impl<'a> LiftVisitor<'a> {
.to_string();

let current_env = self.ctx.current_env().expect("an env");
let result = current_env.lookup_nested_str(&node.full_path_str(), Some(self.ctx.current_stmt_idx()));
let type_ = match result {
LookupResult::Found(SymbolKind::Type(t), _) => *t,
_ => todo!(),
};

// We don't have the FQN for any non-JSII classes (e.g. winglib classes)
let fqn = if let Some(class) = type_.as_class() {
class.fqn.clone()
} else if let Some(iface) = type_.as_interface() {
iface.fqn.clone()
} else {
None
};
if let Some(SymbolKind::Namespace(root_namespace)) = current_env.lookup(&node.root, None) {
let type_path = node.field_path_str();
let module_path = match &root_namespace.module_path {
ResolveSource::WingFile => "",
ResolveSource::ExternalModule(p) => p,
};
format!("$stdlib.core.toLiftableModuleType({udt_js}, \"{module_path}\", \"{type_path}\")")
if let Some(fqn) = fqn {
// TODO: fall back to "this"?
format!("$stdlib.core.toLiftableModuleType({SCOPE_PARAM}.node.root.typeForFqn(\"{fqn}\") ?? {udt_js}, \"{module_path}\", \"{type_path}\")")
} else {
format!("$stdlib.core.toLiftableModuleType({udt_js}, \"{module_path}\", \"{type_path}\")")
}
// format!("$stdlib.core.toLiftableModuleType({udt_js}, \"{module_path}\", \"{type_path}\")")
} else {
// Non-namespaced reference, should be a wing type with a helper to lift it
udt_js
Expand Down
41 changes: 39 additions & 2 deletions libs/wingsdk/src/core/inflight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,24 @@ export class InflightClient {
dirname: string,
filename: string,
clientClass: string,
args: string[]
args: string[] | undefined,
liftedFields?: Record<string, string>
): string {
const inflightDir = dirname;
const inflightFile = basename(filename).split(".")[0] + ".inflight";
let argsStr = "";
if (args !== undefined) {
argsStr = args.join(", ");
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
} else {
argsStr += "{";
for (const [key, value] of Object.entries(liftedFields ?? {})) {
argsStr += `${key}: ${value},`;
}
argsStr += "}";
}
return `new (require("${normalPath(
`${inflightDir}/${inflightFile}`
)}")).${clientClass}(${args.join(", ")})`;
)}")).${clientClass}(${argsStr})`;
}

/**
Expand All @@ -60,6 +71,32 @@ export class InflightClient {
return `require("${normalPath(filename)}").${clientClass}`;
}

/**
* Returns code for instantiating an inflight client from a class.
*/
public static forV2(
klass: any,
liftedFields: Record<string, string>
): string {
if (typeof klass !== "function") {
throw new Error("Invalid inflight class");
}
if (typeof klass._toInflightType !== "function") {
throw new Error("Class is missing _toInflightType static method");
}
const liftedFieldsStr = Object.keys(liftedFields)
.map((key) => `${key}: ${liftedFields[key]}`)
.join(", ");
return `
(await (async () => {
const klass = ${klass._toInflightType()};
const client = new klass({${liftedFieldsStr}});
if (client.$inflight_init) { await client.$inflight_init(); }
return client;
})())
`;
}

private constructor() {}
}

Expand Down
18 changes: 13 additions & 5 deletions libs/wingsdk/src/shared-aws/counter.inflight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,19 @@ const VALUE_ATTRIBUTE = "counter_value";
const SET_VALUE = "set_value";

export class CounterClient implements ICounterClient {
constructor(
private readonly tableName: string,
private readonly initial: number = 0,
private readonly client = new DynamoDBClient({})
) {}
private readonly client = new DynamoDBClient({});
private readonly tableName: string;
private readonly initial: number;
constructor({
$tableName,
$initial,
}: {
$tableName: string;
$initial: number;
}) {
this.tableName = $tableName;
this.initial = $initial ?? 0;
}

public async inc(amount = 1, key = COUNTER_ID): Promise<number> {
const command = new UpdateItemCommand({
Expand Down
18 changes: 13 additions & 5 deletions libs/wingsdk/src/shared-aws/function.inflight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,19 @@ import { IFunctionClient } from "../cloud";
import { LogLevel, Trace, TraceType } from "../std";

export class FunctionClient implements IFunctionClient {
constructor(
private readonly functionArn: string,
private readonly constructPath: string,
private readonly lambdaClient = new LambdaClient({})
) {}
private readonly lambdaClient = new LambdaClient({});
private readonly functionArn: string;
private readonly constructPath: string;
constructor({
$functionArn,
$constructPath,
}: {
$functionArn: string;
$constructPath: string;
}) {
this.functionArn = $functionArn;
this.constructPath = $constructPath;
}

/**
* Invokes the function with a payload and waits for the result.
Expand Down
5 changes: 4 additions & 1 deletion libs/wingsdk/src/shared-aws/test-runner.inflight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ export class TestRunnerClient implements ITestRunnerClient {
if (!functionArn) {
throw new Error(`No test found with path "${path}"`);
}
const client = new FunctionClient(functionArn, path);
const client = new FunctionClient({
$functionArn: functionArn,
$constructPath: path,
});
let traces: Trace[] = [];
let pass = false;
let error: string | undefined;
Expand Down
1 change: 1 addition & 0 deletions libs/wingsdk/src/std/datetime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export class Datetime implements ILiftable {
public static _toInflightType(): string {
return InflightClient.forType(__filename, this.name);
}

/**
* Create a Datetime from UTC timezone
*
Expand Down
21 changes: 21 additions & 0 deletions libs/wingsdk/src/std/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ export interface ILiftable {
* @internal
*/
_toInflight(): string;

/**
* Return the fields that need to be passed to the inflight constructor as arguments.
* Each value should be a JavaScript code string.
*
* @internal
*/
_liftedFields?(): Record<string, string>;
}

/**
Expand Down Expand Up @@ -170,6 +178,14 @@ export abstract class Resource extends Construct implements IResource {
throw new AbstractMemberError();
}

/**
* @internal
* @abstract
*/
public _liftedFields(): Record<string, string> {
throw new AbstractMemberError();
}

/**
* A hook called by the Wing compiler once for each inflight host that needs to
* use this resource inflight.
Expand Down Expand Up @@ -229,6 +245,11 @@ export abstract class AutoIdResource extends Resource {
const id = App.of(scope).makeId(scope, idPrefix ? `${idPrefix}_` : "");
super(scope, id);
}

/** @internal */
public _liftedFields(): Record<string, string> {
return {};
}
}

/**
Expand Down
Loading
Loading