diff --git a/examples/tests/invalid/super_call.w b/examples/tests/invalid/super_call.w new file mode 100644 index 00000000000..72e8fee2a07 --- /dev/null +++ b/examples/tests/invalid/super_call.w @@ -0,0 +1,62 @@ +class A { + method() { + // Acccess super with no base class (in case of preflight there's actually an implicit base class, `std.Resource`, we need to ignore) + super.method(); + //^^^^^^ Cannot call super method because class A has no parent + } +} + +inflight class InflightA { + method() { + // Access super with no base class + super.method(); + //^^^^^^ Cannot call super method because class A has no parent + } +} + +class B extends A { + child_method() { + // Access child method through super + super.child_method(); + //^^^^^^^^^^^^ super class "A" does not have a method named "child_method" + } + + static static_method() { + // super doesn't make sense in static context + super.method(); + //^^^^^ Cannot call super method inside of a static method + } +} + +// super doesn't make sense in global context +super.do(); + //^^ "super" can only be used inside of classes + + +// Verify correct error message when inflight closure tries to access super (this isn't suported yet see: https://github.com/winglang/wing/issues/3474) +// Once it is, this test should be moved to "valid" +class BaseClass { + inflight m1(): str { + return "base inflight m1"; + } +} + +bring cloud; +let q = new cloud.Queue(); +class ExtendedClass extends BaseClass { + inflight m1(): str { + return "extended inflight m1"; + } + get_func(): cloud.Function { + let inflight_closure = inflight (s:str): str => { + return "this: ${this.m1()}, super: ${super.m1()}"; + //^^ `super` calls inside inflight closures not supported yet, see: https://github.com/winglang/wing/issues/3474 + }; + return new cloud.Function(inflight_closure); + } +} +// TODO: uncomment and move to valid tests once this: https://github.com/winglang/wing/issues/3474 is fixed +// let y = new ExtendedClass().get_func(); +// test "inflight closure accesses super" { +// assert(y.invoke("") == "this: extended inflight m1, super: base inflight m1"); +// } \ No newline at end of file diff --git a/examples/tests/valid/super_call.w b/examples/tests/valid/super_call.w new file mode 100644 index 00000000000..046aa48130f --- /dev/null +++ b/examples/tests/valid/super_call.w @@ -0,0 +1,72 @@ +class A { + message: str; + + init() { + this.message = "A message from your ancestor"; + } +} + +class B extends A { + description(): str { + return "B"; + } +} + +class C extends B { + description(): str { + return "C extends ${super.description()}"; + } +} + +// This class has no `description` method, so it inherits the one from `B`. +class D extends C { +} + +class E extends D { + description(): str { + return "E extends ${super.description()}"; + } +} + +let e = new E(); +// Make sure super calls work and skip anything in the inheritance chain that doesn't have the method +assert(e.description() == "E extends C extends B"); + +inflight class InflightA { + description(): str { + return "InflightA"; + } +} + +// Test super calls on inflight classes +inflight class InflightB extends InflightA { + description(): str { + return "InflightB extends ${super.description()}"; + } +} + +test "super call inflight" { + let b = new InflightB(); + assert(b.description() == "InflightB extends InflightA"); +} + +// Test correct binding when calling a super method +bring cloud; +let b = new cloud.Bucket(); +class BaseClass { + inflight do(): str { + return b.get("k"); // BaseClass class required read acceess to b + } +} + +class ExtendedClass extends BaseClass { + inflight do(): str { + b.put("k", "value"); // This should require write access to b + return super.do(); // We expect to add binding permissions based on what `super.do()` requires (read) + } +} + +let extended = new ExtendedClass(); +test "super call sets binding permissions" { + assert(extended.do() == "value"); +} \ No newline at end of file diff --git a/libs/tree-sitter-wing/grammar.js b/libs/tree-sitter-wing/grammar.js index cfe29e6593a..1e9b9e2a6a5 100644 --- a/libs/tree-sitter-wing/grammar.js +++ b/libs/tree-sitter-wing/grammar.js @@ -380,20 +380,14 @@ module.exports = grammar({ compiler_dbg_panic: ($) => "😱", compiler_dbg_env: ($) => seq("🗺️", optional(";")), - _callable_expression: ($) => - choice( - $.nested_identifier, - $.identifier, - $.call, - $.parenthesized_expression - ), - call: ($) => prec.left( PREC.CALL, - seq(field("caller", $.expression), field("args", $.argument_list)) + seq(field("caller", choice($.expression, $.super_call)), field("args", $.argument_list)) ), + super_call: ($) => seq($._super, ".", field("method", $.identifier)), + argument_list: ($) => seq( "(", diff --git a/libs/wingc/src/ast.rs b/libs/wingc/src/ast.rs index b91ebae31cd..832540f4483 100644 --- a/libs/wingc/src/ast.rs +++ b/libs/wingc/src/ast.rs @@ -514,7 +514,7 @@ pub enum ExprKind { }, Reference(Reference), Call { - callee: Box, + callee: CalleeKind, arg_list: ArgList, }, Unary { @@ -557,6 +557,23 @@ pub enum ExprKind { CompilerDebugPanic, } +#[derive(Debug)] +pub enum CalleeKind { + /// The callee is any expression + Expr(Box), + /// The callee is a method in our super class + SuperCall(Symbol), +} + +impl Spanned for CalleeKind { + fn span(&self) -> WingSpan { + match self { + CalleeKind::Expr(e) => e.span.clone(), + CalleeKind::SuperCall(method) => method.span(), + } + } +} + #[derive(Debug)] pub struct Expr { /// An identifier that is unique among all expressions in the AST. diff --git a/libs/wingc/src/fold.rs b/libs/wingc/src/fold.rs index 0e18f0ed90d..cc517fea5ad 100644 --- a/libs/wingc/src/fold.rs +++ b/libs/wingc/src/fold.rs @@ -1,6 +1,6 @@ use crate::{ ast::{ - ArgList, CatchBlock, Class, ClassField, ElifBlock, Expr, ExprKind, FunctionBody, FunctionDefinition, + ArgList, CalleeKind, CatchBlock, Class, ClassField, ElifBlock, Expr, ExprKind, FunctionBody, FunctionDefinition, FunctionParameter, FunctionSignature, Interface, InterpolatedString, InterpolatedStringPart, Literal, NewExpr, Reference, Scope, Stmt, StmtKind, StructField, Symbol, TypeAnnotation, TypeAnnotationKind, UserDefinedType, }, @@ -263,7 +263,10 @@ where }, ExprKind::Reference(reference) => ExprKind::Reference(f.fold_reference(reference)), ExprKind::Call { callee, arg_list } => ExprKind::Call { - callee: Box::new(f.fold_expr(*callee)), + callee: match callee { + CalleeKind::Expr(expr) => CalleeKind::Expr(Box::new(f.fold_expr(*expr))), + CalleeKind::SuperCall(method) => CalleeKind::SuperCall(f.fold_symbol(method)), + }, arg_list: f.fold_args(arg_list), }, ExprKind::Unary { op, exp } => ExprKind::Unary { diff --git a/libs/wingc/src/jsify.rs b/libs/wingc/src/jsify.rs index 698ed77d346..5d3ea8cb0b0 100644 --- a/libs/wingc/src/jsify.rs +++ b/libs/wingc/src/jsify.rs @@ -9,7 +9,7 @@ use std::{borrow::Borrow, cmp::Ordering, collections::BTreeMap, path::Path, vec} use crate::{ ast::{ - ArgList, BinaryOperator, Class as AstClass, Expr, ExprKind, FunctionBody, FunctionDefinition, + ArgList, BinaryOperator, CalleeKind, Class as AstClass, Expr, ExprKind, FunctionBody, FunctionDefinition, InterpolatedStringPart, Literal, NewExpr, Phase, Reference, Scope, Stmt, StmtKind, Symbol, TypeAnnotationKind, UnaryOperator, UserDefinedType, }, @@ -18,8 +18,10 @@ use crate::{ diagnostic::{report_diagnostic, Diagnostic, WingSpan}, files::Files, type_check::{ - lifts::Lifts, symbol_env::SymbolEnv, ClassLike, Type, TypeRef, Types, VariableKind, CLASS_INFLIGHT_INIT_NAME, + lifts::Lifts, resolve_super_method, symbol_env::SymbolEnv, ClassLike, Type, TypeRef, Types, VariableKind, + CLASS_INFLIGHT_INIT_NAME, }, + visit_context::VisitContext, MACRO_REPLACE_ARGS, MACRO_REPLACE_ARGS_TEXT, MACRO_REPLACE_SELF, WINGSDK_ASSEMBLY_NAME, WINGSDK_RESOURCE, WINGSDK_STD_MODULE, }; @@ -39,13 +41,13 @@ const ROOT_CLASS: &str = "$Root"; const JS_CONSTRUCTOR: &str = "constructor"; pub struct JSifyContext<'a> { - pub in_json: bool, /// The current execution phase of the AST traversal. /// The root of any Wing app starts with the preflight phase, and /// the `inflight` keyword specifies scopes that are inflight. - pub phase: Phase, pub files: &'a mut Files, pub lifts: Option<&'a Lifts>, + + pub visit_ctx: &'a mut VisitContext, } pub struct JSifier<'a> { @@ -88,6 +90,15 @@ impl<'a> JSifier<'a> { let mut js = CodeMaker::default(); let mut imports = CodeMaker::default(); + let mut visit_ctx = VisitContext::new(); + let mut jsify_context = JSifyContext { + visit_ctx: &mut visit_ctx, + files: &mut files, + lifts: None, + }; + jsify_context + .visit_ctx + .push_env(scope.env.borrow().as_ref().unwrap().get_ref()); for statement in scope.statements.iter().sorted_by(|a, b| match (&a.kind, &b.kind) { // Put type definitions first so JS won't complain of unknown types (StmtKind::Class(AstClass { .. }), StmtKind::Class(AstClass { .. })) => Ordering::Equal, @@ -95,12 +106,6 @@ impl<'a> JSifier<'a> { (_, StmtKind::Class(AstClass { .. })) => Ordering::Greater, _ => Ordering::Equal, }) { - let mut jsify_context = JSifyContext { - in_json: false, - phase: Phase::Preflight, - files: &mut files, - lifts: None, - }; let s = self.jsify_statement(scope.env.borrow().as_ref().unwrap(), statement, &mut jsify_context); // top level statements are always preflight if let StmtKind::Bring { identifier: _, @@ -159,10 +164,12 @@ impl<'a> JSifier<'a> { CompilationContext::set(CompilationPhase::Jsifying, &scope.span); let mut code = CodeMaker::default(); + ctx.visit_ctx.push_env(scope.env.borrow().as_ref().unwrap().get_ref()); for statement in scope.statements.iter() { let statement_code = self.jsify_statement(scope.env.borrow().as_ref().unwrap(), statement, ctx); code.add_code(statement_code); } + ctx.visit_ctx.pop_env(); code } @@ -236,7 +243,7 @@ impl<'a> JSifier<'a> { // if we are in inflight and there's a lifting/capturing token associated with this expression // then emit the token instead of the expression. - if ctx.phase == Phase::Inflight { + if ctx.visit_ctx.current_phase() == Phase::Inflight { if let Some(lifts) = &ctx.lifts { if let Some(t) = lifts.token_for_expr(&expression.id) { return t.clone(); @@ -248,7 +255,7 @@ impl<'a> JSifier<'a> { // this is an error. this can happen if we render a lifted preflight expression that references // an e.g. variable from inflight (`myarr.get(i)` where `myarr` is preflight and `i` is an // inflight variable). in this case we need to bail out. - if ctx.phase == Phase::Preflight { + if ctx.visit_ctx.current_phase() == Phase::Preflight { if let Some(expr_phase) = self.types.get_expr_phase(expression) { if expr_phase == Phase::Inflight { report_diagnostic(Diagnostic { @@ -261,7 +268,7 @@ impl<'a> JSifier<'a> { } } - let auto_await = match ctx.phase { + let auto_await = match ctx.visit_ctx.current_phase() { Phase::Inflight => "await ", _ => "", }; @@ -357,7 +364,7 @@ impl<'a> JSifier<'a> { Literal::Boolean(b) => (if *b { "true" } else { "false" }).to_string(), }, ExprKind::Range { start, inclusive, end } => { - match ctx.phase { + match ctx.visit_ctx.current_phase() { Phase::Inflight => format!( "((s,e,i) => {{ function* iterator(start,end,inclusive) {{ let i = start; let limit = inclusive ? ((end < start) ? end - 1 : end + 1) : end; while (i < limit) yield i++; while (i > limit) yield i--; }}; return iterator(s,e,i); }})({},{},{})", self.jsify_expression(start, ctx), @@ -375,11 +382,18 @@ impl<'a> JSifier<'a> { } ExprKind::Reference(_ref) => self.jsify_reference(&_ref, ctx), ExprKind::Call { callee, arg_list } => { - let function_type = self.types.get_expr_type(callee); + + let function_type = match callee { + CalleeKind::Expr(expr) => self.types.get_expr_type(expr), + CalleeKind::SuperCall(method) => resolve_super_method(method, ctx.visit_ctx.current_env().expect("an env"), self.types).expect("valid super method").0 + }; let is_option = function_type.is_option(); let function_type = function_type.maybe_unwrap_option(); let function_sig = function_type.as_function_sig(); - let expr_string = self.jsify_expression(callee, ctx); + let expr_string = match callee { + CalleeKind::Expr(expr) => self.jsify_expression(expr, ctx), + CalleeKind::SuperCall(method) => format!("super.{}", method), + }; let args_string = self.jsify_arg_list(&arg_list, None, None, ctx); let mut args_text_string = lookup_span(&arg_list.span, &self.source_files); if args_text_string.len() > 0 { @@ -390,17 +404,24 @@ impl<'a> JSifier<'a> { if let Some(function_sig) = function_sig { if let Some(js_override) = &function_sig.js_override { - let self_string = &match &callee.kind { - // for "loose" macros, e.g. `print()`, $self$ is the global object - ExprKind::Reference(Reference::Identifier(_)) => "global".to_string(), - ExprKind::Reference(Reference::InstanceMember { object, .. }) => { - self.jsify_expression(object, ctx) + let self_string = match callee { + CalleeKind::Expr(expr) => match &expr.kind { + // for "loose" macros, e.g. `print()`, $self$ is the global object + ExprKind::Reference(Reference::Identifier(_)) => "global".to_string(), + ExprKind::Reference(Reference::InstanceMember { object, .. }) => { + self.jsify_expression(&object, ctx) + } + _ => expr_string, } - - _ => expr_string, + CalleeKind::SuperCall{..} => + // Note: in case of a $self$ macro override of a super call there's no clear definition of what $self$ should be, + // "this" is a decent option because it'll refer to the object where "super" was used, but depending on how + // $self$ is used in the macro it might lead to unexpected results if $self$.some_method() is called and is + // defined differently in the parent class of "this". + "this".to_string(), }; let patterns = &[MACRO_REPLACE_SELF, MACRO_REPLACE_ARGS, MACRO_REPLACE_ARGS_TEXT]; - let replace_with = &[self_string, &args_string, &args_text_string]; + let replace_with = &[self_string, args_string, args_text_string]; let ac = AhoCorasick::new(patterns); return ac.replace_all(js_override, replace_with); } @@ -464,7 +485,7 @@ impl<'a> JSifier<'a> { .collect::>() .join(", "); - if self.types.get_expr_type(expression).is_mutable_collection() || ctx.in_json { + if self.types.get_expr_type(expression).is_mutable_collection() || ctx.visit_ctx.in_json() { // json arrays dont need frozen at nested level format!("[{}]", item_list) } else { @@ -482,22 +503,18 @@ impl<'a> JSifier<'a> { ) } ExprKind::JsonLiteral { is_mut, element } => { - let json_context = &mut JSifyContext { - in_json: true, - phase: ctx.phase, - files: ctx.files, - lifts: ctx.lifts, - }; + ctx.visit_ctx.push_json(); let js_out = match &element.kind { ExprKind::JsonMapLiteral { .. } => { if *is_mut { - self.jsify_expression(element, json_context) + self.jsify_expression(element, ctx) } else { - format!("Object.freeze({})", self.jsify_expression(element, json_context)) + format!("Object.freeze({})", self.jsify_expression(element, ctx)) } } - _ => self.jsify_expression(element, json_context) + _ => self.jsify_expression(element, ctx) }; + ctx.visit_ctx.pop_json(); js_out } ExprKind::JsonMapLiteral { fields } => { @@ -516,7 +533,7 @@ impl<'a> JSifier<'a> { .collect::>() .join(","); - if self.types.get_expr_type(expression).is_mutable_collection() || ctx.in_json { + if self.types.get_expr_type(expression).is_mutable_collection() || ctx.visit_ctx.in_json() { // json maps dont need frozen in the nested level format!("{{{}}}", f) } else { @@ -550,7 +567,7 @@ impl<'a> JSifier<'a> { match &statement.kind { StmtKind::SuperConstructor { arg_list } => { let args = self.jsify_arg_list(&arg_list, None, None, ctx); - match ctx.phase { + match ctx.visit_ctx.current_phase() { Phase::Preflight => CodeMaker::one_line(format!("super(scope,id,{});", args)), _ => CodeMaker::one_line(format!("super({});", args)), } @@ -894,10 +911,9 @@ impl<'a> JSifier<'a> { }; let ctx = &mut JSifyContext { - in_json: ctx.in_json, - phase: ctx.phase, files: ctx.files, lifts, + visit_ctx: &mut ctx.visit_ctx, }; // emit the inflight side of the class into a separate file @@ -905,7 +921,7 @@ impl<'a> JSifier<'a> { // if this is inflight/independent, class, just emit the inflight class code inline and move on // with your life. - if ctx.phase != Phase::Preflight { + if ctx.visit_ctx.current_phase() != Phase::Preflight { return inflight_class_code; } @@ -1072,13 +1088,8 @@ impl<'a> JSifier<'a> { } // Write a class's inflight to a file - fn jsify_class_inflight(&self, class: &AstClass, ctx: &mut JSifyContext) -> CodeMaker { - let mut ctx = JSifyContext { - phase: Phase::Inflight, - in_json: false, - files: ctx.files, - lifts: ctx.lifts, - }; + fn jsify_class_inflight(&self, class: &AstClass, mut ctx: &mut JSifyContext) -> CodeMaker { + ctx.visit_ctx.push_phase(Phase::Inflight); let mut class_code = CodeMaker::default(); @@ -1109,6 +1120,7 @@ impl<'a> JSifier<'a> { } class_code.close("}"); + ctx.visit_ctx.pop_phase(); class_code } diff --git a/libs/wingc/src/lifting.rs b/libs/wingc/src/lifting.rs index 53c34e1980a..1fc799468ae 100644 --- a/libs/wingc/src/lifting.rs +++ b/libs/wingc/src/lifting.rs @@ -127,16 +127,18 @@ impl<'a> LiftTransform<'a> { return true; } - fn jsify_expr(&self, node: &Expr, phase: Phase) -> String { - self.jsify.jsify_expression( + fn jsify_expr(&mut self, node: &Expr, phase: Phase) -> String { + self.ctx.push_phase(phase); + let res = self.jsify.jsify_expression( &node, &mut JSifyContext { - in_json: false, - phase: phase, files: &mut Files::default(), lifts: None, + visit_ctx: &mut self.ctx, }, - ) + ); + self.ctx.pop_phase(); + res } } diff --git a/libs/wingc/src/lsp/completions.rs b/libs/wingc/src/lsp/completions.rs index bba1c67017c..4172c55b6f7 100644 --- a/libs/wingc/src/lsp/completions.rs +++ b/libs/wingc/src/lsp/completions.rs @@ -6,15 +6,17 @@ use lsp_types::{ use std::cmp::max; use tree_sitter::{Node, Point}; -use crate::ast::{Expr, ExprKind, Phase, Scope, Symbol, TypeAnnotation, TypeAnnotationKind, UserDefinedType}; +use crate::ast::{ + CalleeKind, Expr, ExprKind, Phase, Scope, Symbol, TypeAnnotation, TypeAnnotationKind, UserDefinedType, +}; use crate::closure_transform::{CLOSURE_CLASS_PREFIX, PARENT_THIS_NAME}; use crate::diagnostic::{WingLocation, WingSpan}; use crate::docs::Documented; use crate::lsp::sync::{FILES, JSII_TYPES}; use crate::type_check::symbol_env::{LookupResult, StatementIdx}; use crate::type_check::{ - fully_qualify_std_type, import_udt_from_jsii, resolve_user_defined_type, ClassLike, Namespace, Struct, SymbolKind, - Type, Types, UnsafeRef, VariableKind, CLASS_INFLIGHT_INIT_NAME, CLASS_INIT_NAME, + fully_qualify_std_type, import_udt_from_jsii, resolve_super_method, resolve_user_defined_type, ClassLike, Namespace, + Struct, SymbolKind, Type, Types, UnsafeRef, VariableKind, CLASS_INFLIGHT_INIT_NAME, CLASS_INIT_NAME, }; use crate::visit::{visit_expr, visit_type_annotation, Visit}; use crate::wasm_util::{ptr_to_string, string_to_combined_ptr, WASM_RETURN_ERROR}; @@ -275,7 +277,12 @@ pub fn on_completion(params: lsp_types::CompletionParams) -> CompletionResponse || matches!(nearest_non_reference_parent, Some(p) if p.kind() == "argument_list" || p.kind() == "positional_argument")) { if let Some(callish_expr) = scope_visitor.expression_trail.iter().rev().find_map(|e| match &e.kind { - ExprKind::Call { arg_list, callee } => Some((types.get_expr_type(&callee), arg_list)), + ExprKind::Call { arg_list, callee } => Some(( + match callee { + CalleeKind::Expr(expr) => types.get_expr_type(expr), + CalleeKind::SuperCall(method) => resolve_super_method(method, found_env, types).map_or(types.error(), |(t,_)| t), + } + , arg_list)), ExprKind::New(new_expr) => { Some((types.get_expr_type(&new_expr.class), &new_expr.arg_list)) } diff --git a/libs/wingc/src/lsp/hover.rs b/libs/wingc/src/lsp/hover.rs index 7731b5474b9..e5bf2f192e5 100644 --- a/libs/wingc/src/lsp/hover.rs +++ b/libs/wingc/src/lsp/hover.rs @@ -1,12 +1,12 @@ use crate::ast::{ - Class, Expr, ExprKind, FunctionBody, FunctionDefinition, Phase, Reference, Scope, Stmt, StmtKind, Symbol, + CalleeKind, Class, Expr, ExprKind, FunctionBody, FunctionDefinition, Phase, Reference, Scope, Stmt, StmtKind, Symbol, TypeAnnotation, TypeAnnotationKind, }; use crate::diagnostic::WingSpan; use crate::docs::Documented; use crate::lsp::sync::FILES; use crate::type_check::symbol_env::LookupResult; -use crate::type_check::{ClassLike, Type, Types, CLASS_INFLIGHT_INIT_NAME, CLASS_INIT_NAME}; +use crate::type_check::{resolve_super_method, ClassLike, Type, Types, CLASS_INFLIGHT_INIT_NAME, CLASS_INIT_NAME}; use crate::visit::{self, Visit}; use crate::wasm_util::WASM_RETURN_ERROR; use crate::wasm_util::{ptr_to_string, string_to_combined_ptr}; @@ -240,8 +240,16 @@ impl<'a> Visit<'a> for HoverVisitor<'a> { ExprKind::Call { arg_list, callee } => { let x = arg_list.named_args.iter().find(|a| a.0.span.contains(&self.position)); if let Some((arg_name, ..)) = x { + let curr_env = self.current_scope.env.borrow(); + let env = curr_env.as_ref().expect("an env"); // we need to get the struct type from the callee - let callee_type = self.types.get_expr_type(callee); + let callee_type = match callee { + CalleeKind::Expr(expr) => self.types.get_expr_type(expr), + CalleeKind::SuperCall(method) => resolve_super_method(method, env, &self.types) + .ok() + .map_or(self.types.error(), |t| t.0), + }; + if let Some(structy) = callee_type.get_function_struct_arg() { self.found = Some((arg_name.span.clone(), docs_from_classlike_property(structy, arg_name))); } diff --git a/libs/wingc/src/lsp/signature.rs b/libs/wingc/src/lsp/signature.rs index 209d99761f1..a8c779995ca 100644 --- a/libs/wingc/src/lsp/signature.rs +++ b/libs/wingc/src/lsp/signature.rs @@ -4,12 +4,13 @@ use lsp_types::{ SignatureInformation, }; -use crate::ast::{Expr, ExprKind, NewExpr, Symbol}; +use crate::ast::{CalleeKind, Expr, ExprKind, NewExpr, Symbol}; use crate::docs::Documented; use crate::lsp::sync::FILES; -use crate::type_check::{resolve_user_defined_type, CLASS_INIT_NAME}; -use crate::visit::{visit_expr, Visit}; +use crate::type_check::symbol_env::SymbolEnvRef; +use crate::type_check::{resolve_super_method, resolve_user_defined_type, CLASS_INIT_NAME}; +use crate::visit::{visit_expr, visit_scope, Visit}; use crate::wasm_util::{ptr_to_string, string_to_combined_ptr, WASM_RETURN_ERROR}; #[no_mangle] @@ -37,6 +38,7 @@ pub fn on_signature_help(params: lsp_types::SignatureHelpParams) -> Option, @@ -63,7 +65,13 @@ pub fn on_signature_help(params: lsp_types::SignatureHelpParams) -> Option { - let t = file_data.types.get_expr_type(callee); + let t = match callee { + CalleeKind::Expr(expr) => file_data.types.get_expr_type(expr), + CalleeKind::SuperCall(method) => resolve_super_method(method, &env, &file_data.types) + .ok() + .map_or(file_data.types.error(), |t| t.0), + }; + (t, arg_list) } _ => return None, @@ -174,6 +182,10 @@ pub struct ScopeVisitor<'a> { pub location: Position, /// The nearest expression before (or containing) the target location pub call_expr: Option<&'a Expr>, + // The env of the found expression + pub call_env: Option, + /// The current symbol env we're in + curr_env: Vec, } impl<'a> ScopeVisitor<'a> { @@ -181,6 +193,8 @@ impl<'a> ScopeVisitor<'a> { Self { location, call_expr: None, + call_env: None, + curr_env: vec![], } } } @@ -195,6 +209,7 @@ impl<'a> Visit<'a> for ScopeVisitor<'a> { match node.kind { ExprKind::Call { .. } | ExprKind::New { .. } => { self.call_expr = Some(node); + self.call_env = Some(*self.curr_env.last().unwrap()); } _ => {} } @@ -202,6 +217,12 @@ impl<'a> Visit<'a> for ScopeVisitor<'a> { visit_expr(self, node); } + + fn visit_scope(&mut self, node: &'a crate::ast::Scope) { + self.curr_env.push(node.env.borrow().as_ref().unwrap().get_ref()); + visit_scope(self, node); + self.curr_env.pop(); + } } #[cfg(test)] diff --git a/libs/wingc/src/parser.rs b/libs/wingc/src/parser.rs index a392038c5f3..8284128fa45 100644 --- a/libs/wingc/src/parser.rs +++ b/libs/wingc/src/parser.rs @@ -7,10 +7,10 @@ use tree_sitter::Node; use tree_sitter_traversal::{traverse, Order}; use crate::ast::{ - ArgList, BinaryOperator, CatchBlock, Class, ClassField, ElifBlock, Expr, ExprKind, FunctionBody, FunctionDefinition, - FunctionParameter, FunctionSignature, Interface, InterpolatedString, InterpolatedStringPart, Literal, NewExpr, Phase, - Reference, Scope, Stmt, StmtKind, StructField, Symbol, TypeAnnotation, TypeAnnotationKind, UnaryOperator, - UserDefinedType, + ArgList, BinaryOperator, CalleeKind, CatchBlock, Class, ClassField, ElifBlock, Expr, ExprKind, FunctionBody, + FunctionDefinition, FunctionParameter, FunctionSignature, Interface, InterpolatedString, InterpolatedStringPart, + Literal, NewExpr, Phase, Reference, Scope, Stmt, StmtKind, StructField, Symbol, TypeAnnotation, TypeAnnotationKind, + UnaryOperator, UserDefinedType, }; use crate::comp_ctx::{CompilationContext, CompilationPhase}; use crate::diagnostic::{report_diagnostic, Diagnostic, DiagnosticResult, WingSpan}; @@ -269,7 +269,7 @@ impl<'s> Parser<'s> { // represent duration literals as the AST equivalent of `duration.fromSeconds(value)` Ok(Expr::new( ExprKind::Call { - callee: Box::new(Expr::new( + callee: CalleeKind::Expr(Box::new(Expr::new( ExprKind::Reference(Reference::InstanceMember { object: Box::new(Expr::new( ExprKind::Reference(Reference::Identifier(Symbol { @@ -285,7 +285,7 @@ impl<'s> Parser<'s> { optional_accessor: false, }), span.clone(), - )), + ))), arg_list: ArgList { pos_args: vec![Expr::new(ExprKind::Literal(Literal::Number(seconds)), span.clone())], named_args: IndexMap::new(), @@ -1494,13 +1494,21 @@ impl<'s> Parser<'s> { "reference" => self.build_reference(&expression_node, phase), "positional_argument" => self.build_expression(&expression_node.named_child(0).unwrap(), phase), "keyword_argument_value" => self.build_expression(&expression_node.named_child(0).unwrap(), phase), - "call" => Ok(Expr::new( - ExprKind::Call { - callee: Box::new(self.build_expression(&expression_node.child_by_field_name("caller").unwrap(), phase)?), - arg_list: self.build_arg_list(&expression_node.child_by_field_name("args").unwrap(), phase)?, - }, - expression_span, - )), + "call" => { + let caller_node = expression_node.child_by_field_name("caller").unwrap(); + let callee = if caller_node.kind() == "super_call" { + CalleeKind::SuperCall(self.node_symbol(&caller_node.child_by_field_name("method").unwrap())?) + } else { + CalleeKind::Expr(Box::new(self.build_expression(&caller_node, phase)?)) + }; + Ok(Expr::new( + ExprKind::Call { + callee, + arg_list: self.build_arg_list(&expression_node.child_by_field_name("args").unwrap(), phase)?, + }, + expression_span, + )) + } "parenthesized_expression" => self.build_expression(&expression_node.named_child(0).unwrap(), phase), "preflight_closure" => Ok(Expr::new( ExprKind::FunctionClosure(self.build_anonymous_closure(&expression_node, phase)?), diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index c47f3734fff..69a021331c5 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -3,7 +3,7 @@ pub(crate) mod jsii_importer; pub mod lifts; pub mod symbol_env; -use crate::ast::{self, ClassField, FunctionDefinition, NewExpr, TypeAnnotationKind}; +use crate::ast::{self, CalleeKind, ClassField, FunctionDefinition, NewExpr, TypeAnnotationKind}; use crate::ast::{ ArgList, BinaryOperator, Class as AstClass, Expr, ExprKind, FunctionBody, FunctionParameter as AstFunctionParameter, Interface as AstInterface, InterpolatedStringPart, Literal, Phase, Reference, Scope, Spanned, Stmt, StmtKind, Symbol, @@ -71,7 +71,7 @@ pub type TypeRef = UnsafeRef; #[derive(Debug)] pub enum SymbolKind { - Type(TypeRef), + Type(TypeRef), // TODO: <- deprecated since we treat types as a VeriableInfo of kind VariableKind::Type Variable(VariableInfo), Namespace(NamespaceRef), } @@ -1100,8 +1100,6 @@ pub struct Types { err_idx: usize, type_for_expr: Vec>, - - resource_base_type: Option, } impl Types { @@ -1148,7 +1146,6 @@ impl Types { nil_idx, err_idx, type_for_expr: Vec::new(), - resource_base_type: None, } } @@ -1235,22 +1232,15 @@ impl Types { UnsafeRef::(&**t as *const Namespace) } - fn resource_base_type(&mut self) -> TypeRef { - // cache the resource base type ref - if self.resource_base_type.is_none() { - let resource_fqn = format!("{}.{}", WINGSDK_ASSEMBLY_NAME, WINGSDK_RESOURCE); - self.resource_base_type = Some( - self - .libraries - .lookup_nested_str(&resource_fqn, None) - .unwrap() - .0 - .as_type() - .unwrap(), - ); - } - - self.resource_base_type.unwrap() + pub fn resource_base_type(&self) -> TypeRef { + let resource_fqn = format!("{}.{}", WINGSDK_ASSEMBLY_NAME, WINGSDK_RESOURCE); + self + .libraries + .lookup_nested_str(&resource_fqn, None) + .expect("Resouce base class to be loaded") + .0 + .as_type() + .expect("Resouce base class to be a type") } /// Stores the type and phase of a given expression node. @@ -1685,7 +1675,13 @@ impl<'a> TypeChecker<'a> { } ExprKind::Call { callee, arg_list } => { // Resolve the function's reference (either a method in the class's env or a function in the current env) - let (func_type, callee_phase) = self.type_check_exp(callee, env); + let (func_type, callee_phase) = match callee { + CalleeKind::Expr(expr) => self.type_check_exp(expr, env), + CalleeKind::SuperCall(method) => resolve_super_method(method, env, &self.types).unwrap_or_else(|e| { + self.type_error(e); + self.resolved_error() + }), + }; let is_option = func_type.is_option(); let func_type = func_type.maybe_unwrap_option(); @@ -1746,10 +1742,21 @@ impl<'a> TypeChecker<'a> { } // If the function is "wingc_env", then print out the current environment - if let ExprKind::Reference(Reference::Identifier(ident)) = &callee.kind { - if ident.name == "wingc_env" { - println!("[symbol environment at {}]", exp.span().to_string()); - println!("{}", env.to_string()); + if let CalleeKind::Expr(call_expr) = callee { + if let ExprKind::Reference(Reference::Identifier(ident)) = &call_expr.kind { + if ident.name == "wingc_env" { + println!("[symbol environment at {}]", exp.span().to_string()); + println!("{}", env.to_string()); + } + } + } + + if let CalleeKind::Expr(call_expr) = callee { + if let ExprKind::Reference(Reference::Identifier(ident)) = &call_expr.kind { + if ident.name == "wingc_env" { + println!("[symbol environment at {}]", exp.span().to_string()); + println!("{}", env.to_string()); + } } } @@ -1757,11 +1764,17 @@ impl<'a> TypeChecker<'a> { // When calling a an optional function, the return type is always optional // To allow this to be both safe and unsurprising, // the callee must be a reference with an optional accessor - if let ExprKind::Reference(Reference::InstanceMember { optional_accessor, .. }) = &callee.kind { - if *optional_accessor { - (self.types.make_option(func_sig.return_type), func_phase) + if let CalleeKind::Expr(call_expr) = callee { + if let ExprKind::Reference(Reference::InstanceMember { optional_accessor, .. }) = &call_expr.kind { + if *optional_accessor { + (self.types.make_option(func_sig.return_type), func_phase) + } else { + // No additional error is needed here, since the type checker will already have errored without optional chaining + (self.types.error(), func_phase) + } } else { - // No additional error is needed here, since the type checker will already have errored without optional chaining + // TODO do we want syntax for this? e.g. `foo?.()` + self.spanned_error(callee, "Cannot call an optional function"); (self.types.error(), func_phase) } } else { @@ -4249,6 +4262,59 @@ pub fn resolve_user_defined_type( } } +pub fn resolve_super_method(method: &Symbol, env: &SymbolEnv, types: &Types) -> Result<(TypeRef, Phase), TypeError> { + let this_type = env.lookup(&Symbol::global("this"), None); + if let Some(SymbolKind::Variable(VariableInfo { + type_, + kind: VariableKind::Free, + .. + })) = this_type + { + if type_.is_closure() { + return Err(TypeError { + message: + "`super` calls inside inflight closures not supported yet, see: https://github.com/winglang/wing/issues/3474" + .to_string(), + span: method.span.clone(), + }); + } + // Get the parent type of "this" (if it's a preflight class that's directly derived from `std.Resource` it's an implicit derive so we'll treat it as if there's no parent) + let parent_type = type_ + .as_class() + .expect("Expected \"this\" to be a class") + .parent + .filter(|t| !(t.is_preflight_class() && t.is_same_type_as(&types.resource_base_type()))); + if let Some(parent_type) = parent_type { + if let Some(method_info) = parent_type.as_class().unwrap().get_method(method) { + Ok((method_info.type_, method_info.phase)) + } else { + Err(TypeError { + message: format!( + "super class \"{}\" does not have a method named \"{}\"", + parent_type, method + ), + span: method.span.clone(), + }) + } + } else { + Err(TypeError { + message: format!("Cannot call super method because class {} has no parent", type_), + span: method.span.clone(), + }) + } + } else { + Err(TypeError { + message: (if env.is_function { + "Cannot call super method inside of a static method" + } else { + "\"super\" can only be used inside of classes" + }) + .to_string(), + span: method.span.clone(), + }) + } +} + pub fn import_udt_from_jsii( wing_types: &mut Types, jsii_types: &mut TypeSystem, diff --git a/libs/wingc/src/visit.rs b/libs/wingc/src/visit.rs index 885cd61c90e..7c5bc3c35bc 100644 --- a/libs/wingc/src/visit.rs +++ b/libs/wingc/src/visit.rs @@ -1,7 +1,7 @@ use crate::{ ast::{ - ArgList, Class, Expr, ExprKind, FunctionBody, FunctionDefinition, FunctionParameter, FunctionSignature, Interface, - InterpolatedStringPart, Literal, NewExpr, Reference, Scope, Stmt, StmtKind, Symbol, TypeAnnotation, + ArgList, CalleeKind, Class, Expr, ExprKind, FunctionBody, FunctionDefinition, FunctionParameter, FunctionSignature, + Interface, InterpolatedStringPart, Literal, NewExpr, Reference, Scope, Stmt, StmtKind, Symbol, TypeAnnotation, TypeAnnotationKind, UserDefinedType, }, dbg_panic, @@ -301,7 +301,10 @@ where v.visit_reference(ref_); } ExprKind::Call { callee, arg_list } => { - v.visit_expr(callee); + match callee { + CalleeKind::Expr(expr) => v.visit_expr(expr), + CalleeKind::SuperCall(method) => v.visit_symbol(method), + } v.visit_args(arg_list); } ExprKind::Unary { op: _, exp } => { diff --git a/libs/wingc/src/visit_context.rs b/libs/wingc/src/visit_context.rs index 681d0f97e02..c95abf41000 100644 --- a/libs/wingc/src/visit_context.rs +++ b/libs/wingc/src/visit_context.rs @@ -11,6 +11,7 @@ pub struct VisitContext { method: Vec>, class: Vec, statement: Vec, + in_json: Vec, } impl VisitContext { @@ -23,6 +24,7 @@ impl VisitContext { class: vec![], statement: vec![], method: vec![], + in_json: vec![], } } } @@ -44,13 +46,13 @@ impl VisitContext { pub fn push_class(&mut self, class: UserDefinedType, phase: &Phase, initializer_env: Option) { self.class.push(class); - self.phase.push(*phase); + self.push_phase(*phase); self.method_env.push(initializer_env); } pub fn pop_class(&mut self) { self.class.pop(); - self.phase.pop(); + self.pop_phase(); self.method_env.pop(); } @@ -61,7 +63,7 @@ impl VisitContext { // -- pub fn push_function_definition(&mut self, function_name: &Option, phase: &Phase, env: SymbolEnvRef) { - self.phase.push(*phase); + self.push_phase(*phase); self.method.push(function_name.clone()); // if the function definition doesn't have a name (i.e. it's a closure), don't push its env @@ -71,7 +73,7 @@ impl VisitContext { } pub fn pop_function_definition(&mut self) { - self.phase.pop(); + self.pop_phase(); self.method.pop(); self.method_env.pop(); } @@ -116,4 +118,28 @@ impl VisitContext { pub fn current_env(&self) -> Option<&SymbolEnvRef> { self.env.last() } + + // -- + + pub fn push_json(&mut self) { + self.in_json.push(true); + } + + pub fn pop_json(&mut self) { + self.in_json.pop(); + } + + pub fn in_json(&self) -> bool { + *self.in_json.last().unwrap_or(&false) + } + + // -- + + pub fn push_phase(&mut self, phase: Phase) { + self.phase.push(phase); + } + + pub fn pop_phase(&mut self) { + self.phase.pop(); + } } diff --git a/tools/hangar/__snapshots__/invalid.ts.snap b/tools/hangar/__snapshots__/invalid.ts.snap index ffe22624037..472bd6c472a 100644 --- a/tools/hangar/__snapshots__/invalid.ts.snap +++ b/tools/hangar/__snapshots__/invalid.ts.snap @@ -1862,6 +1862,56 @@ error: Cannot instantiate type \\"BucketProps\\" because it is a struct and not +Tests 1 failed (1) +Test Files 1 failed (1) +Duration " +`; + +exports[`super_call.w 1`] = ` +"error: \\"super\\" can only be used inside of classes + --> ../../../examples/tests/invalid/super_call.w:32:7 + | +32 | super.do(); + | ^^ \\"super\\" can only be used inside of classes + + +error: Cannot call super method because class A has no parent + --> ../../../examples/tests/invalid/super_call.w:4:11 + | +4 | super.method(); + | ^^^^^^ Cannot call super method because class A has no parent + + +error: Cannot call super method because class InflightA has no parent + --> ../../../examples/tests/invalid/super_call.w:12:11 + | +12 | super.method(); + | ^^^^^^ Cannot call super method because class InflightA has no parent + + +error: super class \\"A\\" does not have a method named \\"child_method\\" + --> ../../../examples/tests/invalid/super_call.w:20:11 + | +20 | super.child_method(); + | ^^^^^^^^^^^^ super class \\"A\\" does not have a method named \\"child_method\\" + + +error: Cannot call super method inside of a static method + --> ../../../examples/tests/invalid/super_call.w:26:11 + | +26 | super.method(); + | ^^^^^^ Cannot call super method inside of a static method + + +error: \`super\` calls inside inflight closures not supported yet, see: https://github.com/winglang/wing/issues/3474 + --> ../../../examples/tests/invalid/super_call.w:52:50 + | +52 | return \\"this: \${this.m1()}, super: \${super.m1()}\\"; + | ^^ \`super\` calls inside inflight closures not supported yet, see: https://github.com/winglang/wing/issues/3474 + + + + Tests 1 failed (1) Test Files 1 failed (1) Duration " diff --git a/tools/hangar/__snapshots__/test_corpus/valid/super_call.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/super_call.w_compile_tf-aws.md new file mode 100644 index 00000000000..90c4ccc1817 --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/valid/super_call.w_compile_tf-aws.md @@ -0,0 +1,708 @@ +# [super_call.w](../../../../../examples/tests/valid/super_call.w) | compile | tf-aws + +## inflight.$Closure1.js +```js +module.exports = function({ $InflightB }) { + class $Closure1 { + constructor({ }) { + const $obj = (...args) => this.handle(...args); + Object.setPrototypeOf($obj, this); + return $obj; + } + async handle() { + const b = new $InflightB(); + {((cond) => {if (!cond) throw new Error("assertion failed: b.description() == \"InflightB extends InflightA\"")})(((await b.description()) === "InflightB extends InflightA"))}; + } + } + return $Closure1; +} + +``` + +## inflight.$Closure2.js +```js +module.exports = function({ $extended }) { + class $Closure2 { + constructor({ }) { + const $obj = (...args) => this.handle(...args); + Object.setPrototypeOf($obj, this); + return $obj; + } + async handle() { + {((cond) => {if (!cond) throw new Error("assertion failed: extended.do() == \"value\"")})(((await $extended.do()) === "value"))}; + } + } + return $Closure2; +} + +``` + +## inflight.A.js +```js +module.exports = function({ }) { + class A { + constructor({ }) { + } + } + return A; +} + +``` + +## inflight.B.js +```js +module.exports = function({ $A }) { + class B extends $A { + constructor({ }) { + super({ }); + } + } + return B; +} + +``` + +## inflight.BaseClass.js +```js +module.exports = function({ $b }) { + class BaseClass { + constructor({ }) { + } + async do() { + return (await $b.get("k")); + } + } + return BaseClass; +} + +``` + +## inflight.C.js +```js +module.exports = function({ $B }) { + class C extends $B { + constructor({ }) { + super({ }); + } + } + return C; +} + +``` + +## inflight.D.js +```js +module.exports = function({ $C }) { + class D extends $C { + constructor({ }) { + super({ }); + } + } + return D; +} + +``` + +## inflight.E.js +```js +module.exports = function({ $D }) { + class E extends $D { + constructor({ }) { + super({ }); + } + } + return E; +} + +``` + +## inflight.ExtendedClass.js +```js +module.exports = function({ $BaseClass, $b }) { + class ExtendedClass extends $BaseClass { + constructor({ }) { + super({ }); + } + async do() { + (await $b.put("k","value")); + return (await super.do()); + } + } + return ExtendedClass; +} + +``` + +## inflight.InflightA.js +```js +module.exports = function({ }) { + class InflightA { + async description() { + return "InflightA"; + } + } + return InflightA; +} + +``` + +## inflight.InflightB.js +```js +module.exports = function({ $InflightA }) { + class InflightB extends $InflightA { + async description() { + return String.raw({ raw: ["InflightB extends ", ""] }, (await super.description())); + } + } + return InflightB; +} + +``` + +## main.tf.json +```json +{ + "//": { + "metadata": { + "backend": "local", + "stackName": "root", + "version": "0.17.0" + }, + "outputs": { + "root": { + "Default": { + "cloud.TestRunner": { + "TestFunctionArns": "WING_TEST_RUNNER_FUNCTION_ARNS" + } + } + } + } + }, + "output": { + "WING_TEST_RUNNER_FUNCTION_ARNS": { + "value": "[[\"root/Default/Default/test:super call inflight\",\"${aws_lambda_function.testsupercallinflight_Handler_8BA833E3.arn}\"],[\"root/Default/Default/test:super call sets binding permissions\",\"${aws_lambda_function.testsupercallsetsbindingpermissions_Handler_094D9398.arn}\"]]" + } + }, + "provider": { + "aws": [ + {} + ] + }, + "resource": { + "aws_iam_role": { + "testsupercallinflight_Handler_IamRole_BFD5186E": { + "//": { + "metadata": { + "path": "root/Default/Default/test:super call inflight/Handler/IamRole", + "uniqueId": "testsupercallinflight_Handler_IamRole_BFD5186E" + } + }, + "assume_role_policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"},\"Effect\":\"Allow\"}]}" + }, + "testsupercallsetsbindingpermissions_Handler_IamRole_64C69931": { + "//": { + "metadata": { + "path": "root/Default/Default/test:super call sets binding permissions/Handler/IamRole", + "uniqueId": "testsupercallsetsbindingpermissions_Handler_IamRole_64C69931" + } + }, + "assume_role_policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"},\"Effect\":\"Allow\"}]}" + } + }, + "aws_iam_role_policy": { + "testsupercallinflight_Handler_IamRolePolicy_657A4ED2": { + "//": { + "metadata": { + "path": "root/Default/Default/test:super call inflight/Handler/IamRolePolicy", + "uniqueId": "testsupercallinflight_Handler_IamRolePolicy_657A4ED2" + } + }, + "policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":\"none:null\",\"Resource\":\"*\"}]}", + "role": "${aws_iam_role.testsupercallinflight_Handler_IamRole_BFD5186E.name}" + }, + "testsupercallsetsbindingpermissions_Handler_IamRolePolicy_58870805": { + "//": { + "metadata": { + "path": "root/Default/Default/test:super call sets binding permissions/Handler/IamRolePolicy", + "uniqueId": "testsupercallsetsbindingpermissions_Handler_IamRolePolicy_58870805" + } + }, + "policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":[\"s3:List*\",\"s3:PutObject*\",\"s3:Abort*\",\"s3:GetObject*\",\"s3:GetBucket*\"],\"Resource\":[\"${aws_s3_bucket.cloudBucket.arn}\",\"${aws_s3_bucket.cloudBucket.arn}/*\"],\"Effect\":\"Allow\"}]}", + "role": "${aws_iam_role.testsupercallsetsbindingpermissions_Handler_IamRole_64C69931.name}" + } + }, + "aws_iam_role_policy_attachment": { + "testsupercallinflight_Handler_IamRolePolicyAttachment_74E493C1": { + "//": { + "metadata": { + "path": "root/Default/Default/test:super call inflight/Handler/IamRolePolicyAttachment", + "uniqueId": "testsupercallinflight_Handler_IamRolePolicyAttachment_74E493C1" + } + }, + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "${aws_iam_role.testsupercallinflight_Handler_IamRole_BFD5186E.name}" + }, + "testsupercallsetsbindingpermissions_Handler_IamRolePolicyAttachment_FB4798F3": { + "//": { + "metadata": { + "path": "root/Default/Default/test:super call sets binding permissions/Handler/IamRolePolicyAttachment", + "uniqueId": "testsupercallsetsbindingpermissions_Handler_IamRolePolicyAttachment_FB4798F3" + } + }, + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "${aws_iam_role.testsupercallsetsbindingpermissions_Handler_IamRole_64C69931.name}" + } + }, + "aws_lambda_function": { + "testsupercallinflight_Handler_8BA833E3": { + "//": { + "metadata": { + "path": "root/Default/Default/test:super call inflight/Handler/Default", + "uniqueId": "testsupercallinflight_Handler_8BA833E3" + } + }, + "environment": { + "variables": { + "WING_FUNCTION_NAME": "Handler-c83c6423", + "WING_TARGET": "tf-aws" + } + }, + "function_name": "Handler-c83c6423", + "handler": "index.handler", + "publish": true, + "role": "${aws_iam_role.testsupercallinflight_Handler_IamRole_BFD5186E.arn}", + "runtime": "nodejs18.x", + "s3_bucket": "${aws_s3_bucket.Code.bucket}", + "s3_key": "${aws_s3_object.testsupercallinflight_Handler_S3Object_83823694.key}", + "timeout": 30, + "vpc_config": { + "security_group_ids": [], + "subnet_ids": [] + } + }, + "testsupercallsetsbindingpermissions_Handler_094D9398": { + "//": { + "metadata": { + "path": "root/Default/Default/test:super call sets binding permissions/Handler/Default", + "uniqueId": "testsupercallsetsbindingpermissions_Handler_094D9398" + } + }, + "environment": { + "variables": { + "BUCKET_NAME_d755b447": "${aws_s3_bucket.cloudBucket.bucket}", + "WING_FUNCTION_NAME": "Handler-c8c1dd55", + "WING_TARGET": "tf-aws" + } + }, + "function_name": "Handler-c8c1dd55", + "handler": "index.handler", + "publish": true, + "role": "${aws_iam_role.testsupercallsetsbindingpermissions_Handler_IamRole_64C69931.arn}", + "runtime": "nodejs18.x", + "s3_bucket": "${aws_s3_bucket.Code.bucket}", + "s3_key": "${aws_s3_object.testsupercallsetsbindingpermissions_Handler_S3Object_6D280664.key}", + "timeout": 30, + "vpc_config": { + "security_group_ids": [], + "subnet_ids": [] + } + } + }, + "aws_s3_bucket": { + "Code": { + "//": { + "metadata": { + "path": "root/Default/Code", + "uniqueId": "Code" + } + }, + "bucket_prefix": "code-c84a50b1-" + }, + "cloudBucket": { + "//": { + "metadata": { + "path": "root/Default/Default/cloud.Bucket/Default", + "uniqueId": "cloudBucket" + } + }, + "bucket_prefix": "cloud-bucket-c87175e7-", + "force_destroy": false + } + }, + "aws_s3_bucket_public_access_block": { + "cloudBucket_PublicAccessBlock_5946CCE8": { + "//": { + "metadata": { + "path": "root/Default/Default/cloud.Bucket/PublicAccessBlock", + "uniqueId": "cloudBucket_PublicAccessBlock_5946CCE8" + } + }, + "block_public_acls": true, + "block_public_policy": true, + "bucket": "${aws_s3_bucket.cloudBucket.bucket}", + "ignore_public_acls": true, + "restrict_public_buckets": true + } + }, + "aws_s3_bucket_server_side_encryption_configuration": { + "cloudBucket_Encryption_77B6AEEF": { + "//": { + "metadata": { + "path": "root/Default/Default/cloud.Bucket/Encryption", + "uniqueId": "cloudBucket_Encryption_77B6AEEF" + } + }, + "bucket": "${aws_s3_bucket.cloudBucket.bucket}", + "rule": [ + { + "apply_server_side_encryption_by_default": { + "sse_algorithm": "AES256" + } + } + ] + } + }, + "aws_s3_object": { + "testsupercallinflight_Handler_S3Object_83823694": { + "//": { + "metadata": { + "path": "root/Default/Default/test:super call inflight/Handler/S3Object", + "uniqueId": "testsupercallinflight_Handler_S3Object_83823694" + } + }, + "bucket": "${aws_s3_bucket.Code.bucket}", + "key": "", + "source": "" + }, + "testsupercallsetsbindingpermissions_Handler_S3Object_6D280664": { + "//": { + "metadata": { + "path": "root/Default/Default/test:super call sets binding permissions/Handler/S3Object", + "uniqueId": "testsupercallsetsbindingpermissions_Handler_S3Object_6D280664" + } + }, + "bucket": "${aws_s3_bucket.Code.bucket}", + "key": "", + "source": "" + } + } + } +} +``` + +## preflight.js +```js +const $stdlib = require('@winglang/sdk'); +const $outdir = process.env.WING_SYNTH_DIR ?? "."; +const std = $stdlib.std; +const $wing_is_test = process.env.WING_IS_TEST === "true"; +const cloud = require('@winglang/sdk').cloud; +class $Root extends $stdlib.std.Resource { + constructor(scope, id) { + super(scope, id); + class A extends $stdlib.std.Resource { + constructor(scope, id, ) { + super(scope, id); + this.message = "A message from your ancestor"; + this._addInflightOps("$inflight_init"); + } + static _toInflightType(context) { + return $stdlib.core.NodeJsCode.fromInline(` + require("./inflight.A.js")({ + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const AClient = ${A._toInflightType(this).text}; + const client = new AClient({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + } + class B extends A { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("$inflight_init"); + } + description() { + return "B"; + } + static _toInflightType(context) { + return $stdlib.core.NodeJsCode.fromInline(` + require("./inflight.B.js")({ + $A: ${context._lift(A)}, + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const BClient = ${B._toInflightType(this).text}; + const client = new BClient({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + } + class C extends B { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("$inflight_init"); + } + description() { + return String.raw({ raw: ["C extends ", ""] }, (super.description())); + } + static _toInflightType(context) { + return $stdlib.core.NodeJsCode.fromInline(` + require("./inflight.C.js")({ + $B: ${context._lift(B)}, + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const CClient = ${C._toInflightType(this).text}; + const client = new CClient({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + } + class D extends C { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("$inflight_init"); + } + static _toInflightType(context) { + return $stdlib.core.NodeJsCode.fromInline(` + require("./inflight.D.js")({ + $C: ${context._lift(C)}, + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const DClient = ${D._toInflightType(this).text}; + const client = new DClient({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + } + class E extends D { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("$inflight_init"); + } + description() { + return String.raw({ raw: ["E extends ", ""] }, (super.description())); + } + static _toInflightType(context) { + return $stdlib.core.NodeJsCode.fromInline(` + require("./inflight.E.js")({ + $D: ${context._lift(D)}, + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const EClient = ${E._toInflightType(this).text}; + const client = new EClient({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + } + class InflightA extends $stdlib.std.Resource { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("description", "$inflight_init"); + } + static _toInflightType(context) { + return $stdlib.core.NodeJsCode.fromInline(` + require("./inflight.InflightA.js")({ + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const InflightAClient = ${InflightA._toInflightType(this).text}; + const client = new InflightAClient({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + } + class InflightB extends InflightA { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("description", "$inflight_init"); + } + static _toInflightType(context) { + return $stdlib.core.NodeJsCode.fromInline(` + require("./inflight.InflightB.js")({ + $InflightA: ${context._lift(InflightA)}, + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const InflightBClient = ${InflightB._toInflightType(this).text}; + const client = new InflightBClient({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + } + class $Closure1 extends $stdlib.std.Resource { + constructor(scope, id, ) { + super(scope, id); + this.display.hidden = true; + this._addInflightOps("handle", "$inflight_init"); + } + static _toInflightType(context) { + return $stdlib.core.NodeJsCode.fromInline(` + require("./inflight.$Closure1.js")({ + $InflightB: ${context._lift(InflightB)}, + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const $Closure1Client = ${$Closure1._toInflightType(this).text}; + const client = new $Closure1Client({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + } + class BaseClass extends $stdlib.std.Resource { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("do", "$inflight_init"); + } + static _toInflightType(context) { + return $stdlib.core.NodeJsCode.fromInline(` + require("./inflight.BaseClass.js")({ + $b: ${context._lift(b)}, + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const BaseClassClient = ${BaseClass._toInflightType(this).text}; + const client = new BaseClassClient({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + _registerBind(host, ops) { + if (ops.includes("do")) { + BaseClass._registerBindObject(b, host, ["get"]); + } + super._registerBind(host, ops); + } + } + class ExtendedClass extends BaseClass { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("do", "$inflight_init"); + } + static _toInflightType(context) { + return $stdlib.core.NodeJsCode.fromInline(` + require("./inflight.ExtendedClass.js")({ + $BaseClass: ${context._lift(BaseClass)}, + $b: ${context._lift(b)}, + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const ExtendedClassClient = ${ExtendedClass._toInflightType(this).text}; + const client = new ExtendedClassClient({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + _registerBind(host, ops) { + if (ops.includes("do")) { + ExtendedClass._registerBindObject(b, host, ["put"]); + } + super._registerBind(host, ops); + } + } + class $Closure2 extends $stdlib.std.Resource { + constructor(scope, id, ) { + super(scope, id); + this.display.hidden = true; + this._addInflightOps("handle", "$inflight_init"); + } + static _toInflightType(context) { + return $stdlib.core.NodeJsCode.fromInline(` + require("./inflight.$Closure2.js")({ + $extended: ${context._lift(extended)}, + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const $Closure2Client = ${$Closure2._toInflightType(this).text}; + const client = new $Closure2Client({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + _registerBind(host, ops) { + if (ops.includes("handle")) { + $Closure2._registerBindObject(extended, host, ["do"]); + } + super._registerBind(host, ops); + } + } + const e = new E(this,"E"); + {((cond) => {if (!cond) throw new Error("assertion failed: e.description() == \"E extends C extends B\"")})(((e.description()) === "E extends C extends B"))}; + this.node.root.new("@winglang/sdk.std.Test",std.Test,this,"test:super call inflight",new $Closure1(this,"$Closure1")); + const b = this.node.root.newAbstract("@winglang/sdk.cloud.Bucket",this,"cloud.Bucket"); + const extended = new ExtendedClass(this,"ExtendedClass"); + this.node.root.new("@winglang/sdk.std.Test",std.Test,this,"test:super call sets binding permissions",new $Closure2(this,"$Closure2")); + } +} +const $App = $stdlib.core.App.for(process.env.WING_TARGET); +new $App({ outdir: $outdir, name: "super_call", rootConstruct: $Root, plugins: $plugins, isTestEnvironment: $wing_is_test }).synth(); + +``` + diff --git a/tools/hangar/__snapshots__/test_corpus/valid/super_call.w_test_sim.md b/tools/hangar/__snapshots__/test_corpus/valid/super_call.w_test_sim.md new file mode 100644 index 00000000000..af23a3f2a03 --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/valid/super_call.w_test_sim.md @@ -0,0 +1,13 @@ +# [super_call.w](../../../../../examples/tests/valid/super_call.w) | test | sim + +## stdout.log +```log +pass ─ super_call.wsim » root/env0/test:super call inflight +pass ─ super_call.wsim » root/env1/test:super call sets binding permissions + + +Tests 2 passed (2) +Test Files 1 passed (1) +Duration +``` +