From c07f132418ef42c7b0c0433ccc15f7e3d6284325 Mon Sep 17 00:00:00 2001 From: Chris Rybicki Date: Tue, 13 Feb 2024 16:55:57 -0500 Subject: [PATCH] feat(compiler): support mutually recursive type references (#5683) Fixes #5665 and unblocks a fix for the `github` winglib (https://github.com/winglang/winglibs/pull/72). With this change, it's possible for interfaces and structs to reference each other: ```js // mutually referential interfaces interface IThing1 { m2(): IThing2; } interface IThing2 { m1(): IThing1; } // mutually referential structs struct M1 { m2: M2?; } struct M2 { m1: M1?; } ``` A side benefit of this is it's possible for interfaces, structs, and enums to be referenced as type annotations before they have been declared, and enums to be referenced as values before they have been declared: ```js let opt: SomeEnum? = nil; let three = SomeEnum.THREE; enum SomeEnum { ONE, TWO, THREE } let a: IShape? = nil; interface IShape {} let b: Order? = nil; struct Order {} ``` The same treatment has not yet been applied to classes in this PR since it requires more extensive changes to the type checker. For example, as enforced in other languages like TypeScript, it should not be possible to instantiate classes or access static members before the declaration; ```js let a = new A(); // Error: Class "A" used before its declaration class A {} ``` Miscellaneous changes: - Refactored AST data structures in compiler so that there are dedicated `Struct` and `Enum` structs ## Checklist - [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted) - [x] Description explains motivation and solution - [x] Tests added (always) - [ ] Docs updated (only required for features) - [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing *By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*. --- examples/tests/valid/bring_jsii.test.w | 2 +- examples/tests/valid/enums.test.w | 13 +- examples/tests/valid/interface.test.w | 27 + examples/tests/valid/structs.test.w | 19 +- libs/wingc/src/ast.rs | 28 +- libs/wingc/src/dtsify/mod.rs | 27 +- libs/wingc/src/fold.rs | 59 +- libs/wingc/src/jsify.rs | 32 +- .../wingc/src/jsify/snapshots/enum_value.snap | 14 +- libs/wingc/src/lsp/document_symbols.rs | 8 +- libs/wingc/src/lsp/symbol_locator.rs | 6 +- libs/wingc/src/parser.rs | 12 +- libs/wingc/src/type_check.rs | 585 ++++++++++-------- libs/wingc/src/visit.rs | 81 ++- tools/hangar/__snapshots__/invalid.ts.snap | 4 +- .../bring_local.test.w_compile_tf-aws.md | 16 +- .../valid/enums.test.w_compile_tf-aws.md | 18 +- .../valid/interface.test.w_compile_tf-aws.md | 85 +++ .../valid/resource.test.w_compile_tf-aws.md | 16 +- .../valid/store.w_compile_tf-aws.md | 16 +- .../valid/structs.test.w_compile_tf-aws.md | 1 + 21 files changed, 645 insertions(+), 424 deletions(-) diff --git a/examples/tests/valid/bring_jsii.test.w b/examples/tests/valid/bring_jsii.test.w index eafa2529e73..54766ba558b 100644 --- a/examples/tests/valid/bring_jsii.test.w +++ b/examples/tests/valid/bring_jsii.test.w @@ -12,4 +12,4 @@ test "sayHello" { } let jsiiClass = new jsii_fixture.JsiiClass(10); -assert(jsiiClass.applyClosure(5, (x) => { return x * 2; }) == 10); \ No newline at end of file +assert(jsiiClass.applyClosure(5, (x) => { return x * 2; }) == 10); diff --git a/examples/tests/valid/enums.test.w b/examples/tests/valid/enums.test.w index 0dd9f064b6f..f006c94899f 100644 --- a/examples/tests/valid/enums.test.w +++ b/examples/tests/valid/enums.test.w @@ -1,3 +1,14 @@ +// enums can be referenced by other types before they are defined +struct A { + e: SomeEnum; +} + +// type annotations can reference enums before they are defined +let opt: SomeEnum? = nil; + +// values can reference enums before they are defined +let three = SomeEnum.THREE; + enum SomeEnum { ONE, TWO, THREE } @@ -8,4 +19,4 @@ let two: SomeEnum = SomeEnum.TWO; // Try with explicit type annotation test "inflight" { assert(one == SomeEnum.ONE); assert(two == SomeEnum.TWO); -} \ No newline at end of file +} diff --git a/examples/tests/valid/interface.test.w b/examples/tests/valid/interface.test.w index 17eb540428e..b5a422f3c3c 100644 --- a/examples/tests/valid/interface.test.w +++ b/examples/tests/valid/interface.test.w @@ -1,3 +1,11 @@ +struct A { + // other types can reference interfaces before they are defined + field0: IShape; +} + +// type annotations can reference interfaces before they are defined +let a: IShape? = nil; + interface IShape { // method with a return type method1(): str; @@ -14,3 +22,22 @@ interface IPointy { interface ISquare extends IShape, IPointy { } + +class C {} + +// interfaces can reference classes +interface IClass { + method1(): C; + // method2(): D; // TODO: not supported yet - classes are not hoisted +} + +class D {} + +// mutually referential interfaces +interface IThing1 { + m2(): IThing2?; +} + +interface IThing2 { + m1(): IThing1?; +} diff --git a/examples/tests/valid/structs.test.w b/examples/tests/valid/structs.test.w index c7da198c7ad..09edfeb427a 100644 --- a/examples/tests/valid/structs.test.w +++ b/examples/tests/valid/structs.test.w @@ -1,3 +1,11 @@ +interface IFoo { + // other types can reference structs before they are defined + field0(): A; +} + +// type annotations can reference structs before they are defined +let a: A? = nil; + struct A { field0: str; } @@ -70,4 +78,13 @@ test "struct definitions are phase independant" { }; assert(s2.a == "foo"); -} \ No newline at end of file +} + +// mutually referential structs +struct M1 { + m2: M2?; +} + +struct M2 { + m1: M1?; +} diff --git a/libs/wingc/src/ast.rs b/libs/wingc/src/ast.rs index 124b6da5870..ee59c3b1fac 100644 --- a/libs/wingc/src/ast.rs +++ b/libs/wingc/src/ast.rs @@ -426,6 +426,21 @@ pub struct Interface { pub access: AccessModifier, } +#[derive(Debug)] +pub struct Struct { + pub name: Symbol, + pub extends: Vec, + pub fields: Vec, + pub access: AccessModifier, +} + +#[derive(Debug)] +pub struct Enum { + pub name: Symbol, + pub values: IndexSet, + pub access: AccessModifier, +} + #[derive(Debug)] pub enum BringSource { BuiltinModule(Symbol), @@ -507,17 +522,8 @@ pub enum StmtKind { Scope(Scope), Class(Class), Interface(Interface), - Struct { - name: Symbol, - extends: Vec, - fields: Vec, - access: AccessModifier, - }, - Enum { - name: Symbol, - values: IndexSet, - access: AccessModifier, - }, + Struct(Struct), + Enum(Enum), TryCatch { try_statements: Scope, catch_block: Option, diff --git a/libs/wingc/src/dtsify/mod.rs b/libs/wingc/src/dtsify/mod.rs index 09ee0dfc1c4..15285eed205 100644 --- a/libs/wingc/src/dtsify/mod.rs +++ b/libs/wingc/src/dtsify/mod.rs @@ -251,23 +251,18 @@ impl<'a> DTSifier<'a> { code.line(self.dtsify_interface(interface, false)); code.line(self.dtsify_interface(interface, true)); } - StmtKind::Struct { - name, - extends, - fields, - access: _, - } => { - if !extends.is_empty() { + StmtKind::Struct(st) => { + if !st.extends.is_empty() { code.open(format!( "export interface {} extends {} {{", - name.name, - extends.iter().map(|udt| udt.to_string()).join(", ") + st.name.name, + st.extends.iter().map(|udt| udt.to_string()).join(", ") )); } else { - code.open(format!("export interface {} {{", name.name)); + code.open(format!("export interface {} {{", st.name.name)); } - for field in fields { + for field in &st.fields { code.line(format!( "readonly {}{}: {};", field.name, @@ -281,13 +276,9 @@ impl<'a> DTSifier<'a> { } code.close("}"); } - StmtKind::Enum { - name, - values, - access: _, - } => { - code.open(format!("export enum {} {{", name.name)); - for value in values { + StmtKind::Enum(enu) => { + code.open(format!("export enum {} {{", enu.name.name)); + for value in &enu.values { code.line(format!("{},", value.name)); } code.close("}"); diff --git a/libs/wingc/src/fold.rs b/libs/wingc/src/fold.rs index b8376bda7b5..2e00bbf378c 100644 --- a/libs/wingc/src/fold.rs +++ b/libs/wingc/src/fold.rs @@ -1,9 +1,9 @@ use crate::{ ast::{ - ArgList, BringSource, CalleeKind, CatchBlock, Class, ClassField, ElifBlock, ElifLetBlock, Elifs, Expr, ExprKind, - FunctionBody, FunctionDefinition, FunctionParameter, FunctionSignature, IfLet, Interface, InterpolatedString, - InterpolatedStringPart, Literal, New, Reference, Scope, Stmt, StmtKind, StructField, Symbol, TypeAnnotation, - TypeAnnotationKind, UserDefinedType, + ArgList, BringSource, CalleeKind, CatchBlock, Class, ClassField, ElifBlock, ElifLetBlock, Elifs, Enum, Expr, + ExprKind, FunctionBody, FunctionDefinition, FunctionParameter, FunctionSignature, IfLet, Interface, + InterpolatedString, InterpolatedStringPart, Literal, New, Reference, Scope, Stmt, StmtKind, Struct, StructField, + Symbol, TypeAnnotation, TypeAnnotationKind, UserDefinedType, }, dbg_panic, }; @@ -24,12 +24,18 @@ pub trait Fold { fn fold_class_field(&mut self, node: ClassField) -> ClassField { fold_class_field(self, node) } + fn fold_struct(&mut self, node: Struct) -> Struct { + fold_struct(self, node) + } fn fold_struct_field(&mut self, node: StructField) -> StructField { fold_struct_field(self, node) } fn fold_interface(&mut self, node: Interface) -> Interface { fold_interface(self, node) } + fn fold_enum(&mut self, node: Enum) -> Enum { + fold_enum(self, node) + } fn fold_expr(&mut self, node: Expr) -> Expr { fold_expr(self, node) } @@ -179,22 +185,8 @@ where StmtKind::Scope(scope) => StmtKind::Scope(f.fold_scope(scope)), StmtKind::Class(class) => StmtKind::Class(f.fold_class(class)), StmtKind::Interface(interface) => StmtKind::Interface(f.fold_interface(interface)), - StmtKind::Struct { - name, - extends, - fields, - access, - } => StmtKind::Struct { - name: f.fold_symbol(name), - extends: extends.into_iter().map(|e| f.fold_user_defined_type(e)).collect(), - fields: fields.into_iter().map(|field| f.fold_struct_field(field)).collect(), - access, - }, - StmtKind::Enum { name, values, access } => StmtKind::Enum { - name: f.fold_symbol(name), - values: values.into_iter().map(|value| f.fold_symbol(value)).collect(), - access, - }, + StmtKind::Struct(st) => StmtKind::Struct(f.fold_struct(st)), + StmtKind::Enum(enu) => StmtKind::Enum(f.fold_enum(enu)), StmtKind::TryCatch { try_statements, catch_block, @@ -290,6 +282,33 @@ where } } +pub fn fold_struct(f: &mut F, node: Struct) -> Struct +where + F: Fold + ?Sized, +{ + Struct { + name: f.fold_symbol(node.name), + extends: node.extends.into_iter().map(|e| f.fold_user_defined_type(e)).collect(), + fields: node + .fields + .into_iter() + .map(|field| f.fold_struct_field(field)) + .collect(), + access: node.access, + } +} + +pub fn fold_enum(f: &mut F, node: Enum) -> Enum +where + F: Fold + ?Sized, +{ + Enum { + name: f.fold_symbol(node.name), + values: node.values.into_iter().map(|v| f.fold_symbol(v)).collect(), + access: node.access, + } +} + pub fn fold_expr(f: &mut F, node: Expr) -> Expr where F: Fold + ?Sized, diff --git a/libs/wingc/src/jsify.rs b/libs/wingc/src/jsify.rs index 5a0296beb75..b819b408c37 100644 --- a/libs/wingc/src/jsify.rs +++ b/libs/wingc/src/jsify.rs @@ -12,9 +12,9 @@ use std::{borrow::Borrow, cell::RefCell, cmp::Ordering, collections::BTreeMap, v use crate::{ ast::{ - AccessModifier, ArgList, AssignmentKind, BinaryOperator, BringSource, CalleeKind, Class as AstClass, Elifs, Expr, - ExprKind, FunctionBody, FunctionDefinition, IfLet, InterpolatedStringPart, Literal, New, Phase, Reference, Scope, - Stmt, StmtKind, Symbol, UnaryOperator, UserDefinedType, + AccessModifier, ArgList, AssignmentKind, BinaryOperator, BringSource, CalleeKind, Class as AstClass, Elifs, Enum, + Expr, ExprKind, FunctionBody, FunctionDefinition, IfLet, InterpolatedStringPart, Literal, New, Phase, Reference, + Scope, Stmt, StmtKind, Symbol, UnaryOperator, UserDefinedType, }, comp_ctx::{CompilationContext, CompilationPhase}, dbg_panic, @@ -139,9 +139,12 @@ impl<'a> JSifier<'a> { jsify_context.visit_ctx.push_env(self.types.get_scope_env(&scope)); 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, - (StmtKind::Class(AstClass { .. }), _) => Ordering::Less, - (_, StmtKind::Class(AstClass { .. })) => Ordering::Greater, + (StmtKind::Enum(_), StmtKind::Enum(_)) => Ordering::Equal, + (StmtKind::Enum(_), _) => Ordering::Less, + (_, StmtKind::Enum(_)) => Ordering::Greater, + (StmtKind::Class(_), StmtKind::Class(_)) => Ordering::Equal, + (StmtKind::Class(_), _) => Ordering::Less, + (_, StmtKind::Class(_)) => Ordering::Greater, _ => Ordering::Equal, }) { let scope_env = self.types.get_scope_env(&scope); @@ -1151,10 +1154,15 @@ impl<'a> JSifier<'a> { StmtKind::Interface { .. } => { // This is a no-op in JS } - StmtKind::Struct { .. } => { + StmtKind::Struct(_) => { // Struct schemas are emitted before jsification phase } - StmtKind::Enum { name, values, .. } => { + StmtKind::Enum(enu) => { + let Enum { + name, + values, + access: _, + } = enu; code.open(format!("const {name} =")); code.add_code(self.jsify_enum(name, values)); code.close(";"); @@ -1937,10 +1945,10 @@ fn get_public_symbols(scope: &Scope) -> Vec { StmtKind::Interface(_) => {} // structs are bringable, but we don't emit anything for them // unless a static method is called on them - StmtKind::Struct { .. } => {} - StmtKind::Enum { name, access, .. } => { - if *access == AccessModifier::Public { - symbols.push(name.clone()); + StmtKind::Struct(_) => {} + StmtKind::Enum(enu) => { + if enu.access == AccessModifier::Public { + symbols.push(enu.name.clone()); } } StmtKind::TryCatch { .. } => {} diff --git a/libs/wingc/src/jsify/snapshots/enum_value.snap b/libs/wingc/src/jsify/snapshots/enum_value.snap index d0ca5e25226..192a093c0d4 100644 --- a/libs/wingc/src/jsify/snapshots/enum_value.snap +++ b/libs/wingc/src/jsify/snapshots/enum_value.snap @@ -50,6 +50,13 @@ const $helpers = $stdlib.helpers; class $Root extends $stdlib.std.Resource { constructor($scope, $id) { super($scope, $id); + const MyEnum = + (function (tmp) { + tmp[tmp["B"] = 0] = ",B"; + tmp[tmp["C"] = 1] = ",C"; + return tmp; + })({}) + ; class $Closure1 extends $stdlib.std.AutoIdResource { _id = $stdlib.core.closureId(); constructor($scope, $id, ) { @@ -85,13 +92,6 @@ class $Root extends $stdlib.std.Resource { }); } } - const MyEnum = - (function (tmp) { - tmp[tmp["B"] = 0] = ",B"; - tmp[tmp["C"] = 1] = ",C"; - return tmp; - })({}) - ; const x = MyEnum.C; this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:test", new $Closure1(this, "$Closure1")); } diff --git a/libs/wingc/src/lsp/document_symbols.rs b/libs/wingc/src/lsp/document_symbols.rs index 4e50bc2deb6..9c6dde4c93a 100644 --- a/libs/wingc/src/lsp/document_symbols.rs +++ b/libs/wingc/src/lsp/document_symbols.rs @@ -67,14 +67,14 @@ impl Visit<'_> for DocumentSymbolVisitor { .document_symbols .push(create_document_symbol(symbol, SymbolKind::CLASS)); } - StmtKind::Struct { name, .. } => { - let symbol = name; + StmtKind::Struct(st) => { + let symbol = &st.name; self .document_symbols .push(create_document_symbol(symbol, SymbolKind::STRUCT)); } - StmtKind::Enum { name, .. } => { - let symbol = name; + StmtKind::Enum(enu) => { + let symbol = &enu.name; self .document_symbols .push(create_document_symbol(symbol, SymbolKind::ENUM)); diff --git a/libs/wingc/src/lsp/symbol_locator.rs b/libs/wingc/src/lsp/symbol_locator.rs index ab6d163da86..1b233defe85 100644 --- a/libs/wingc/src/lsp/symbol_locator.rs +++ b/libs/wingc/src/lsp/symbol_locator.rs @@ -221,14 +221,14 @@ impl<'a> Visit<'a> for SymbolLocator<'a> { // Handle situations where symbols are actually defined in inner scopes match &node.kind { - StmtKind::Struct { name, fields, .. } => { - let Some(struct_env) = self.get_env_from_classlike_symbol(name) else { + StmtKind::Struct(st) => { + let Some(struct_env) = self.get_env_from_classlike_symbol(&st.name) else { return; }; self.ctx.push_env(struct_env); - for field in fields { + for field in &st.fields { self.visit_symbol(&field.name); } diff --git a/libs/wingc/src/parser.rs b/libs/wingc/src/parser.rs index 25551a83ca2..a72c8a82a22 100644 --- a/libs/wingc/src/parser.rs +++ b/libs/wingc/src/parser.rs @@ -11,9 +11,9 @@ use tree_sitter_traversal::{traverse, Order}; use crate::ast::{ AccessModifier, ArgList, AssignmentKind, BinaryOperator, BringSource, CalleeKind, CatchBlock, Class, ClassField, - ElifBlock, ElifLetBlock, Elifs, Expr, ExprKind, FunctionBody, FunctionDefinition, FunctionParameter, + ElifBlock, ElifLetBlock, Elifs, Enum, Expr, ExprKind, FunctionBody, FunctionDefinition, FunctionParameter, FunctionSignature, IfLet, Interface, InterpolatedString, InterpolatedStringPart, Literal, New, Phase, Reference, - Scope, Spanned, Stmt, StmtKind, StructField, Symbol, TypeAnnotation, TypeAnnotationKind, UnaryOperator, + Scope, Spanned, Stmt, StmtKind, Struct, StructField, Symbol, TypeAnnotation, TypeAnnotationKind, UnaryOperator, UserDefinedType, }; use crate::comp_ctx::{CompilationContext, CompilationPhase}; @@ -866,12 +866,12 @@ impl<'s> Parser<'s> { )?; } - Ok(StmtKind::Struct { + Ok(StmtKind::Struct(Struct { name, extends, fields: members, access, - }) + })) } fn build_variable_def_statement(&self, statement_node: &Node, phase: Phase) -> DiagnosticResult { @@ -1143,11 +1143,11 @@ impl<'s> Parser<'s> { )?; } - Ok(StmtKind::Enum { + Ok(StmtKind::Enum(Enum { name: name.unwrap(), values, access, - }) + })) } fn get_modifier<'a>(&'a self, modifier: &str, maybe_modifiers: &'a Option) -> DiagnosticResult> { diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index c2e96d900a7..f900ec1f3ca 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -11,9 +11,10 @@ use crate::ast::{ TypeAnnotationKind, UtilityFunctions, }; use crate::ast::{ - ArgList, BinaryOperator, Class as AstClass, Elifs, Expr, ExprKind, FunctionBody, + ArgList, BinaryOperator, Class as AstClass, Elifs, Enum as AstEnum, Expr, ExprKind, FunctionBody, FunctionParameter as AstFunctionParameter, Interface as AstInterface, InterpolatedStringPart, Literal, Phase, - Reference, Scope, Spanned, Stmt, StmtKind, Symbol, TypeAnnotation, UnaryOperator, UserDefinedType, + Reference, Scope, Spanned, Stmt, StmtKind, Struct as AstStruct, Symbol, TypeAnnotation, UnaryOperator, + UserDefinedType, }; use crate::comp_ctx::{CompilationContext, CompilationPhase}; use crate::diagnostic::{report_diagnostic, Diagnostic, DiagnosticAnnotation, TypeError, WingSpan}; @@ -3324,6 +3325,8 @@ impl<'a> TypeChecker<'a> { assert!(self.inner_scopes.is_empty()); let mut env = self.types.get_scope_env(scope); + self.hoist_type_definitions(scope, &mut env); + for statement in scope.statements.iter() { self.type_check_statement(statement, &mut env); } @@ -3383,6 +3386,281 @@ impl<'a> TypeChecker<'a> { } } + /// Check if there are any type declaration statements in the given scope. + /// If so, define the the respective types in the environment so that the type can be referenced by other + /// type declarations, even if they come before the type declaration. + fn hoist_type_definitions(&mut self, scope: &Scope, env: &mut SymbolEnv) { + for statement in scope.statements.iter() { + match &statement.kind { + StmtKind::Bring { source, identifier } => self.hoist_bring_statement(source, identifier, statement, env), + StmtKind::Struct(st) => self.hoist_struct_definition(st, env), + StmtKind::Interface(iface) => self.hoist_interface_definition(iface, env), + StmtKind::Enum(enu) => self.hoist_enum_definition(enu, env), + _ => {} + } + } + } + + fn hoist_bring_statement( + &mut self, + source: &BringSource, + identifier: &Option, + stmt: &Stmt, + env: &mut SymbolEnv, + ) { + let library_name: String; + let namespace_filter: Vec; + let alias: &Symbol; + match &source { + BringSource::BuiltinModule(name) => { + if WINGSDK_BRINGABLE_MODULES.contains(&name.name.as_str()) { + library_name = WINGSDK_ASSEMBLY_NAME.to_string(); + namespace_filter = vec![name.name.clone()]; + alias = identifier.as_ref().unwrap_or(&name); + } else if name.name.as_str() == WINGSDK_STD_MODULE { + self.spanned_error(stmt, format!("Redundant bring of \"{}\"", WINGSDK_STD_MODULE)); + return; + } else { + self.spanned_error(stmt, format!("\"{}\" is not a built-in module", name)); + return; + } + } + BringSource::JsiiModule(name) => { + library_name = name.name.to_string(); + // no namespace filter (we only support importing entire libraries at the moment) + namespace_filter = vec![]; + alias = identifier.as_ref().unwrap(); + } + BringSource::WingFile(name) => { + let brought_env = match self.types.source_file_envs.get(Utf8Path::new(&name.name)) { + Some(SymbolEnvOrNamespace::SymbolEnv(env)) => *env, + Some(SymbolEnvOrNamespace::Namespace(_)) => { + panic!("Expected a symbol environment to be associated with the file") + } + Some(SymbolEnvOrNamespace::Error(diagnostic)) => { + report_diagnostic(Diagnostic { + span: Some(stmt.span()), + ..diagnostic.clone() + }); + return; + } + None => { + self.spanned_error( + stmt, + format!("Could not type check \"{}\" due to cyclic bring statements", name), + ); + return; + } + }; + let ns = self.types.add_namespace(Namespace { + name: name.name.to_string(), + envs: vec![brought_env], + loaded: true, + module_path: ResolveSource::WingFile, + }); + if let Err(e) = env.define( + identifier.as_ref().unwrap(), + SymbolKind::Namespace(ns), + AccessModifier::Private, + StatementIdx::Top, + ) { + self.type_error(e); + } + return; + } + BringSource::WingLibrary(name, module_dir) | BringSource::TrustedModule(name, module_dir) => { + let brought_ns = match self.types.source_file_envs.get(module_dir) { + Some(SymbolEnvOrNamespace::SymbolEnv(_)) => { + panic!("Expected a namespace to be associated with the library") + } + Some(SymbolEnvOrNamespace::Namespace(ns)) => ns, + Some(SymbolEnvOrNamespace::Error(diagnostic)) => { + report_diagnostic(Diagnostic { + span: Some(stmt.span()), + ..diagnostic.clone() + }); + return; + } + None => { + self.spanned_error( + stmt, + format!("Could not type check \"{}\" due to cyclic bring statements", name), + ); + return; + } + }; + if let Err(e) = env.define( + identifier.as_ref().unwrap_or(&name), + SymbolKind::Namespace(*brought_ns), + AccessModifier::Private, + StatementIdx::Top, + ) { + self.type_error(e); + } + return; + } + BringSource::Directory(name) => { + let brought_ns = match self.types.source_file_envs.get(Utf8Path::new(&name.name)) { + Some(SymbolEnvOrNamespace::SymbolEnv(_)) => { + panic!("Expected a namespace to be associated with the directory") + } + Some(SymbolEnvOrNamespace::Namespace(ns)) => ns, + Some(SymbolEnvOrNamespace::Error(diagnostic)) => { + report_diagnostic(Diagnostic { + span: Some(stmt.span()), + ..diagnostic.clone() + }); + return; + } + None => { + self.spanned_error( + stmt, + format!("Could not type check \"{}\" due to cyclic bring statements", name), + ); + return; + } + }; + if let Err(e) = env.define( + identifier.as_ref().unwrap(), + SymbolKind::Namespace(*brought_ns), + AccessModifier::Private, + StatementIdx::Top, + ) { + self.type_error(e); + } + return; + } + } + self.add_jsii_module_to_env(env, library_name, namespace_filter, alias, Some(&stmt)); + // library_name is the name of the library we are importing from the JSII world + // namespace_filter describes what types we are importing from the library + // e.g. [] means we are importing everything from `mylib` + // e.g. ["ns1", "ns2"] means we are importing everything from `mylib.ns1.ns2` + // alias is the symbol we are giving to the imported library or namespace + } + + fn hoist_struct_definition(&mut self, st: &AstStruct, env: &mut SymbolEnv) { + let AstStruct { + name, extends, access, .. + } = st; + + // Structs can't be defined in preflight or inflight contexts, only at the top-level of a program + if let Some(_) = env.parent { + self.spanned_error( + name, + format!("struct \"{name}\" must be declared at the top-level of a file"), + ); + } + + // Create environment representing this struct, for now it'll be empty just so we can support referencing it + let dummy_env = SymbolEnv::new(None, SymbolEnvKind::Type(self.types.void()), env.phase, 0); + + // Collect types this struct extends + let extends_types = extends + .iter() + .filter_map(|ext| { + let t = self + .resolve_user_defined_type(ext, &env, self.ctx.current_stmt_idx()) + .unwrap_or_else(|e| self.type_error(e)); + if t.as_struct().is_some() { + Some(t) + } else { + self.spanned_error(ext, format!("Expected a struct, found type \"{}\"", t)); + None + } + }) + .collect::>(); + + // Create the struct type with the empty environment + let struct_type = self.types.add_type(Type::Struct(Struct { + name: name.clone(), + fqn: None, + extends: extends_types.clone(), + env: dummy_env, + docs: Docs::default(), + })); + + match env.define(name, SymbolKind::Type(struct_type), *access, StatementIdx::Top) { + Err(type_error) => { + self.type_error(type_error); + } + _ => {} + }; + } + + fn hoist_interface_definition(&mut self, iface: &AstInterface, env: &mut SymbolEnv) { + // Create environment representing this interface, for now it'll be empty just so we can support referencing ourselves + // from the interface definition or by other type definitions that come before the interface statement. + let dummy_env = SymbolEnv::new( + None, + SymbolEnvKind::Type(self.types.void()), + env.phase, + self.ctx.current_stmt_idx(), + ); + + // Collect types this interface extends + let extend_interfaces = iface + .extends + .iter() + .filter_map(|i| { + let t = self + .resolve_user_defined_type(i, env, self.ctx.current_stmt_idx()) + .unwrap_or_else(|e| self.type_error(e)); + if t.as_interface().is_some() { + Some(t) + } else { + // The type checker resolves non-existing definitions to `any`, so we avoid duplicate errors by checking for that here + if !t.is_unresolved() { + self.spanned_error(i, format!("Expected an interface, instead found type \"{}\"", t)); + } + None + } + }) + .collect::>(); + + // Create the interface type with the empty environment + let interface_spec = Interface { + name: iface.name.clone(), + fqn: None, + docs: Docs::default(), + env: dummy_env, + extends: extend_interfaces.clone(), + }; + let interface_type = self.types.add_type(Type::Interface(interface_spec)); + + match env.define( + &iface.name, + SymbolKind::Type(interface_type), + iface.access, + StatementIdx::Top, + ) { + Err(type_error) => { + self.type_error(type_error); + } + _ => {} + }; + } + + fn hoist_enum_definition(&mut self, enu: &AstEnum, env: &mut SymbolEnv) { + let enum_type_ref = self.types.add_type(Type::Enum(Enum { + name: enu.name.clone(), + values: enu.values.clone(), + docs: Default::default(), + })); + + match env.define( + &enu.name, + SymbolKind::Type(enum_type_ref), + enu.access, + StatementIdx::Top, + ) { + Err(type_error) => { + self.type_error(type_error); + } + _ => {} + }; + } + fn resolve_type_annotation(&mut self, annotation: &TypeAnnotation, env: &SymbolEnv) -> TypeRef { match &annotation.kind { TypeAnnotationKind::Inferred => self.types.make_inference(), @@ -3568,8 +3846,8 @@ impl<'a> TypeChecker<'a> { StmtKind::Assignment { kind, variable, value } => { tc.type_check_assignment(kind, value, variable, env); } - StmtKind::Bring { source, identifier } => { - tc.type_check_bring(source, identifier, stmt, env); + StmtKind::Bring { .. } => { + // nothing to do here - bring statements are hoisted during type_check_scope } StmtKind::Scope(scope) => { let scope_env = tc.types.add_symbol_env(SymbolEnv::new( @@ -3593,16 +3871,11 @@ impl<'a> TypeChecker<'a> { StmtKind::Interface(ast_iface) => { tc.type_check_interface(ast_iface, env); } - StmtKind::Struct { - name, - extends, - fields, - access, - } => { - tc.type_check_struct(fields, extends, name, access, env); + StmtKind::Struct(st) => { + tc.type_check_struct(st, env); } - StmtKind::Enum { name, values, access } => { - tc.type_check_enum(name, values, access, env); + StmtKind::Enum(_) => { + // nothing to do here - enums are hoisted during type_check_scope } StmtKind::TryCatch { try_statements, @@ -3678,80 +3951,30 @@ impl<'a> TypeChecker<'a> { } } - fn type_check_enum( - &mut self, - name: &Symbol, - values: &IndexSet, - access: &AccessModifier, - env: &mut SymbolEnv, - ) { - let enum_type_ref = self.types.add_type(Type::Enum(Enum { - name: name.clone(), - values: values.clone(), - docs: Default::default(), - })); - - match env.define(name, SymbolKind::Type(enum_type_ref), *access, StatementIdx::Top) { - Err(type_error) => { - self.type_error(type_error); - } - _ => {} - }; - } + fn type_check_struct(&mut self, st: &AstStruct, env: &mut SymbolEnv) { + let AstStruct { + name, + extends: _, + fields, + access: _, + } = st; + + // Note: to support mutually recursive type definitions (types that refer to each other), struct types + // are initialized during `type_check_scope`. The struct type is created with a dummy environment and + // then replaced with the real environment after the struct's fields are type checked. + // In this method, we are filling in the struct's environment with the fields and parent members + // and updating the struct's Type with the new environment. + let mut struct_type = env + .lookup(name, Some(self.ctx.current_stmt_idx())) + .expect("struct type should have been defined in the environment") + .as_type() + .unwrap(); - fn type_check_struct( - &mut self, - fields: &Vec, - extends: &Vec, - name: &Symbol, - access: &AccessModifier, - env: &mut SymbolEnv, - ) { - // Structs can't be defined in preflight or inflight contexts, only at the top-level of a program - if let Some(_) = env.parent { - self.spanned_error( - name, - format!("struct \"{name}\" must be declared at the top-level of a file"), - ); - } // Note: structs don't have a parent environment, instead they flatten their parent's members into the struct's env. // If we encounter an existing member with the same name and type we skip it, if the types are different we // fail type checking. - // Create a dummy environment for the struct so we can create the struct type - let dummy_env = SymbolEnv::new( - None, - SymbolEnvKind::Type(self.types.void()), - Phase::Independent, - self.ctx.current_stmt_idx(), - ); - - // Collect types this struct extends - let extends_types = extends - .iter() - .filter_map(|ext| { - let t = self - .resolve_user_defined_type(ext, env, self.ctx.current_stmt_idx()) - .unwrap_or_else(|e| self.type_error(e)); - if t.as_struct().is_some() { - Some(t) - } else { - self.spanned_error(ext, format!("Expected a struct, found type \"{}\"", t)); - None - } - }) - .collect::>(); - - // Create the struct type - let mut struct_type = self.types.add_type(Type::Struct(Struct { - name: name.clone(), - fqn: None, - extends: extends_types.clone(), - env: dummy_env, - docs: Docs::default(), - })); - - // Create the actual environment for the struct + // Create an environment for the struct's members let mut struct_env = SymbolEnv::new( None, SymbolEnvKind::Type(struct_type), @@ -3786,15 +4009,10 @@ impl<'a> TypeChecker<'a> { }; } - if let Err(e) = add_parent_members_to_struct_env(&extends_types, name, &mut struct_env) { + let extends_types = &struct_type.as_struct().unwrap().extends; + if let Err(e) = add_parent_members_to_struct_env(extends_types, name, &mut struct_env) { self.type_error(e); } - match env.define(name, SymbolKind::Type(struct_type), *access, StatementIdx::Top) { - Err(type_error) => { - self.type_error(type_error); - } - _ => {} - }; // Replace the dummy struct environment with the real one struct_type.as_mut_struct().unwrap().env = struct_env; @@ -3803,51 +4021,21 @@ impl<'a> TypeChecker<'a> { fn type_check_interface(&mut self, ast_iface: &AstInterface, env: &mut SymbolEnv) { let AstInterface { name, - extends, + extends: _, methods, - access, + access: _, } = ast_iface; - // Create environment representing this interface, for now it'll be empty just so we can support referencing ourselves from the interface definition. - let dummy_env = SymbolEnv::new( - None, - SymbolEnvKind::Type(self.types.void()), - env.phase, - self.ctx.current_stmt_idx(), - ); - let extend_interfaces = extends - .iter() - .filter_map(|i| { - let t = self - .resolve_user_defined_type(i, env, self.ctx.current_stmt_idx()) - .unwrap_or_else(|e| self.type_error(e)); - if t.as_interface().is_some() { - Some(t) - } else { - // The type checker resolves non-existing definitions to `any`, so we avoid duplicate errors by checking for that here - if !t.is_unresolved() { - self.spanned_error(i, format!("Expected an interface, instead found type \"{}\"", t)); - } - None - } - }) - .collect::>(); - - // Create the interface type and add it to the current environment (so interface implementation can reference itself) - let interface_spec = Interface { - name: name.clone(), - fqn: None, - docs: Docs::default(), - env: dummy_env, - extends: extend_interfaces.clone(), - }; - let mut interface_type = self.types.add_type(Type::Interface(interface_spec)); - match env.define(name, SymbolKind::Type(interface_type), *access, StatementIdx::Top) { - Err(type_error) => { - self.type_error(type_error); - } - _ => {} - }; + // Note: to support mutually recursive type definitions (types that refer to each other), interface types + // are initialized during `type_check_scope`. The interface type is created with a dummy environment and + // then replaced with the real environment after the interface's fields are type checked. + // In this method, we are filling in the interface's environment with its members + // and updating the interface's Type with the new environment. + let mut interface_type = env + .lookup(name, Some(self.ctx.current_stmt_idx())) + .expect("interface type should have been defined in the environment") + .as_type() + .unwrap(); // Create the real interface environment to be filled with the interface AST types let mut interface_env = SymbolEnv::new( @@ -3889,7 +4077,8 @@ impl<'a> TypeChecker<'a> { } // add methods from all extended interfaces to the interface env - if let Err(e) = add_parent_members_to_iface_env(&extend_interfaces, name, &mut interface_env) { + let extend_interfaces = &interface_type.as_interface().unwrap().extends; + if let Err(e) = add_parent_members_to_iface_env(extend_interfaces, name, &mut interface_env) { self.type_error(e); } @@ -4172,138 +4361,6 @@ impl<'a> TypeChecker<'a> { self.validate_type(exp_type, self.types.string(), exp); } - fn type_check_bring(&mut self, source: &BringSource, identifier: &Option, stmt: &Stmt, env: &mut SymbolEnv) { - let library_name: String; - let namespace_filter: Vec; - let alias: &Symbol; - match &source { - BringSource::BuiltinModule(name) => { - if WINGSDK_BRINGABLE_MODULES.contains(&name.name.as_str()) { - library_name = WINGSDK_ASSEMBLY_NAME.to_string(); - namespace_filter = vec![name.name.clone()]; - alias = identifier.as_ref().unwrap_or(&name); - } else if name.name.as_str() == WINGSDK_STD_MODULE { - self.spanned_error(stmt, format!("Redundant bring of \"{}\"", WINGSDK_STD_MODULE)); - return; - } else { - self.spanned_error(stmt, format!("\"{}\" is not a built-in module", name)); - return; - } - } - BringSource::JsiiModule(name) => { - library_name = name.name.to_string(); - // no namespace filter (we only support importing entire libraries at the moment) - namespace_filter = vec![]; - alias = identifier.as_ref().unwrap(); - } - BringSource::WingFile(name) => { - let brought_env = match self.types.source_file_envs.get(Utf8Path::new(&name.name)) { - Some(SymbolEnvOrNamespace::SymbolEnv(env)) => *env, - Some(SymbolEnvOrNamespace::Namespace(_)) => { - panic!("Expected a symbol environment to be associated with the file") - } - Some(SymbolEnvOrNamespace::Error(diagnostic)) => { - report_diagnostic(Diagnostic { - span: Some(stmt.span()), - ..diagnostic.clone() - }); - return; - } - None => { - self.spanned_error( - stmt, - format!("Could not type check \"{}\" due to cyclic bring statements", name), - ); - return; - } - }; - let ns = self.types.add_namespace(Namespace { - name: name.name.to_string(), - envs: vec![brought_env], - loaded: true, - module_path: ResolveSource::WingFile, - }); - if let Err(e) = env.define( - identifier.as_ref().unwrap(), - SymbolKind::Namespace(ns), - AccessModifier::Private, - StatementIdx::Top, - ) { - self.type_error(e); - } - return; - } - BringSource::WingLibrary(name, module_dir) | BringSource::TrustedModule(name, module_dir) => { - let brought_ns = match self.types.source_file_envs.get(module_dir) { - Some(SymbolEnvOrNamespace::SymbolEnv(_)) => { - panic!("Expected a namespace to be associated with the library") - } - Some(SymbolEnvOrNamespace::Namespace(ns)) => ns, - Some(SymbolEnvOrNamespace::Error(diagnostic)) => { - report_diagnostic(Diagnostic { - span: Some(stmt.span()), - ..diagnostic.clone() - }); - return; - } - None => { - self.spanned_error( - stmt, - format!("Could not type check \"{}\" due to cyclic bring statements", name), - ); - return; - } - }; - if let Err(e) = env.define( - identifier.as_ref().unwrap_or(&name), - SymbolKind::Namespace(*brought_ns), - AccessModifier::Private, - StatementIdx::Top, - ) { - self.type_error(e); - } - return; - } - BringSource::Directory(name) => { - let brought_ns = match self.types.source_file_envs.get(Utf8Path::new(&name.name)) { - Some(SymbolEnvOrNamespace::SymbolEnv(_)) => { - panic!("Expected a namespace to be associated with the directory") - } - Some(SymbolEnvOrNamespace::Namespace(ns)) => ns, - Some(SymbolEnvOrNamespace::Error(diagnostic)) => { - report_diagnostic(Diagnostic { - span: Some(stmt.span()), - ..diagnostic.clone() - }); - return; - } - None => { - self.spanned_error( - stmt, - format!("Could not type check \"{}\" due to cyclic bring statements", name), - ); - return; - } - }; - if let Err(e) = env.define( - identifier.as_ref().unwrap(), - SymbolKind::Namespace(*brought_ns), - AccessModifier::Private, - StatementIdx::Top, - ) { - self.type_error(e); - } - return; - } - } - self.add_jsii_module_to_env(env, library_name, namespace_filter, alias, Some(&stmt)); - // library_name is the name of the library we are importing from the JSII world - // namespace_filter describes what types we are importing from the library - // e.g. [] means we are importing everything from `mylib` - // e.g. ["ns1", "ns2"] means we are importing everything from `mylib.ns1.ns2` - // alias is the symbol we are giving to the imported library or namespace - } - fn type_check_assignment(&mut self, kind: &AssignmentKind, value: &Expr, variable: &Reference, env: &mut SymbolEnv) { let (exp_type, _) = self.type_check_exp(value, env); diff --git a/libs/wingc/src/visit.rs b/libs/wingc/src/visit.rs index fa127bd37cf..465a98d62f8 100644 --- a/libs/wingc/src/visit.rs +++ b/libs/wingc/src/visit.rs @@ -1,8 +1,8 @@ use crate::{ ast::{ - ArgList, BringSource, CalleeKind, Class, Elifs, Expr, ExprKind, FunctionBody, FunctionDefinition, + ArgList, BringSource, CalleeKind, Class, Elifs, Enum, Expr, ExprKind, FunctionBody, FunctionDefinition, FunctionParameter, FunctionSignature, IfLet, Interface, InterpolatedStringPart, Literal, New, Reference, Scope, - Stmt, StmtKind, Symbol, TypeAnnotation, TypeAnnotationKind, UserDefinedType, + Stmt, StmtKind, Struct, Symbol, TypeAnnotation, TypeAnnotationKind, UserDefinedType, }, dbg_panic, }; @@ -42,9 +42,15 @@ pub trait Visit<'ast> { fn visit_class(&mut self, node: &'ast Class) { visit_class(self, node); } + fn visit_struct(&mut self, node: &'ast Struct) { + visit_struct(self, node); + } fn visit_interface(&mut self, node: &'ast Interface) { visit_interface(self, node); } + fn visit_enum(&mut self, node: &'ast Enum) { + visit_enum(self, node); + } fn visit_expr(&mut self, node: &'ast Expr) { visit_expr(self, node); } @@ -178,9 +184,7 @@ where v.visit_scope(statements); } } - StmtKind::Expression(expr) => { - v.visit_expr(&expr); - } + StmtKind::Expression(expr) => v.visit_expr(&expr), StmtKind::Assignment { kind: _, variable, @@ -194,43 +198,12 @@ where v.visit_expr(expr); } } - StmtKind::Throw(expr) => { - v.visit_expr(expr); - } - StmtKind::Scope(scope) => { - v.visit_scope(scope); - } - StmtKind::Class(class) => { - v.visit_class(class); - } - StmtKind::Interface(interface) => { - v.visit_interface(interface); - } - StmtKind::Struct { - name, - extends, - fields, - access: _, - } => { - v.visit_symbol(name); - for extend in extends { - v.visit_user_defined_type(extend); - } - for member in fields { - v.visit_symbol(&member.name); - v.visit_type_annotation(&member.member_type); - } - } - StmtKind::Enum { - name, - values, - access: _, - } => { - v.visit_symbol(name); - for value in values { - v.visit_symbol(value); - } - } + StmtKind::Throw(expr) => v.visit_expr(expr), + StmtKind::Scope(scope) => v.visit_scope(scope), + StmtKind::Class(class) => v.visit_class(class), + StmtKind::Interface(interface) => v.visit_interface(interface), + StmtKind::Struct(st) => v.visit_struct(st), + StmtKind::Enum(enu) => v.visit_enum(enu), StmtKind::TryCatch { try_statements, catch_block, @@ -279,6 +252,20 @@ where } } +pub fn visit_struct<'ast, V>(v: &mut V, node: &'ast Struct) +where + V: Visit<'ast> + ?Sized, +{ + v.visit_symbol(&node.name); + for extend in &node.extends { + v.visit_user_defined_type(&extend); + } + for member in &node.fields { + v.visit_symbol(&member.name); + v.visit_type_annotation(&member.member_type); + } +} + pub fn visit_interface<'ast, V>(v: &mut V, node: &'ast Interface) where V: Visit<'ast> + ?Sized, @@ -295,6 +282,16 @@ where } } +pub fn visit_enum<'ast, V>(v: &mut V, node: &'ast Enum) +where + V: Visit<'ast> + ?Sized, +{ + v.visit_symbol(&node.name); + for value in &node.values { + v.visit_symbol(value); + } +} + pub fn visit_new_expr<'ast, V>(v: &mut V, node: &'ast New) where V: Visit<'ast> + ?Sized, diff --git a/tools/hangar/__snapshots__/invalid.ts.snap b/tools/hangar/__snapshots__/invalid.ts.snap index 3fb33d14cb1..8f12169801f 100644 --- a/tools/hangar/__snapshots__/invalid.ts.snap +++ b/tools/hangar/__snapshots__/invalid.ts.snap @@ -2368,11 +2368,11 @@ error: Unknown symbol \\"IDontExist\\" | ^^^^^^^^^^ Unknown symbol \\"IDontExist\\" -error: Expected an interface, instead found type \\"ISomeClass\\" +error: Unknown symbol \\"ISomeClass\\" --> ../../../examples/tests/invalid/interface.test.w:16:34 | 16 | interface ISomeInterface extends ISomeClass { - | ^^^^^^^^^^ Expected an interface, instead found type \\"ISomeClass\\" + | ^^^^^^^^^^ Unknown symbol \\"ISomeClass\\" error: Symbol \\"foo\\" already defined in this scope diff --git a/tools/hangar/__snapshots__/test_corpus/valid/bring_local.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/bring_local.test.w_compile_tf-aws.md index 62ec7b25dba..748b9eebc33 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/bring_local.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/bring_local.test.w_compile_tf-aws.md @@ -411,6 +411,14 @@ const $helpers = $stdlib.helpers; const file3 = require("./preflight.empty-1.js"); const math = $stdlib.math; const cloud = $stdlib.cloud; +const Color = + (function (tmp) { + tmp[tmp["RED"] = 0] = ",RED"; + tmp[tmp["GREEN"] = 1] = ",GREEN"; + tmp[tmp["BLUE"] = 2] = ",BLUE"; + return tmp; + })({}) +; class Util extends $stdlib.std.Resource { constructor($scope, $id, ) { super($scope, $id); @@ -509,14 +517,6 @@ class Store extends $stdlib.std.Resource { }); } } -const Color = - (function (tmp) { - tmp[tmp["RED"] = 0] = ",RED"; - tmp[tmp["GREEN"] = 1] = ",GREEN"; - tmp[tmp["BLUE"] = 2] = ",BLUE"; - return tmp; - })({}) -; module.exports = { Util, Store, Color }; //# sourceMappingURL=preflight.store-2.js.map ``` diff --git a/tools/hangar/__snapshots__/test_corpus/valid/enums.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/enums.test.w_compile_tf-aws.md index 194bf12b887..f52c6f0468b 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/enums.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/enums.test.w_compile_tf-aws.md @@ -52,6 +52,14 @@ const $helpers = $stdlib.helpers; class $Root extends $stdlib.std.Resource { constructor($scope, $id) { super($scope, $id); + const SomeEnum = + (function (tmp) { + tmp[tmp["ONE"] = 0] = ",ONE"; + tmp[tmp["TWO"] = 1] = ",TWO"; + tmp[tmp["THREE"] = 2] = ",THREE"; + return tmp; + })({}) + ; class $Closure1 extends $stdlib.std.AutoIdResource { _id = $stdlib.core.closureId(); constructor($scope, $id, ) { @@ -89,14 +97,8 @@ class $Root extends $stdlib.std.Resource { }); } } - const SomeEnum = - (function (tmp) { - tmp[tmp["ONE"] = 0] = ",ONE"; - tmp[tmp["TWO"] = 1] = ",TWO"; - tmp[tmp["THREE"] = 2] = ",THREE"; - return tmp; - })({}) - ; + const opt = undefined; + const three = SomeEnum.THREE; const one = SomeEnum.ONE; const two = SomeEnum.TWO; this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:inflight", new $Closure1(this, "$Closure1")); diff --git a/tools/hangar/__snapshots__/test_corpus/valid/interface.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/interface.test.w_compile_tf-aws.md index 01338876c20..1afaf41a4bd 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/interface.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/interface.test.w_compile_tf-aws.md @@ -1,5 +1,33 @@ # [interface.test.w](../../../../../examples/tests/valid/interface.test.w) | compile | tf-aws +## inflight.C-1.js +```js +"use strict"; +const $helpers = require("@winglang/sdk/lib/helpers"); +module.exports = function({ }) { + class C { + constructor({ }) { + } + } + return C; +} +//# sourceMappingURL=inflight.C-1.js.map +``` + +## inflight.D-1.js +```js +"use strict"; +const $helpers = require("@winglang/sdk/lib/helpers"); +module.exports = function({ }) { + class D { + constructor({ }) { + } + } + return D; +} +//# sourceMappingURL=inflight.D-1.js.map +``` + ## main.tf.json ```json { @@ -31,6 +59,63 @@ const $helpers = $stdlib.helpers; class $Root extends $stdlib.std.Resource { constructor($scope, $id) { super($scope, $id); + class C extends $stdlib.std.Resource { + constructor($scope, $id, ) { + super($scope, $id); + } + static _toInflightType() { + return ` + require("${$helpers.normalPath(__dirname)}/inflight.C-1.js")({ + }) + `; + } + _toInflight() { + return ` + (await (async () => { + const CClient = ${C._toInflightType()}; + const client = new CClient({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `; + } + get _liftMap() { + return ({ + "$inflight_init": [ + ], + }); + } + } + class D extends $stdlib.std.Resource { + constructor($scope, $id, ) { + super($scope, $id); + } + static _toInflightType() { + return ` + require("${$helpers.normalPath(__dirname)}/inflight.D-1.js")({ + }) + `; + } + _toInflight() { + return ` + (await (async () => { + const DClient = ${D._toInflightType()}; + const client = new DClient({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `; + } + get _liftMap() { + return ({ + "$inflight_init": [ + ], + }); + } + } + const a = undefined; } } const $PlatformManager = new $stdlib.platform.PlatformManager({platformPaths: $platforms}); 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 fcd1ffdcfcf..4790c095540 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 @@ -705,6 +705,14 @@ const cloud = $stdlib.cloud; class $Root extends $stdlib.std.Resource { constructor($scope, $id) { super($scope, $id); + const MyEnum = + (function (tmp) { + tmp[tmp["A"] = 0] = ",A"; + tmp[tmp["B"] = 1] = ",B"; + tmp[tmp["C"] = 2] = ",C"; + return tmp; + })({}) + ; class Foo extends $stdlib.std.Resource { constructor($scope, $id, ) { super($scope, $id); @@ -1104,14 +1112,6 @@ class $Root extends $stdlib.std.Resource { }); } } - const MyEnum = - (function (tmp) { - tmp[tmp["A"] = 0] = ",A"; - tmp[tmp["B"] = 1] = ",B"; - tmp[tmp["C"] = 2] = ",C"; - return tmp; - })({}) - ; const bucket = this.node.root.new("@winglang/sdk.cloud.Bucket", cloud.Bucket, this, "cloud.Bucket"); const res = new Bar(this, "Bar", "Arr", bucket, MyEnum.B); this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:test", new $Closure1(this, "$Closure1")); diff --git a/tools/hangar/__snapshots__/test_corpus/valid/store.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/store.w_compile_tf-aws.md index 912476a8539..23d5f2f9da1 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/store.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/store.w_compile_tf-aws.md @@ -71,6 +71,14 @@ const $helpers = $stdlib.helpers; const file3 = require("./preflight.empty-1.js"); const math = $stdlib.math; const cloud = $stdlib.cloud; +const Color = + (function (tmp) { + tmp[tmp["RED"] = 0] = ",RED"; + tmp[tmp["GREEN"] = 1] = ",GREEN"; + tmp[tmp["BLUE"] = 2] = ",BLUE"; + return tmp; + })({}) +; class Util extends $stdlib.std.Resource { constructor($scope, $id, ) { super($scope, $id); @@ -169,14 +177,6 @@ class Store extends $stdlib.std.Resource { }); } } -const Color = - (function (tmp) { - tmp[tmp["RED"] = 0] = ",RED"; - tmp[tmp["GREEN"] = 1] = ",GREEN"; - tmp[tmp["BLUE"] = 2] = ",BLUE"; - return tmp; - })({}) -; module.exports = { Util, Store, Color }; //# sourceMappingURL=preflight.js.map ``` diff --git a/tools/hangar/__snapshots__/test_corpus/valid/structs.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/structs.test.w_compile_tf-aws.md index 0b52330d732..c94bcc2bab4 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/structs.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/structs.test.w_compile_tf-aws.md @@ -136,6 +136,7 @@ class $Root extends $stdlib.std.Resource { }); } } + const a = undefined; const x = ({"field0": "Sup"}); const y = ({"field0": "hello", "field1": 1, "field2": "world", "field3": ({"field0": "foo"})}); $helpers.assert($helpers.eq(x.field0, "Sup"), "x.field0 == \"Sup\"");