diff --git a/docs/docs/02-concepts/01-preflight-and-inflight.md b/docs/docs/02-concepts/01-preflight-and-inflight.md index f857da0da82..c3208888995 100644 --- a/docs/docs/02-concepts/01-preflight-and-inflight.md +++ b/docs/docs/02-concepts/01-preflight-and-inflight.md @@ -268,6 +268,65 @@ inflight () => { }; ``` +### Lift qualification + +Preflight objects referenced inflight are called "lifted" objects: + +```js playground +let preflight_str = "hello from preflight"; +inflight () => { + log(preflight_str); // `preflight_str` is "lifted" into inflight. +}; +``` + +During the lifting process the compiler tries to figure out in what way the lifted objects are being used. +This is how Winglang generats least privilage permissions. Consider the case of lifting a [`cloud.Bucket`](../04-standard-library/cloud/bucket.md) object: + +```js playground +bring cloud; +let bucket = new cloud.Bucket(); +new cloud.Function(inflight () => { + bucket.put("key", "value"); // `bucket` is lifted and `put` is being used on it +}); +``` + +In this example the compiler generates the correct _write_ access permissions for the [`cloud.Function`](../04-standard-library/cloud/function.md) on `bucket` based on the fact we're `put`ing into it. We say `bucket`'s lift is qualified with `put`. + +#### Explicit lift qualification +In some cases the compiler can't figure out (yet) the lift qualifications, and therefore will report an error: + +```js playground +bring cloud; +let main_bucket = new cloud.Bucket() as "main"; +let secondary_bucket = new cloud.Bucket() as "backup"; +let use_main = true; +new cloud.Function(inflight () => { + let var b = main_bucket; + if !use_main { + b = secondary_bucket; + } + b.put("key", "value"); // Error: the compiler doesn't know the possible values for `b` and therefore can't qualify the lift. +}); +``` + +To explicitly qualify lifts in an inflight closure or inflight method and supress the above compiler error use the `lift()` utility function: + +```js playground +bring cloud; +let main_bucket = new cloud.Bucket() as "main"; +let secondary_bucket = new cloud.Bucket() as "backup"; +let use_main = true; +new cloud.Function(inflight () => { + lift(main_bucket, ["put"]); // Explicitly sate the "put" may be used on `main_bucket` + lift(secondary_bucket, ["put"]); // Explicitly sate the "put" may be used on `secondary_bucket` + let var b = main_bucket; + if !use_main { + b = secondary_bucket; + } + b.put("key", "value"); // Error is supressed and all possible values of `b` were explicitly qualified with "put" +}); +``` + ## Phase-independent code The global functions `log`, `assert`, and `throw` can all be used in both preflight and inflight code. diff --git a/docs/docs/03-language-reference.md b/docs/docs/03-language-reference.md index a90d6681e46..67f81559896 100644 --- a/docs/docs/03-language-reference.md +++ b/docs/docs/03-language-reference.md @@ -553,6 +553,7 @@ log("UTC: {t1.utc.toIso())}"); // output: 2023-02-09T06:21:03.000Z | `assert` | checks a condition and _throws_ if evaluated to false | | `unsafeCast` | cast a value into a different type | | `nodeof` | obtain the [tree node](./02-concepts/02-application-tree.md) of a preflight object | +| `lift` | explicitly qualify a [lift](./02-concepts/01-preflight-and-inflight.md#explicit-lift-qualification) of a preflight object | > ```TS > log("Hello {name}"); diff --git a/examples/tests/invalid/explicit_lift_qualification.test.w b/examples/tests/invalid/explicit_lift_qualification.test.w new file mode 100644 index 00000000000..e7f1a44d383 --- /dev/null +++ b/examples/tests/invalid/explicit_lift_qualification.test.w @@ -0,0 +1,40 @@ +bring cloud; + +let bucket = new cloud.Bucket(); + +let prelight_string = "hi"; + +class Foo { + pub inflight mehtod1() { + let b = bucket; + lift(b, ["put"]); // Explicit qualification with inflight object, lift call as non first statement + // ^ Expected a preflight object as first argument to `lift` builtin, found inflight expression instead + //^^^^^^^^^^^^^^^ lift() calls must be at the top of the method + + lift(prelight_string, ["contains"]); // Explicit qualification on preflight non-class + // ^^^^^^^^^^^^^^^ Expected type to be "Resource", but got "str" instead + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lift() calls must be at the top of the method + + let inflight_qualifier = "delete"; + lift(bucket, [inflight_qualifier]); // Explicit qualification with inflight qualifiers, lift call as non first statement + // ^^^^^^^^^^^^^^^^^^^^ Qualification list must not contain any inflight elements + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lift() calls must be at the top of the method + + let inner_closure = () => { + lift(bucket, ["get"]); // lift() call in inner closure + //^^^^^^^^^^^^^^^^^^^^ lift() calls are only allowed in inflight methods and closures defined in preflight + }; + class Bar { + pub inflight method() { + lift(bucket, ["get"]); // lift() call in inner class + //^^^^^^^^^^^^^^^^^^^^ lift() calls are only allowed in inflight methods and closures defined in preflight + } + } + } + + pub inflight method2() { + let b = bucket; + b.put("k", "v"); // With no explicit qualification this should be an error + //^ Expression of type "Bucket" references an unknown preflight object + } +} diff --git a/examples/tests/valid/explicit_lift_qualification.test.w b/examples/tests/valid/explicit_lift_qualification.test.w new file mode 100644 index 00000000000..4089d6b6953 --- /dev/null +++ b/examples/tests/valid/explicit_lift_qualification.test.w @@ -0,0 +1,43 @@ +bring cloud; + +let bucket = new cloud.Bucket(); +bucket.addObject("k", "value"); + +let put_and_list = ["put", "list"]; + +class Foo { + pub inflight mehtod() { + lift(bucket, put_and_list); // Qualify `bucket` with a preflight expression + lift(bucket, ["delete"]); // Qualify `bucket` with `delete` via literal + let b = bucket; // Assign `bucket` to an inflight variable + + // `put` should work on `b` since we explicitly qualified `bucket` with `put` + // no error generated here because of use of `lift()` in this method + b.put("k2", "value2"); + + // validate `put` worked and that we can also `list` + assert(b.list() == ["k", "k2"]); + + // Validate `delete` works + b.delete("k2"); + assert(bucket.tryGet("k2") == nil); + } +} + +let foo = new Foo(); + +test "explicit method lift qualification" { + foo.mehtod(); +} + +// Similar to the above test, but using a closure +let inflight_closure = inflight () => { + lift(bucket, ["put"]); + let b = bucket; + b.put("k3", "value3"); // Use inflight expression to access explicitly qualified `bucket` + assert(bucket.get("k3") == "value3"); +}; + +test "explicit closure lift qualification" { + inflight_closure(); +} diff --git a/libs/wingc/src/ast.rs b/libs/wingc/src/ast.rs index 6f45c3044a1..8487093d516 100644 --- a/libs/wingc/src/ast.rs +++ b/libs/wingc/src/ast.rs @@ -304,37 +304,6 @@ pub struct Stmt { pub idx: usize, } -#[derive(Debug)] -pub enum UtilityFunctions { - Log, - Assert, - UnsafeCast, - Nodeof, -} - -impl UtilityFunctions { - /// Returns all utility functions. - pub fn all() -> Vec { - vec![ - UtilityFunctions::Log, - UtilityFunctions::Assert, - UtilityFunctions::UnsafeCast, - UtilityFunctions::Nodeof, - ] - } -} - -impl Display for UtilityFunctions { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - UtilityFunctions::Log => write!(f, "log"), - UtilityFunctions::Assert => write!(f, "assert"), - UtilityFunctions::UnsafeCast => write!(f, "unsafeCast"), - UtilityFunctions::Nodeof => write!(f, "nodeof"), - } - } -} - #[derive(Debug)] pub struct ElifBlock { pub condition: Expr, diff --git a/libs/wingc/src/jsify.rs b/libs/wingc/src/jsify.rs index 16406647d00..d60ae2aec7e 100644 --- a/libs/wingc/src/jsify.rs +++ b/libs/wingc/src/jsify.rs @@ -1855,8 +1855,15 @@ impl<'a> JSifier<'a> { for (method_name, method_qual) in lift_qualifications { bind_method.open(format!("\"{method_name}\": [",)); for (code, method_lift_qual) in method_qual { - let ops_strings = method_lift_qual.ops.iter().map(|op| format!("\"{}\"", op)).join(", "); - bind_method.line(format!("[{code}, [{ops_strings}]],",)); + let ops = method_lift_qual.ops.iter().join(", "); + // To keep the code concise treat no ops, single op and multiple ops differenly here, although the multiple ops is the generic case + if method_lift_qual.ops.len() == 0 { + bind_method.line(format!("[{code}, []],")); + } else if method_lift_qual.ops.len() == 1 { + bind_method.line(format!("[{code}, {ops}],")); + } else { + bind_method.line(format!("[{code}, [].concat({ops})],")); + } } bind_method.close("],"); } diff --git a/libs/wingc/src/jsify/snapshots/base_class_lift_indirect.snap b/libs/wingc/src/jsify/snapshots/base_class_lift_indirect.snap index f78004d09d4..649c199d1e0 100644 --- a/libs/wingc/src/jsify/snapshots/base_class_lift_indirect.snap +++ b/libs/wingc/src/jsify/snapshots/base_class_lift_indirect.snap @@ -106,7 +106,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "put": [ - [this.b, ["list", "put"]], + [this.b, [].concat(["put"], ["list"])], ], "$inflight_init": [ [this.b, []], diff --git a/libs/wingc/src/jsify/snapshots/calls_methods_on_preflight_object.snap b/libs/wingc/src/jsify/snapshots/calls_methods_on_preflight_object.snap index 53f3b82011e..0f0f4e5b970 100644 --- a/libs/wingc/src/jsify/snapshots/calls_methods_on_preflight_object.snap +++ b/libs/wingc/src/jsify/snapshots/calls_methods_on_preflight_object.snap @@ -78,7 +78,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "handle": [ - [b, ["list", "put"]], + [b, [].concat(["put"], ["list"])], ], "$inflight_init": [ [b, []], diff --git a/libs/wingc/src/jsify/snapshots/fail_unqualified_lift.snap b/libs/wingc/src/jsify/snapshots/fail_unqualified_lift.snap index ef45ec54a2a..d663f71bbfd 100644 --- a/libs/wingc/src/jsify/snapshots/fail_unqualified_lift.snap +++ b/libs/wingc/src/jsify/snapshots/fail_unqualified_lift.snap @@ -2,4 +2,4 @@ source: libs/wingc/src/jsify/tests.rs --- ## Errors -Expression of type "Queue" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) 7:6 +Expression of type "Queue" references an unknown preflight object, can't qualify its capabilities. Use `lift()` to explicitly qualify the preflight object to disable this error. 7:6 diff --git a/libs/wingc/src/jsify/snapshots/fail_unqualified_lift_as_arg.snap b/libs/wingc/src/jsify/snapshots/fail_unqualified_lift_as_arg.snap index 1115197e304..2ce6bfcd445 100644 --- a/libs/wingc/src/jsify/snapshots/fail_unqualified_lift_as_arg.snap +++ b/libs/wingc/src/jsify/snapshots/fail_unqualified_lift_as_arg.snap @@ -2,4 +2,4 @@ source: libs/wingc/src/jsify/tests.rs --- ## Errors -Expression of type "Queue" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) 6:6 +Expression of type "Queue" references an unknown preflight object, can't qualify its capabilities. Use `lift()` to explicitly qualify the preflight object to disable this error. 6:6 diff --git a/libs/wingc/src/jsify/snapshots/fail_unqualified_lift_element_from_collection_of_objects.snap b/libs/wingc/src/jsify/snapshots/fail_unqualified_lift_element_from_collection_of_objects.snap index 66138dd01b6..55ca5b933c7 100644 --- a/libs/wingc/src/jsify/snapshots/fail_unqualified_lift_element_from_collection_of_objects.snap +++ b/libs/wingc/src/jsify/snapshots/fail_unqualified_lift_element_from_collection_of_objects.snap @@ -2,4 +2,4 @@ source: libs/wingc/src/jsify/tests.rs --- ## Errors -Expression of type "Bucket" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) 6:6 +Expression of type "Bucket" references an unknown preflight object, can't qualify its capabilities. Use `lift()` to explicitly qualify the preflight object to disable this error. 6:6 diff --git a/libs/wingc/src/jsify/snapshots/fail_unqualified_lift_return.snap b/libs/wingc/src/jsify/snapshots/fail_unqualified_lift_return.snap index d700f7e5a34..a3165260454 100644 --- a/libs/wingc/src/jsify/snapshots/fail_unqualified_lift_return.snap +++ b/libs/wingc/src/jsify/snapshots/fail_unqualified_lift_return.snap @@ -2,4 +2,4 @@ source: libs/wingc/src/jsify/tests.rs --- ## Errors -Expression of type "Queue" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) 11:6 +Expression of type "Queue" references an unknown preflight object, can't qualify its capabilities. Use `lift()` to explicitly qualify the preflight object to disable this error. 11:6 diff --git a/libs/wingc/src/jsify/snapshots/preflight_object.snap b/libs/wingc/src/jsify/snapshots/preflight_object.snap index e6d69f3c784..203f9e4fc28 100644 --- a/libs/wingc/src/jsify/snapshots/preflight_object.snap +++ b/libs/wingc/src/jsify/snapshots/preflight_object.snap @@ -130,7 +130,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "handle": [ - [pf_obj, ["goodbye", "hello"]], + [pf_obj, [].concat(["hello"], ["goodbye"])], ], "$inflight_init": [ [pf_obj, []], diff --git a/libs/wingc/src/jsify/snapshots/preflight_object_with_operations.snap b/libs/wingc/src/jsify/snapshots/preflight_object_with_operations.snap index 2af7944c3cb..78cca399191 100644 --- a/libs/wingc/src/jsify/snapshots/preflight_object_with_operations.snap +++ b/libs/wingc/src/jsify/snapshots/preflight_object_with_operations.snap @@ -77,7 +77,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "handle": [ - [b, ["list", "put"]], + [b, [].concat(["list"], ["put"])], ], "$inflight_init": [ [b, []], diff --git a/libs/wingc/src/jsify/snapshots/reference_preflight_field_from_inflight_expr.snap b/libs/wingc/src/jsify/snapshots/reference_preflight_field_from_inflight_expr.snap index 04c1b4dbdd9..7370ff0727b 100644 --- a/libs/wingc/src/jsify/snapshots/reference_preflight_field_from_inflight_expr.snap +++ b/libs/wingc/src/jsify/snapshots/reference_preflight_field_from_inflight_expr.snap @@ -3,4 +3,4 @@ source: libs/wingc/src/jsify/tests.rs --- ## Errors Can't access preflight member "x" on inflight instance of type "A" 9:14 -Expression of type "A" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) 9:12 +Expression of type "A" references an unknown preflight object, can't qualify its capabilities. Use `lift()` to explicitly qualify the preflight object to disable this error. 9:12 diff --git a/libs/wingc/src/jsify/snapshots/reference_preflight_fields.snap b/libs/wingc/src/jsify/snapshots/reference_preflight_fields.snap index acf17e637c4..f6356ca41fd 100644 --- a/libs/wingc/src/jsify/snapshots/reference_preflight_fields.snap +++ b/libs/wingc/src/jsify/snapshots/reference_preflight_fields.snap @@ -100,7 +100,7 @@ class $Root extends $stdlib.std.Resource { [this.s, ["length"]], ], "bam": [ - [this.b, ["get", "put"]], + [this.b, [].concat(["put"], ["get"])], ], "$inflight_init": [ [this.b, []], diff --git a/libs/wingc/src/lifting.rs b/libs/wingc/src/lifting.rs index cdd3565b524..69af9b1f7ca 100644 --- a/libs/wingc/src/lifting.rs +++ b/libs/wingc/src/lifting.rs @@ -1,6 +1,7 @@ use crate::{ ast::{ - Class, Expr, ExprKind, FunctionBody, FunctionDefinition, Phase, Reference, Scope, Stmt, Symbol, UserDefinedType, + ArgList, CalleeKind, Class, Expr, ExprKind, FunctionBody, FunctionDefinition, Phase, Reference, Scope, Stmt, + StmtKind, Symbol, UserDefinedType, }, comp_ctx::{CompilationContext, CompilationPhase}, diagnostic::{report_diagnostic, Diagnostic}, @@ -9,7 +10,7 @@ use crate::{ lifts::{Liftable, Lifts}, resolve_user_defined_type, symbol_env::LookupResult, - ClassLike, ResolveSource, SymbolKind, TypeRef, CLOSURE_CLASS_HANDLE_METHOD, + ClassLike, ResolveSource, SymbolKind, TypeRef, UtilityFunctions, CLOSURE_CLASS_HANDLE_METHOD, }, visit::{self, Visit}, visit_context::{VisitContext, VisitorWithContext}, @@ -19,6 +20,7 @@ pub struct LiftVisitor<'a> { ctx: VisitContext, jsify: &'a JSifier<'a>, lifts_stack: Vec, + in_inner_inflight_class: usize, } impl<'a> LiftVisitor<'a> { @@ -27,6 +29,7 @@ impl<'a> LiftVisitor<'a> { jsify: jsifier, ctx: VisitContext::new(), lifts_stack: vec![], + in_inner_inflight_class: 0, } } @@ -151,6 +154,12 @@ impl<'a> LiftVisitor<'a> { udt_js } } + + // Used for generating a js array represtining a lift qualificaiton (an operation done on a lifted preflight object) + // lift qualifcations are in array format so multiple ops can be bunched together in some cases. + fn jsify_symbol_to_op_array(&self, symb: &Symbol) -> String { + format!("[\"{symb}\"]") + } } impl<'a> Visit<'a> for LiftVisitor<'a> { @@ -197,13 +206,16 @@ impl<'a> Visit<'a> for LiftVisitor<'a> { return; } + // If the expression is a call to the `lift` builtin, use this opportunity to qualify the lift + v.check_explicit_lift(node); + // Inflight expressions that evaluate to a preflight type are currently unsupported because // we can't determine exactly which preflight object is being accessed and therefore can't // qualify the original lift expression. - if expr_phase == Phase::Inflight && expr_type.is_preflight_class() && v.ctx.current_property().is_some() { + if expr_phase == Phase::Inflight && expr_type.is_preflight_class() && v.ctx.current_property().is_some() && !v.ignore_unknown_preflight_object_error() { report_diagnostic(Diagnostic { message: format!( - "Expression of type \"{expr_type}\" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details)" + "Expression of type \"{expr_type}\" references an unknown preflight object, can't qualify its capabilities. Use `lift()` to explicitly qualify the preflight object to disable this error." ), span: Some(node.span.clone()), annotations: vec![], @@ -214,9 +226,7 @@ impl<'a> Visit<'a> for LiftVisitor<'a> { //--------------- // LIFT if expr_phase == Phase::Preflight { - // jsify the expression so we can get the preflight code - let code = v.jsify_expr(&node); - + // Get the property being accessed on the preflight expression, this is used to qualify the lift let property = if let Some(property) = v.ctx.current_property() { Some(property) } else if expr_type.is_closure() { @@ -233,9 +243,17 @@ impl<'a> Visit<'a> for LiftVisitor<'a> { return; } + // jsify the expression so we can get the preflight code + let code = v.jsify_expr(&node); + let mut lifts = v.lifts_stack.pop().unwrap(); let is_field = code.contains("this."); // TODO: starts_with? - lifts.lift(v.ctx.current_method().map(|(m,_)|m), property, &code); + lifts.lift( + v.ctx.current_method().map(|(m,_)|m).expect("a method"), + property.map(|p| v.jsify_symbol_to_op_array(&p)), + &code, + false + ); lifts.capture(&Liftable::Expr(node.id), &code, is_field); v.lifts_stack.push(lifts); return; @@ -295,7 +313,12 @@ impl<'a> Visit<'a> for LiftVisitor<'a> { } let mut lifts = self.lifts_stack.pop().unwrap(); - lifts.lift(self.ctx.current_method().map(|(m, _)| m), property, &code); + lifts.lift( + self.ctx.current_method().map(|(m, _)| m).expect("a method"), + property.map(|p| self.jsify_symbol_to_op_array(&p)), + &code, + false, + ); self.lifts_stack.push(lifts); } @@ -320,66 +343,108 @@ impl<'a> Visit<'a> for LiftVisitor<'a> { fn visit_function_definition(&mut self, node: &'a FunctionDefinition) { match &node.body { FunctionBody::Statements(scope) => { - self.ctx.push_function_definition( - node.name.as_ref(), - &node.signature, - node.is_static, - self.jsify.types.get_scope_env(&scope), - ); + // If this is a method (of a non-inner inflight class), make sure there are no `lift()` calls that aren't at the top of the method + if node.name.is_some() && self.in_inner_inflight_class == 0 { + // Skip all statments that are a lift call and then search to see if there are further lift calls + let stmts = scope.statements.iter(); + let lift_stmts = stmts + .skip_while(|s| { + if let StmtKind::Expression(expr) = &s.kind { + return Self::is_lift_builtin_call(expr).is_some(); + } + false + }) + .filter( + // If we find a lift call after the first non-lift call statement, report an error + |s| { + if let StmtKind::Expression(expr) = &s.kind { + return Self::is_lift_builtin_call(expr).is_some(); + } + false + }, + ); + + for lift_stmt in lift_stmts { + report_diagnostic(Diagnostic { + span: Some(lift_stmt.span.clone()), + message: "lift() calls must be at the top of the method".to_string(), + annotations: vec![], + hints: vec![], + }); + } + } else { + // This isn't a method, don't allow any lift statments + let lift_stmts = scope.statements.iter().filter(|s| { + if let StmtKind::Expression(expr) = &s.kind { + return Self::is_lift_builtin_call(expr).is_some(); + } + false + }); + for lift_stmt in lift_stmts { + report_diagnostic(Diagnostic { + span: Some(lift_stmt.span.clone()), + message: "lift() calls are only allowed in inflight methods and closures defined in preflight" + .to_string(), + annotations: vec![], + hints: vec![], + }); + } + } + + // If we're in an inner inflight class then we don't need to track this inner method since lifts are + // collected for methods of classes defined preflight. + if self.in_inner_inflight_class == 0 { + self.ctx.push_function_definition( + node.name.as_ref(), + &node.signature, + node.is_static, + self.jsify.types.get_scope_env(&scope), + ); + } visit::visit_function_definition(self, node); - self.ctx.pop_function_definition(); + + if self.in_inner_inflight_class == 0 { + self.ctx.pop_function_definition(); + } } FunctionBody::External(_) => visit::visit_function_definition(self, node), } } fn visit_class(&mut self, node: &'a Class) { - // nothing to do if we are emitting an inflight class from within an inflight scope - if self.ctx.current_phase() == Phase::Inflight && node.phase == Phase::Inflight { - self.visit_symbol(&node.name); + let in_inner_inflight_class = self.ctx.current_phase() == Phase::Inflight && node.phase == Phase::Inflight; + if in_inner_inflight_class { + // nothing to do if we are emitting an inflight class from within an inflight scope: + // inner inflight classes collect lifts in their outer class, just mark we're in such a class and do nothing + self.in_inner_inflight_class += 1; + } else { + self.ctx.push_class(node); - visit::visit_function_definition(self, &node.initializer); - visit::visit_function_definition(self, &node.inflight_initializer); + self.lifts_stack.push(Lifts::new()); - for field in node.fields.iter() { - self.visit_symbol(&field.name); - self.visit_type_annotation(&field.member_type); - } - for (name, def) in node.methods.iter() { - self.visit_symbol(&name); - visit::visit_function_definition(self, &def); - } if let Some(parent) = &node.parent { - self.visit_user_defined_type(&parent); + let mut lifts = self.lifts_stack.pop().unwrap(); + lifts.capture(&Liftable::Type(parent.clone()), &self.jsify_udt(&parent), false); + self.lifts_stack.push(lifts); } - for interface in node.implements.iter() { - self.visit_user_defined_type(&interface); - } - return; - } - - self.ctx.push_class(node); - - self.lifts_stack.push(Lifts::new()); - - if let Some(parent) = &node.parent { - let mut lifts = self.lifts_stack.pop().unwrap(); - lifts.capture(&Liftable::Type(parent.clone()), &self.jsify_udt(&parent), false); - self.lifts_stack.push(lifts); } visit::visit_class(self, node); - self.ctx.pop_class(); - - let lifts = self.lifts_stack.pop().expect("Unable to pop class tokens"); + if in_inner_inflight_class { + self.in_inner_inflight_class -= 1; + } else { + let lifts = self.lifts_stack.pop().expect("Unable to pop class tokens"); - if let Some(env) = self.ctx.current_env() { - if let Some(mut t) = resolve_user_defined_type(&UserDefinedType::for_class(node), env, 0).ok() { - let mut_class = t.as_class_mut().unwrap(); - mut_class.set_lifts(lifts); + if let Some(env) = self.ctx.current_env() { + if let Some(mut t) = resolve_user_defined_type(&UserDefinedType::for_class(node), env, 0).ok() { + let mut_class = t.as_class_mut().unwrap(); + mut_class.set_lifts(lifts); + } } + + self.ctx.pop_class(); } } @@ -398,6 +463,99 @@ impl<'a> Visit<'a> for LiftVisitor<'a> { } } +impl LiftVisitor<'_> { + /// Helper function to check if the current method has explicit lifts. If it does then ignore + /// inflight expressions that reference unknown preflight objects assuming that the explicit lifts + /// qualify the capabilities of the preflight objects correctly. + fn ignore_unknown_preflight_object_error(&mut self) -> bool { + let lifts = self.lifts_stack.pop().expect("lifts"); + let current_method = self.ctx.current_method().map(|(m, _)| m).expect("a method"); + let res = lifts.has_explicit_lifts(¤t_method.name); + self.lifts_stack.push(lifts); + res + } + + fn is_lift_builtin_call(node: &Expr) -> Option<&ArgList> { + if let ExprKind::Call { + callee: CalleeKind::Expr(callee_expr), + arg_list, + } = &node.kind + { + if let ExprKind::Reference(Reference::Identifier(Symbol { name, .. })) = &callee_expr.kind { + if UtilityFunctions::Lift.to_string().eq(name) { + return Some(arg_list); + } + } + } + + None + } + + /// Helper function to check if the given expression is a call to the `lift` builtin. + /// If it is, we'll qualify the passed preflight object based on the passed capabilities. + fn check_explicit_lift(&mut self, node: &Expr) { + let Some(arg_list) = Self::is_lift_builtin_call(node) else { + return; + }; + + // Get the preflight object's expression, which is the first argument to the `lift` call + let preflight_object_expr = &arg_list.pos_args[0]; + + // Make sure this really is a preflight expression + let obj_phase = self + .jsify + .types + .get_expr_phase(preflight_object_expr) + .expect("an expr phase"); + if obj_phase != Phase::Preflight { + report_diagnostic(Diagnostic { + span: Some(preflight_object_expr.span.clone()), + message: format!( + "Expected a preflight object as first argument to `lift` builtin, found {obj_phase} expression instead" + ), + annotations: vec![], + hints: vec![], + }); + return; + } + + // Make sure the second argument, the qualifications, isn't an inflight expression since we'll need to evaluate it preflihgt + let qualifications_expr = &arg_list.pos_args[1]; + let qualifications_phase = self + .jsify + .types + .get_expr_phase(qualifications_expr) + .expect("an expr phase"); + if qualifications_phase == Phase::Inflight { + report_diagnostic(Diagnostic { + span: Some(qualifications_expr.span.clone()), + message: "Qualification list must not contain any inflight elements".to_string(), + annotations: vec![], + hints: vec![], + }); + return; + } + + // This seems like a valid explicit lift qualification, add it + + // jsify the expression so we can get the preflight code + let code = self.jsify_expr(&preflight_object_expr); + + // jsify the explicit lift qualifications + let qualification_code = self.jsify_expr(qualifications_expr); + + let mut lifts = self.lifts_stack.pop().unwrap(); + + lifts.lift( + self.ctx.current_method().map(|(m, _)| m).expect("a method"), + Some(qualification_code), + &code, + true, + ); + self.lifts_stack.push(lifts); + } +} + /// Check if an expression is a reference to an inflight field (`this.`). /// in this case, we don't need to lift the field because it is already available fn is_inflight_field(expr: &Expr, expr_type: TypeRef, property: &Option) -> bool { diff --git a/libs/wingc/src/lsp/snapshots/completions/empty.snap b/libs/wingc/src/lsp/snapshots/completions/empty.snap index 0efb78a34f6..94861948cb9 100644 --- a/libs/wingc/src/lsp/snapshots/completions/empty.snap +++ b/libs/wingc/src/lsp/snapshots/completions/empty.snap @@ -20,6 +20,18 @@ source: libs/wingc/src/lsp/completions.rs command: title: triggerParameterHints command: editor.action.triggerParameterHints +- label: lift + kind: 3 + detail: "inflight (preflightObject: Resource, qualifications: Array): void" + documentation: + kind: markdown + value: "```wing\nlift: inflight (preflightObject: Resource, qualifications: Array): void\n```\n---\nExplicitly apply qualifications to a preflight object used in the current method/function\n### Parameters\n- `preflightObject` — `Resource` — The preflight object to qualify\n- `qualifications` — `Array` — \n\t\t\t\t\t\t\tThe qualifications to apply to the preflight object.\n\n\t\t\t\t\t\t\tThis is an array of strings denoting members of the object that are accessed in the current method/function.\n\n\t\t\t\t\t\t\tFor example, if the method accesses the `push` and `pop` members of a `cloud.Queue` object, the qualifications should be `[\"push\", \"pop\"]`." + sortText: cc|lift + insertText: lift($1) + insertTextFormat: 2 + command: + title: triggerParameterHints + command: editor.action.triggerParameterHints - label: log kind: 3 detail: "(message: str): void" diff --git a/libs/wingc/src/lsp/snapshots/completions/hide_parent_symbols_defined_later.snap b/libs/wingc/src/lsp/snapshots/completions/hide_parent_symbols_defined_later.snap index 549cc206761..65a1305e669 100644 --- a/libs/wingc/src/lsp/snapshots/completions/hide_parent_symbols_defined_later.snap +++ b/libs/wingc/src/lsp/snapshots/completions/hide_parent_symbols_defined_later.snap @@ -27,6 +27,18 @@ source: libs/wingc/src/lsp/completions.rs command: title: triggerParameterHints command: editor.action.triggerParameterHints +- label: lift + kind: 3 + detail: "inflight (preflightObject: Resource, qualifications: Array): void" + documentation: + kind: markdown + value: "```wing\nlift: inflight (preflightObject: Resource, qualifications: Array): void\n```\n---\nExplicitly apply qualifications to a preflight object used in the current method/function\n### Parameters\n- `preflightObject` — `Resource` — The preflight object to qualify\n- `qualifications` — `Array` — \n\t\t\t\t\t\t\tThe qualifications to apply to the preflight object.\n\n\t\t\t\t\t\t\tThis is an array of strings denoting members of the object that are accessed in the current method/function.\n\n\t\t\t\t\t\t\tFor example, if the method accesses the `push` and `pop` members of a `cloud.Queue` object, the qualifications should be `[\"push\", \"pop\"]`." + sortText: cc|lift + insertText: lift($1) + insertTextFormat: 2 + command: + title: triggerParameterHints + command: editor.action.triggerParameterHints - label: log kind: 3 detail: "(message: str): void" diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index 4f7623860b5..b211b79f3c0 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -8,7 +8,7 @@ pub(crate) mod type_reference_transform; use crate::ast::{ self, AccessModifier, AssignmentKind, BringSource, CalleeKind, ClassField, ExprId, FunctionDefinition, IfLet, New, - TypeAnnotationKind, UtilityFunctions, + TypeAnnotationKind, }; use crate::ast::{ ArgList, BinaryOperator, Class as AstClass, Elifs, Enum as AstEnum, Expr, ExprKind, FunctionBody, @@ -1740,6 +1740,28 @@ impl Types { } } +/// Enum of builtin functions, this are defined as hard coded AST nodes in `add_builtins` +#[derive(Debug)] +pub enum UtilityFunctions { + Log, + Assert, + UnsafeCast, + Nodeof, + Lift, +} + +impl Display for UtilityFunctions { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UtilityFunctions::Log => write!(f, "log"), + UtilityFunctions::Assert => write!(f, "assert"), + UtilityFunctions::UnsafeCast => write!(f, "unsafeCast"), + UtilityFunctions::Nodeof => write!(f, "nodeof"), + UtilityFunctions::Lift => write!(f, "lift"), + } + } +} + pub struct TypeChecker<'a> { types: &'a mut Types, @@ -2021,9 +2043,44 @@ impl<'a> TypeChecker<'a> { }), scope, ); + + let str_array_type = self.types.add_type(Type::Array(self.types.string())); + self.add_builtin( + &UtilityFunctions::Lift.to_string(), + Type::Function(FunctionSignature { + this_type: None, + parameters: vec![ + FunctionParameter { + name: "preflightObject".into(), + typeref: self.types.resource_base_type(), + docs: Docs::with_summary("The preflight object to qualify"), + variadic: false, + }, + FunctionParameter { + name: "qualifications".into(), + typeref: str_array_type, + docs: Docs::with_summary(" + The qualifications to apply to the preflight object.\n + This is an array of strings denoting members of the object that are accessed in the current method/function.\n + For example, if the method accesses the `push` and `pop` members of a `cloud.Queue` object, the qualifications should be `[\"push\", \"pop\"]`." + ), + variadic: false, + }, + ], + return_type: self.types.void(), + phase: Phase::Inflight, + // This builtin actually compiles to nothing in JS, it's a marker that behaves like a function in the type checker + // and is used during the lifting phase to explicitly define lifts for an inflight method + js_override: Some("".to_string()), + docs: Docs::with_summary( + "Explicitly apply qualifications to a preflight object used in the current method/function", + ), + }), + scope, + ) } - pub fn add_builtin(&mut self, name: &str, typ: Type, scope: &mut Scope) { + fn add_builtin(&mut self, name: &str, typ: Type, scope: &mut Scope) { let sym = Symbol::global(name); let mut scope_env = self.types.get_scope_env(&scope); scope_env @@ -2493,9 +2550,11 @@ impl<'a> TypeChecker<'a> { (self.types.add_type(Type::Array(inner_type)), inner_type) }; - // Verify all types are the same as the inferred type + // Verify all types are the same as the inferred type and find the aggregate phase of all the items + let mut phase = Phase::Independent; for item in items { - let (t, _) = self.type_check_exp(item, env); + let (t, item_phase) = self.type_check_exp(item, env); + phase = combine_phases(phase, item_phase); if t.is_json() && !matches!(*element_type, Type::Json(Some(..))) { // This is an array of JSON, change the element type to reflect that @@ -2533,7 +2592,7 @@ impl<'a> TypeChecker<'a> { *inner = element_type; } - (container_type, env.phase) + (container_type, phase) } ExprKind::MapLiteral { fields, type_ } => { // Infer type based on either the explicit type or the value in one of the fields diff --git a/libs/wingc/src/type_check/lifts.rs b/libs/wingc/src/type_check/lifts.rs index e98b84b35d0..6ed6980ebbd 100644 --- a/libs/wingc/src/type_check/lifts.rs +++ b/libs/wingc/src/type_check/lifts.rs @@ -1,4 +1,6 @@ -use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::collections::{BTreeMap, HashMap}; + +use indexmap::IndexSet; use crate::ast::{Symbol, UserDefinedType}; @@ -30,7 +32,9 @@ pub enum Liftable { #[derive(Debug)] pub struct LiftQualification { /// The operations that qualify the lift (the property names) - pub ops: BTreeSet, + pub ops: IndexSet, + /// Whether this lift was explicitly defined via a `lift` statement. + pub explicit: bool, } /// A record that describes a lift from a class. @@ -57,29 +61,46 @@ impl Lifts { } /// Adds a lift for an expression. - pub fn lift(&mut self, method: Option, property: Option, code: &str) { - let method = method.map(|m| m.name).unwrap_or(Default::default()); - - self.add_lift(method, code, property.as_ref().map(|s| s.name.clone())); + pub fn lift(&mut self, method: Symbol, qualification: Option, code: &str, explicit: bool) { + self.add_lift( + method.to_string(), + code, + qualification.as_ref().map(|s| s.clone()), + explicit, + ); // Add a lift to the inflight initializer to signify this class requires access to that preflight object. // "this" is a special case since it's already in scope and doesn't need to be lifted. if code != "this" { - self.add_lift(CLASS_INFLIGHT_INIT_NAME.to_string(), code, None); + self.add_lift(CLASS_INFLIGHT_INIT_NAME.to_string(), code, None, explicit); } } - fn add_lift(&mut self, method: String, code: &str, property: Option) { + fn add_lift(&mut self, method: String, code: &str, qualification: Option, explicit: bool) { let lift = self .lifts_qualifications .entry(method) .or_default() .entry(code.to_string()) - .or_insert(LiftQualification { ops: BTreeSet::new() }); + .or_insert(LiftQualification { + ops: IndexSet::new(), + explicit, + }); - if let Some(op) = &property { - lift.ops.insert(op.clone()); + if let Some(op) = qualification { + lift.ops.insert(op); } + + // If there are explicit lifts in the method, then the lift is explicit. + lift.explicit |= explicit; + } + + pub fn has_explicit_lifts(&self, method: &str) -> bool { + self + .lifts_qualifications + .get(method) + .map(|lifts| lifts.values().any(|lift| lift.explicit)) + .unwrap_or(false) } /// Returns the token for a liftable. Called by the jsifier when emitting inflight code. diff --git a/tools/hangar/__snapshots__/compatibility-spy.ts.snap b/tools/hangar/__snapshots__/compatibility-spy.ts.snap index 30af07fc9ba..434fe516f82 100644 --- a/tools/hangar/__snapshots__/compatibility-spy.ts.snap +++ b/tools/hangar/__snapshots__/compatibility-spy.ts.snap @@ -622,8 +622,8 @@ exports[`peek.test.w 1`] = ` "args": { "methods": { "Counter": [ - "inc", "peek", + "inc", ], "TestRunner": [ "findTests", @@ -693,8 +693,8 @@ exports[`peek.test.w 1`] = ` "args": { "methods": { "Counter": [ - "inc", "peek", + "inc", ], "TestRunner": [ "findTests", @@ -778,8 +778,8 @@ exports[`set.test.w 1`] = ` "args": { "methods": { "Counter": [ - "peek", "set", + "peek", ], "TestRunner": [ "findTests", @@ -868,8 +868,8 @@ exports[`set.test.w 1`] = ` "args": { "methods": { "Counter": [ - "peek", "set", + "peek", ], "TestRunner": [ "findTests", diff --git a/tools/hangar/__snapshots__/invalid.ts.snap b/tools/hangar/__snapshots__/invalid.ts.snap index 70aa1aaa646..da92ba95642 100644 --- a/tools/hangar/__snapshots__/invalid.ts.snap +++ b/tools/hangar/__snapshots__/invalid.ts.snap @@ -1388,6 +1388,77 @@ error: Property not found +Tests 1 failed (1) +Test Files 1 failed (1) +Duration " +`; + +exports[`explicit_lift_qualification.test.w 1`] = ` +"error: Expected type to be \\"Resource\\", but got \\"str\\" instead + --> ../../../examples/tests/invalid/explicit_lift_qualification.test.w:14:10 + | +14 | lift(prelight_string, [\\"contains\\"]); // Explicit qualification on preflight non-class + | ^^^^^^^^^^^^^^^ Expected type to be \\"Resource\\", but got \\"str\\" instead + + +error: lift() calls must be at the top of the method + --> ../../../examples/tests/invalid/explicit_lift_qualification.test.w:10:5 + | +10 | lift(b, [\\"put\\"]); // Explicit qualification with inflight object, lift call as non first statement + | ^^^^^^^^^^^^^^^^^ lift() calls must be at the top of the method + + +error: lift() calls must be at the top of the method + --> ../../../examples/tests/invalid/explicit_lift_qualification.test.w:14:5 + | +14 | lift(prelight_string, [\\"contains\\"]); // Explicit qualification on preflight non-class + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lift() calls must be at the top of the method + + +error: lift() calls must be at the top of the method + --> ../../../examples/tests/invalid/explicit_lift_qualification.test.w:19:5 + | +19 | lift(bucket, [inflight_qualifier]); // Explicit qualification with inflight qualifiers, lift call as non first statement + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lift() calls must be at the top of the method + + +error: Expected a preflight object as first argument to \`lift\` builtin, found inflight expression instead + --> ../../../examples/tests/invalid/explicit_lift_qualification.test.w:10:10 + | +10 | lift(b, [\\"put\\"]); // Explicit qualification with inflight object, lift call as non first statement + | ^ Expected a preflight object as first argument to \`lift\` builtin, found inflight expression instead + + +error: Qualification list must not contain any inflight elements + --> ../../../examples/tests/invalid/explicit_lift_qualification.test.w:19:18 + | +19 | lift(bucket, [inflight_qualifier]); // Explicit qualification with inflight qualifiers, lift call as non first statement + | ^^^^^^^^^^^^^^^^^^^^ Qualification list must not contain any inflight elements + + +error: lift() calls are only allowed in inflight methods and closures defined in preflight + --> ../../../examples/tests/invalid/explicit_lift_qualification.test.w:24:7 + | +24 | lift(bucket, [\\"get\\"]); // lift() call in inner closure + | ^^^^^^^^^^^^^^^^^^^^^^ lift() calls are only allowed in inflight methods and closures defined in preflight + + +error: lift() calls are only allowed in inflight methods and closures defined in preflight + --> ../../../examples/tests/invalid/explicit_lift_qualification.test.w:29:9 + | +29 | lift(bucket, [\\"get\\"]); // lift() call in inner class + | ^^^^^^^^^^^^^^^^^^^^^^ lift() calls are only allowed in inflight methods and closures defined in preflight + + +error: Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities. Use \`lift()\` to explicitly qualify the preflight object to disable this error. + --> ../../../examples/tests/invalid/explicit_lift_qualification.test.w:37:5 + | +37 | b.put(\\"k\\", \\"v\\"); // With no explicit qualification this should be an error + | ^ Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities. Use \`lift()\` to explicitly qualify the preflight object to disable this error. + + + + Tests 1 failed (1) Test Files 1 failed (1) Duration " @@ -2246,25 +2317,25 @@ Duration " `; exports[`inflight_ref_explicit_ops.test.w 1`] = ` -"error: Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) +"error: Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities. Use \`lift()\` to explicitly qualify the preflight object to disable this error. --> ../../../examples/tests/invalid/inflight_ref_explicit_ops.test.w:36:5 | 36 | x.put(\\"hello\\", \\"world\\"); - | ^ Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) + | ^ Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities. Use \`lift()\` to explicitly qualify the preflight object to disable this error. -error: Expression of type \\"Queue\\" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) +error: Expression of type \\"Queue\\" references an unknown preflight object, can't qualify its capabilities. Use \`lift()\` to explicitly qualify the preflight object to disable this error. --> ../../../examples/tests/invalid/inflight_ref_explicit_ops.test.w:43:5 | 43 | q.push(\\"push!\\"); - | ^ Expression of type \\"Queue\\" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) + | ^ Expression of type \\"Queue\\" references an unknown preflight object, can't qualify its capabilities. Use \`lift()\` to explicitly qualify the preflight object to disable this error. -error: Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) +error: Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities. Use \`lift()\` to explicitly qualify the preflight object to disable this error. --> ../../../examples/tests/invalid/inflight_ref_explicit_ops.test.w:48:12 | 48 | assert(b.list().length == 0); - | ^ Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) + | ^ Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities. Use \`lift()\` to explicitly qualify the preflight object to disable this error. @@ -2275,18 +2346,18 @@ Duration " `; exports[`inflight_ref_resource_sub_method.test.w 1`] = ` -"error: Expression of type \\"Queue\\" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) +"error: Expression of type \\"Queue\\" references an unknown preflight object, can't qualify its capabilities. Use \`lift()\` to explicitly qualify the preflight object to disable this error. --> ../../../examples/tests/invalid/inflight_ref_resource_sub_method.test.w:32:5 | 32 | q.push(\\"push!\\"); - | ^ Expression of type \\"Queue\\" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) + | ^ Expression of type \\"Queue\\" references an unknown preflight object, can't qualify its capabilities. Use \`lift()\` to explicitly qualify the preflight object to disable this error. -error: Expression of type \\"Queue\\" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) +error: Expression of type \\"Queue\\" references an unknown preflight object, can't qualify its capabilities. Use \`lift()\` to explicitly qualify the preflight object to disable this error. --> ../../../examples/tests/invalid/inflight_ref_resource_sub_method.test.w:35:5 | 35 | q2.push(\\"push!\\"); - | ^^ Expression of type \\"Queue\\" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) + | ^^ Expression of type \\"Queue\\" references an unknown preflight object, can't qualify its capabilities. Use \`lift()\` to explicitly qualify the preflight object to disable this error. @@ -2297,18 +2368,18 @@ Duration " `; exports[`inflight_ref_unknown_op.test.w 1`] = ` -"error: Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) +"error: Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities. Use \`lift()\` to explicitly qualify the preflight object to disable this error. --> ../../../examples/tests/invalid/inflight_ref_unknown_op.test.w:15:5 | 15 | x.put(\\"hello\\", \\"world\\"); - | ^ Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) + | ^ Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities. Use \`lift()\` to explicitly qualify the preflight object to disable this error. -error: Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) +error: Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities. Use \`lift()\` to explicitly qualify the preflight object to disable this error. --> ../../../examples/tests/invalid/inflight_ref_unknown_op.test.w:19:5 | 19 | y.put(\\"boom\\", \\"shakalaka\\"); - | ^ Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) + | ^ Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities. Use \`lift()\` to explicitly qualify the preflight object to disable this error. @@ -3329,11 +3400,11 @@ Duration " `; exports[`resource_captures.test.w 1`] = ` -"error: Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) +"error: Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities. Use \`lift()\` to explicitly qualify the preflight object to disable this error. --> ../../../examples/tests/invalid/resource_captures.test.w:15:5 | 15 | b.put(\\"hello\\", \\"world\\"); - | ^ Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities (see https://github.com/winglang/wing/issues/76 for details) + | ^ Expression of type \\"Bucket\\" references an unknown preflight object, can't qualify its capabilities. Use \`lift()\` to explicitly qualify the preflight object to disable this error. diff --git a/tools/hangar/__snapshots__/test_corpus/valid/bucket_keys.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/bucket_keys.test.w_compile_tf-aws.md index 6ba9a4aed63..dc33a561442 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/bucket_keys.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/bucket_keys.test.w_compile_tf-aws.md @@ -103,7 +103,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "handle": [ - [b, ["list", "put"]], + [b, [].concat(["put"], ["list"])], ], "$inflight_init": [ [b, []], diff --git a/tools/hangar/__snapshots__/test_corpus/valid/calling_inflight_variants.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/calling_inflight_variants.test.w_compile_tf-aws.md index 265a30568da..49b039a4526 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/calling_inflight_variants.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/calling_inflight_variants.test.w_compile_tf-aws.md @@ -218,7 +218,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "handle": [ - [foo, ["callFn", "callFn2"]], + [foo, [].concat(["callFn"], ["callFn2"])], ], "$inflight_init": [ [foo, []], diff --git a/tools/hangar/__snapshots__/test_corpus/valid/capture_in_binary.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/capture_in_binary.test.w_compile_tf-aws.md index 665a6651af6..50b0ec169e5 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/capture_in_binary.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/capture_in_binary.test.w_compile_tf-aws.md @@ -96,7 +96,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "handle": [ - [b, ["get", "put"]], + [b, [].concat(["put"], ["get"])], [x, []], ], "$inflight_init": [ diff --git a/tools/hangar/__snapshots__/test_corpus/valid/capture_reassigable_class_field.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/capture_reassigable_class_field.test.w_compile_tf-aws.md index 5deaa47678a..0242fd1b421 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/capture_reassigable_class_field.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/capture_reassigable_class_field.test.w_compile_tf-aws.md @@ -300,7 +300,7 @@ class $Root extends $stdlib.std.Resource { return ({ "handle": [ [counter, ["peek"]], - [kv, ["get", "set"]], + [kv, [].concat(["set"], ["get"])], ], "$inflight_init": [ [counter, []], diff --git a/tools/hangar/__snapshots__/test_corpus/valid/capture_resource_and_data.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/capture_resource_and_data.test.w_compile_tf-aws.md index 32b4e74448c..ef29ca8fa97 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/capture_resource_and_data.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/capture_resource_and_data.test.w_compile_tf-aws.md @@ -113,7 +113,7 @@ class $Root extends $stdlib.std.Resource { "handle": [ [data.size, []], [queue, ["push"]], - [res, ["get", "put"]], + [res, [].concat(["put"], ["get"])], ], "$inflight_init": [ [data.size, []], diff --git a/tools/hangar/__snapshots__/test_corpus/valid/captures.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/captures.test.w_compile_tf-aws.md index b0c89a46799..0ff31be9756 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/captures.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/captures.test.w_compile_tf-aws.md @@ -678,8 +678,8 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "handle": [ - [bucket1, ["list", "publicUrl", "put"]], - [bucket2, ["get", "publicUrl"]], + [bucket1, [].concat(["put"], ["list"], ["publicUrl"])], + [bucket2, [].concat(["get"], ["publicUrl"])], [bucket3, ["get"]], ], "$inflight_init": [ diff --git a/tools/hangar/__snapshots__/test_corpus/valid/class.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/class.test.w_compile_tf-aws.md index 250ba08f852..d05d70e988d 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/class.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/class.test.w_compile_tf-aws.md @@ -546,7 +546,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "handle": [ - [c5, ["set", "x", "y"]], + [c5, [].concat(["x"], ["y"], ["set"])], ], "$inflight_init": [ [c5, []], diff --git a/tools/hangar/__snapshots__/test_corpus/valid/closure_class.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/closure_class.test.w_compile_tf-aws.md index 0c40e12ee68..6912cae53ed 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/closure_class.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/closure_class.test.w_compile_tf-aws.md @@ -135,7 +135,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "handle": [ - [fn, ["another", "handle"]], + [fn, [].concat(["handle"], ["another"])], ], "$inflight_init": [ [fn, []], diff --git a/tools/hangar/__snapshots__/test_corpus/valid/debug_env.test.w_test_sim.md b/tools/hangar/__snapshots__/test_corpus/valid/debug_env.test.w_test_sim.md index 96fe5dcd58f..7752e30e05a 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/debug_env.test.w_test_sim.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/debug_env.test.w_test_sim.md @@ -4,7 +4,7 @@ ```log [symbol environment at debug_env.test.w:7:5] level 0: { this => A } -level 1: { A => A [type], assert => (condition: bool): void, cloud => cloud [namespace], log => (message: str): void, nodeof => preflight (construct: IConstruct): Node, std => std [namespace], this => Construct, unsafeCast => (value: any): any } +level 1: { A => A [type], assert => (condition: bool): void, cloud => cloud [namespace], lift => inflight (preflightObject: Resource, qualifications: Array): void, log => (message: str): void, nodeof => preflight (construct: IConstruct): Node, std => std [namespace], this => Construct, unsafeCast => (value: any): any } ``` ## stdout.log diff --git a/tools/hangar/__snapshots__/test_corpus/valid/explicit_lift_qualification.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/explicit_lift_qualification.test.w_compile_tf-aws.md new file mode 100644 index 00000000000..ea064e2eb81 --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/valid/explicit_lift_qualification.test.w_compile_tf-aws.md @@ -0,0 +1,303 @@ +# [explicit_lift_qualification.test.w](../../../../../examples/tests/valid/explicit_lift_qualification.test.w) | compile | tf-aws + +## inflight.$Closure1-1.js +```js +"use strict"; +const $helpers = require("@winglang/sdk/lib/helpers"); +module.exports = function({ $foo }) { + class $Closure1 { + constructor({ }) { + const $obj = (...args) => this.handle(...args); + Object.setPrototypeOf($obj, this); + return $obj; + } + async handle() { + (await $foo.mehtod()); + } + } + return $Closure1; +} +//# sourceMappingURL=inflight.$Closure1-1.js.map +``` + +## inflight.$Closure2-1.js +```js +"use strict"; +const $helpers = require("@winglang/sdk/lib/helpers"); +module.exports = function({ $bucket }) { + class $Closure2 { + constructor({ }) { + const $obj = (...args) => this.handle(...args); + Object.setPrototypeOf($obj, this); + return $obj; + } + async handle() { + ; + const b = $bucket; + (await b.put("k3", "value3")); + $helpers.assert($helpers.eq((await $bucket.get("k3")), "value3"), "bucket.get(\"k3\") == \"value3\""); + } + } + return $Closure2; +} +//# sourceMappingURL=inflight.$Closure2-1.js.map +``` + +## inflight.$Closure3-1.js +```js +"use strict"; +const $helpers = require("@winglang/sdk/lib/helpers"); +module.exports = function({ $inflight_closure }) { + class $Closure3 { + constructor({ }) { + const $obj = (...args) => this.handle(...args); + Object.setPrototypeOf($obj, this); + return $obj; + } + async handle() { + (await $inflight_closure()); + } + } + return $Closure3; +} +//# sourceMappingURL=inflight.$Closure3-1.js.map +``` + +## inflight.Foo-1.js +```js +"use strict"; +const $helpers = require("@winglang/sdk/lib/helpers"); +module.exports = function({ $bucket, $put_and_list }) { + class Foo { + constructor({ }) { + } + async mehtod() { + ; + ; + const b = $bucket; + (await b.put("k2", "value2")); + $helpers.assert($helpers.eq((await b.list()), ["k", "k2"]), "b.list() == [\"k\", \"k2\"]"); + (await b.delete("k2")); + $helpers.assert($helpers.eq((await $bucket.tryGet("k2")), undefined), "bucket.tryGet(\"k2\") == nil"); + } + } + return Foo; +} +//# sourceMappingURL=inflight.Foo-1.js.map +``` + +## main.tf.json +```json +{ + "//": { + "metadata": { + "backend": "local", + "stackName": "root", + "version": "0.20.3" + }, + "outputs": {} + }, + "provider": { + "aws": [ + {} + ] + }, + "resource": { + "aws_s3_bucket": { + "Bucket": { + "//": { + "metadata": { + "path": "root/Default/Default/Bucket/Default", + "uniqueId": "Bucket" + } + }, + "bucket_prefix": "bucket-c88fdc5f-", + "force_destroy": false + } + }, + "aws_s3_object": { + "Bucket_S3Object-k_D126CC53": { + "//": { + "metadata": { + "path": "root/Default/Default/Bucket/S3Object-k", + "uniqueId": "Bucket_S3Object-k_D126CC53" + } + }, + "bucket": "${aws_s3_bucket.Bucket.bucket}", + "content": "value", + "key": "k" + } + } + } +} +``` + +## preflight.js +```js +"use strict"; +const $stdlib = require('@winglang/sdk'); +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 cloud = $stdlib.cloud; +class $Root extends $stdlib.std.Resource { + constructor($scope, $id) { + super($scope, $id); + class Foo extends $stdlib.std.Resource { + constructor($scope, $id, ) { + super($scope, $id); + } + static _toInflightType() { + return ` + require("${$helpers.normalPath(__dirname)}/inflight.Foo-1.js")({ + $bucket: ${$stdlib.core.liftObject(bucket)}, + $put_and_list: ${$stdlib.core.liftObject(put_and_list)}, + }) + `; + } + _toInflight() { + return ` + (await (async () => { + const FooClient = ${Foo._toInflightType()}; + const client = new FooClient({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `; + } + get _liftMap() { + return ({ + "mehtod": [ + [bucket, [].concat(put_and_list, ["delete"], ["tryGet"])], + [put_and_list, []], + ], + "$inflight_init": [ + [bucket, []], + [put_and_list, []], + ], + }); + } + } + 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.js")({ + $foo: ${$stdlib.core.liftObject(foo)}, + }) + `; + } + _toInflight() { + return ` + (await (async () => { + const $Closure1Client = ${$Closure1._toInflightType()}; + const client = new $Closure1Client({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `; + } + get _liftMap() { + return ({ + "handle": [ + [foo, ["mehtod"]], + ], + "$inflight_init": [ + [foo, []], + ], + }); + } + } + class $Closure2 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.$Closure2-1.js")({ + $bucket: ${$stdlib.core.liftObject(bucket)}, + }) + `; + } + _toInflight() { + return ` + (await (async () => { + const $Closure2Client = ${$Closure2._toInflightType()}; + const client = new $Closure2Client({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `; + } + get _liftMap() { + return ({ + "handle": [ + [bucket, [].concat(["put"], ["get"])], + ], + "$inflight_init": [ + [bucket, []], + ], + }); + } + } + 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-1.js")({ + $inflight_closure: ${$stdlib.core.liftObject(inflight_closure)}, + }) + `; + } + _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": [ + [inflight_closure, ["handle"]], + ], + "$inflight_init": [ + [inflight_closure, []], + ], + }); + } + } + const bucket = this.node.root.new("@winglang/sdk.cloud.Bucket", cloud.Bucket, this, "Bucket"); + (bucket.addObject("k", "value")); + const put_and_list = ["put", "list"]; + const foo = new Foo(this, "Foo"); + this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:explicit method lift qualification", new $Closure1(this, "$Closure1")); + const inflight_closure = new $Closure2(this, "$Closure2"); + this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:explicit closure lift qualification", new $Closure3(this, "$Closure3")); + } +} +const $PlatformManager = new $stdlib.platform.PlatformManager({platformPaths: $platforms}); +const $APP = $PlatformManager.createApp({ outdir: $outdir, name: "explicit_lift_qualification.test", rootConstruct: $Root, isTestEnvironment: $wing_is_test, entrypointDir: process.env['WING_SOURCE_DIR'], rootId: process.env['WING_ROOT_ID'] }); +$APP.synth(); +//# sourceMappingURL=preflight.js.map +``` + diff --git a/tools/hangar/__snapshots__/test_corpus/valid/explicit_lift_qualification.test.w_test_sim.md b/tools/hangar/__snapshots__/test_corpus/valid/explicit_lift_qualification.test.w_test_sim.md new file mode 100644 index 00000000000..6078d33c7e7 --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/valid/explicit_lift_qualification.test.w_test_sim.md @@ -0,0 +1,13 @@ +# [explicit_lift_qualification.test.w](../../../../../examples/tests/valid/explicit_lift_qualification.test.w) | test | sim + +## stdout.log +```log +pass ─ explicit_lift_qualification.test.wsim » root/env0/test:explicit method lift qualification +pass ─ explicit_lift_qualification.test.wsim » root/env1/test:explicit closure lift qualification + + +Tests 2 passed (2) +Test Files 1 passed (1) +Duration +``` + diff --git a/tools/hangar/__snapshots__/test_corpus/valid/extern_implementation.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/extern_implementation.test.w_compile_tf-aws.md index a6ecbf35cf2..e9460d4823d 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/extern_implementation.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/extern_implementation.test.w_compile_tf-aws.md @@ -148,7 +148,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "call": [ - [Foo, ["getData", "getUuid", "regexInflight"]], + [Foo, [].concat(["regexInflight"], ["getUuid"], ["getData"])], ], "$inflight_init": [ [Foo, []], diff --git a/tools/hangar/__snapshots__/test_corpus/valid/inflight_class_outside_inflight_closure.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/inflight_class_outside_inflight_closure.test.w_compile_tf-aws.md index 87dc7f77b94..aba3f04abc0 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/inflight_class_outside_inflight_closure.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/inflight_class_outside_inflight_closure.test.w_compile_tf-aws.md @@ -98,10 +98,10 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "add": [ - [this, ["lhs", "rhs"]], + [this, [].concat(["lhs"], ["rhs"])], ], "$inflight_init": [ - [this, ["lhs", "rhs"]], + [this, [].concat(["lhs"], ["rhs"])], ], "lhs": [ ], diff --git a/tools/hangar/__snapshots__/test_corpus/valid/inflight_handler_singleton.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/inflight_handler_singleton.test.w_compile_tf-aws.md index 2400f91c48d..f11c5f4fe99 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/inflight_handler_singleton.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/inflight_handler_singleton.test.w_compile_tf-aws.md @@ -615,7 +615,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "handle": [ - [foo, ["get", "inc"]], + [foo, [].concat(["inc"], ["get"])], ], "$inflight_init": [ [foo, []], @@ -652,7 +652,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "handle": [ - [fn3, ["invoke", "invokeAsync"]], + [fn3, [].concat(["invokeAsync"], ["invoke"])], ], "$inflight_init": [ [fn3, []], 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 a0e4b3c093d..49ccade7dad 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 @@ -222,7 +222,7 @@ class $Root extends $stdlib.std.Resource { "get_six": [ ], "$inflight_init": [ - [this, ["field1", "field2", "get_six"]], + [this, [].concat(["field1"], ["get_six"], ["field2"])], ], "field1": [ ], diff --git a/tools/hangar/__snapshots__/test_corpus/valid/inheritance_class_inflight.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/inheritance_class_inflight.test.w_compile_tf-aws.md index 06174909c5f..3c4ce05a66c 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/inheritance_class_inflight.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/inheritance_class_inflight.test.w_compile_tf-aws.md @@ -188,7 +188,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "handle": [ - [foo, ["bang", "bug", "over_inflight"]], + [foo, [].concat(["bang"], ["bug"], ["over_inflight"])], ], "$inflight_init": [ [foo, []], diff --git a/tools/hangar/__snapshots__/test_corpus/valid/lift_via_closure.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/lift_via_closure.test.w_compile_tf-aws.md index 35f36be1c68..38ef8c86c52 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/lift_via_closure.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/lift_via_closure.test.w_compile_tf-aws.md @@ -293,7 +293,7 @@ class $Root extends $stdlib.std.Resource { return ({ "handle": [ [bucket2, ["get"]], - [fn2, ["handle", "listFiles"]], + [fn2, [].concat(["handle"], ["listFiles"])], [fn2.bucket, ["get"]], ], "$inflight_init": [ diff --git a/tools/hangar/__snapshots__/test_corpus/valid/nil.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/nil.test.w_compile_tf-aws.md index 03ba212c004..00c2b52fc96 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/nil.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/nil.test.w_compile_tf-aws.md @@ -206,7 +206,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "handle": [ - [foo, ["getOptionalValue", "setOptionalValue"]], + [foo, [].concat(["getOptionalValue"], ["setOptionalValue"])], ], "$inflight_init": [ [foo, []], diff --git a/tools/hangar/__snapshots__/test_corpus/valid/phase_independent_method_on_string.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/phase_independent_method_on_string.test.w_compile_tf-aws.md index c6507b47de5..99301d9f45f 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/phase_independent_method_on_string.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/phase_independent_method_on_string.test.w_compile_tf-aws.md @@ -171,7 +171,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "handle": [ - [api.url, ["length", "startsWith"]], + [api.url, [].concat(["startsWith"], ["length"])], [token_len, []], [url_regex, []], ], diff --git a/tools/hangar/__snapshots__/test_corpus/valid/redis.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/redis.test.w_compile_tf-aws.md index 17853cd2844..d9fb0566bf3 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/redis.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/redis.test.w_compile_tf-aws.md @@ -612,7 +612,7 @@ class $Root extends $stdlib.std.Resource { "handle": [ [queue, ["push"]], [r, ["get"]], - [r2, ["get", "set"]], + [r2, [].concat(["set"], ["get"])], ], "$inflight_init": [ [queue, []], diff --git a/tools/hangar/__snapshots__/test_corpus/valid/resource.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/resource.test.w_compile_tf-aws.md index d0d8e26abdb..f234c9defb4 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/resource.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/resource.test.w_compile_tf-aws.md @@ -745,7 +745,7 @@ class $Root extends $stdlib.std.Resource { [this.c, ["peek"]], ], "$inflight_init": [ - [this.c, ["dec", "inc"]], + [this.c, [].concat(["inc"], ["dec"])], ], "inflightField": [ ], @@ -792,8 +792,8 @@ class $Root extends $stdlib.std.Resource { return ({ "myMethod": [ [Foo, ["fooStatic"]], - [this.b, ["get", "put"]], - [this.foo, ["fooGet", "fooInc"]], + [this.b, [].concat(["put"], ["get"])], + [this.foo, [].concat(["fooInc"], ["fooGet"])], ], "testTypeAccess": [ [Bar, ["barStatic"]], @@ -846,7 +846,7 @@ class $Root extends $stdlib.std.Resource { return ({ "handle": [ [bucket, ["list"]], - [res, ["myMethod", "testTypeAccess"]], + [res, [].concat(["myMethod"], ["testTypeAccess"])], [res.foo, ["inflightField"]], ], "$inflight_init": [ @@ -1043,7 +1043,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "handle": [ - [bigOlPublisher, ["getObjectCount", "publish"]], + [bigOlPublisher, [].concat(["publish"], ["getObjectCount"])], ], "$inflight_init": [ [bigOlPublisher, []], diff --git a/tools/hangar/__snapshots__/test_corpus/valid/resource_captures.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/resource_captures.test.w_compile_tf-aws.md index a1cb4c1c64f..3b2d6e1a63a 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/resource_captures.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/resource_captures.test.w_compile_tf-aws.md @@ -391,13 +391,13 @@ class $Root extends $stdlib.std.Resource { [this.myOptStr, []], ], "testCaptureResource": [ - [this.myResource, ["get", "list", "put"]], + [this.myResource, [].concat(["put"], ["get"], ["list"])], ], "testNestedInflightField": [ [this.another.myField, []], ], "testNestedResource": [ - [this.another.first.myResource, ["get", "list", "put"]], + [this.another.first.myResource, [].concat(["list"], ["put"], ["get"])], [this.myStr, []], ], "testExpressionRecursive": [ @@ -409,7 +409,7 @@ class $Root extends $stdlib.std.Resource { [this.extNum, []], ], "testUserDefinedResource": [ - [this.another, ["anotherFunc", "meaningOfLife"]], + [this.another, [].concat(["meaningOfLife"], ["anotherFunc"])], ], "testInflightField": [ ], @@ -466,7 +466,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "handle": [ - [r, ["testCaptureCollectionsOfData", "testCaptureOptional", "testCapturePrimitives", "testCaptureResource", "testExpressionRecursive", "testExternal", "testInflightField", "testNestedInflightField", "testNestedResource", "testNoCapture", "testUserDefinedResource"]], + [r, [].concat(["testNoCapture"], ["testCaptureCollectionsOfData"], ["testCapturePrimitives"], ["testCaptureOptional"], ["testCaptureResource"], ["testNestedInflightField"], ["testNestedResource"], ["testExpressionRecursive"], ["testExternal"], ["testUserDefinedResource"], ["testInflightField"])], ], "$inflight_init": [ [r, []], diff --git a/tools/hangar/__snapshots__/test_corpus/valid/resource_captures_globals.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/resource_captures_globals.test.w_compile_tf-aws.md index c4cbc019215..76cb035a51d 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/resource_captures_globals.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/resource_captures_globals.test.w_compile_tf-aws.md @@ -418,7 +418,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "myMethod": [ - [globalCounter, ["inc", "peek"]], + [globalCounter, [].concat(["inc"], ["peek"])], ], "$inflight_init": [ [globalCounter, ["peek"]], diff --git a/tools/hangar/__snapshots__/test_corpus/valid/std_string.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/std_string.test.w_compile_tf-aws.md index dc3d682f55e..62af4b4f7d7 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/std_string.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/std_string.test.w_compile_tf-aws.md @@ -81,7 +81,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "handle": [ - [s1, ["concat", "indexOf", "split"]], + [s1, [].concat(["indexOf"], ["split"], ["concat"])], [s2, []], ], "$inflight_init": [ diff --git a/tools/hangar/__snapshots__/test_corpus/valid/test_bucket.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/test_bucket.test.w_compile_tf-aws.md index d262a72910d..215fe5a92b3 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/test_bucket.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/test_bucket.test.w_compile_tf-aws.md @@ -116,7 +116,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "handle": [ - [b, ["list", "put"]], + [b, [].concat(["list"], ["put"])], ], "$inflight_init": [ [b, []], @@ -151,7 +151,7 @@ class $Root extends $stdlib.std.Resource { get _liftMap() { return ({ "handle": [ - [b, ["get", "put"]], + [b, [].concat(["put"], ["get"])], ], "$inflight_init": [ [b, []],