From c9422efa440abc4b825881af74e6007dd66e67c9 Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Thu, 22 Sep 2022 14:51:49 +0300 Subject: [PATCH 01/31] wip: Import JSII types to get `bring cloud` working. --- Cargo.lock | 14 ++ libs/wingc/Cargo.toml | 3 + libs/wingc/src/jsify.rs | 24 +--- libs/wingc/src/lib.rs | 2 +- libs/wingc/src/type_check.rs | 243 ++++++++++++++++++++++++++++++++++- package-lock.json | 1 + 6 files changed, 259 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d426e51ce8e..34d39942af4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -218,6 +218,17 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + [[package]] name = "const_format" version = "0.2.26" @@ -2049,11 +2060,14 @@ dependencies = [ "base16ct", "cbindgen", "cc", + "colored", "derivative", "relative-path", + "serde_json", "sha2", "tree-sitter", "tree-sitter-wing", + "wingii", ] [[package]] diff --git a/libs/wingc/Cargo.toml b/libs/wingc/Cargo.toml index 7c55d77f552..46b75319e1a 100644 --- a/libs/wingc/Cargo.toml +++ b/libs/wingc/Cargo.toml @@ -14,6 +14,9 @@ sha2 = "0.10.6" base16ct = { version = "0.1.1", features = ["alloc"] } derivative = "2.2.0" tree-sitter-wing = { path = "../tree-sitter-wing" } +wingii = { path = "../wingii" } +serde_json = "1.0" +colored = "2.0" [lib] crate-type = ["staticlib", "rlib"] diff --git a/libs/wingc/src/jsify.rs b/libs/wingc/src/jsify.rs index 570ca68b791..e2a4de5550b 100644 --- a/libs/wingc/src/jsify.rs +++ b/libs/wingc/src/jsify.rs @@ -132,31 +132,9 @@ fn jsify_arg_list(arg_list: &ArgList, scope: Option<&str>, id: Option<&str>) -> } } -// TODO: move this into Reference (but note this will only work after type checking is done on Reference) -fn is_resource(reference: &Reference) -> bool { - match reference { - Reference::Identifier(_) => { - // For now return false, but we need to lookup the identifier in our env - false - } - Reference::NestedIdentifier { object, property: _ } => { - // TODO: for now anything under "cloud" is a resource - if let ExprType::Reference(identifier) = &object.variant { - if let Reference::Identifier(identifier) = identifier { - identifier.name == "cloud" - } else { - false - } - } else { - false - } - } - } -} - fn is_resource_type(typ: &Type) -> bool { // TODO: for now anything under "cloud" is a resource - if let Type::CustomType { root, fields } = typ { + if let Type::CustomType { root, fields: _ } = typ { root.name == "cloud" } else { false diff --git a/libs/wingc/src/lib.rs b/libs/wingc/src/lib.rs index 742f4cfd1c0..e60d869fb55 100644 --- a/libs/wingc/src/lib.rs +++ b/libs/wingc/src/lib.rs @@ -115,7 +115,7 @@ mod sanity { for entry in paths { if let Ok(entry) = entry { if let Some(source) = entry.path().to_str() { - if source.ends_with(".w") { + if source.ends_with("sdk_capture_test.w") { println!("\n=== {} ===\n", source); println!("{}\n---", compile(source, None)); } diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index dd6781791cf..5b54b6036d0 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -1,8 +1,11 @@ use std::fmt::{Debug, Display}; use derivative::Derivative; +use serde_json::Value; +use wingii::jsii; use crate::ast::{Type as AstType, *}; +use crate::diagnostic::{CharacterLocation, WingSpan}; use crate::type_env::TypeEnv; #[derive(Debug)] @@ -13,11 +16,21 @@ pub enum Type { String, Duration, Boolean, - Function(Box), + Function(FunctionSignature), Class(Class), Resource(Class), ResourceObject(TypeRef), // Reference to a Resource type ClassInstance(TypeRef), // Reference to a Class type + Namespace(Namespace), +} + +const RESOURCE_CLASS_FQN: &'static str = "@monadahq/wingsdk.cloud.Resource"; +#[derive(Derivative)] +#[derivative(Debug)] +pub struct Namespace { + pub name: String, + #[derivative(Debug = "ignore")] + pub env: TypeEnv, } #[derive(Derivative)] @@ -141,6 +154,7 @@ impl Display for Type { let class_type_name = class.as_class().expect("Class instance must reference to a class"); write!(f, "instance of {}", class_type_name) } + Type::Namespace(namespace) => write!(f, "{}", namespace.name), } } } @@ -209,6 +223,14 @@ impl TypeRef { false } } + + pub fn as_namespace(&self) -> Option<&Namespace> { + if let &Type::Namespace(ref ns) = (*self).into() { + Some(ns) + } else { + None + } + } } impl Display for TypeRef { @@ -531,7 +553,7 @@ impl<'a> TypeChecker<'a> { flight: ast_sig.flight, }; // TODO: avoid creating a new type for each function_sig resolution - self.types.add_type(Type::Function(Box::new(sig))) + self.types.add_type(Type::Function(sig)) } AstType::CustomType { root, fields } => { if fields.is_empty() { @@ -622,9 +644,49 @@ impl<'a> TypeChecker<'a> { self.validate_type(exp_type, var_type, value); } Statement::Use { - module_name: _, + module_name, identifier: _, - } => _ = unimplemented_type(), + } => { + _ = { + // Create a new env for the imported module's namespace + let mut namespace_env = TypeEnv::new(Some(env), None, false, env.flight); + + // TODO Hack: treat "cloud" as "cloud in wingsdk" until I figure out the path issue + if module_name.name == "cloud" { + let mut wingii_types = wingii::type_system::TypeSystem::new(); + let name = wingii_types.load("../../node_modules/@monadahq/wingsdk").unwrap(); + let prefix = format!("{}.{}.", name, module_name.name); + println!("Loaded JSII assembly {}", name); + let assembly = wingii_types.find_assembly(&name).unwrap(); + for type_name in assembly.types.as_ref().unwrap().keys() { + // Skip types outside the imported namespace + if !type_name.starts_with(&prefix) { + println!("Skipping {} (outside of module {})", type_name, module_name.name); + continue; + } + let type_name = type_name.strip_prefix(&prefix).unwrap(); + + // Lookup type before we attempt to import it, this is required because `import_jsii_type` is recursive + // and might have already defined the current type internally + println!("Going to import {}", type_name); + if namespace_env.try_lookup(type_name).is_some() { + println!("Type {} already defined, skipping", type_name); + continue; + } + self.import_jsii_type(type_name, &prefix, assembly, &wingii_types, &mut namespace_env); + } + + // Create a namespace for the imported module + let namespace = self.types.add_type(Type::Namespace(Namespace { + name: module_name.name.clone(), + env: namespace_env, + })); + env.define(module_name, namespace); + + println!("Loaded assembly: {}", assembly.name); + } + } + } Statement::Scope(scope) => { let mut scope_env = TypeEnv::new(Some(env), env.return_type, false, env.flight); for statement in scope.statements.iter_mut() { @@ -829,4 +891,177 @@ impl<'a> TypeChecker<'a> { } } } + + fn import_jsii_type( + &mut self, + type_name: &str, + prefix: &str, + jsii_assembly: &jsii::Assembly, + jsii_types: &wingii::type_system::TypeSystem, + env: &mut TypeEnv, + ) -> Option { + // TODO: avoid infinite recursion by keeping a set of type names we already tried to define + + // Hack: if the base class name is RESOURCE_CLASS_FQN then we treat this class as a resource and don't need to define it + println!("Defining type {}", type_name); + let fqn = format!("{}{}", prefix, type_name); + if fqn == RESOURCE_CLASS_FQN { + println!("Hack: no need to define {}", type_name); + return None; + } + + let jsii_class = jsii_types.find_class(&fqn); + if jsii_class.is_none() { + println!( + "Type {} is not a class in the JSII assembly {}", + fqn, jsii_assembly.name + ); + return None; + } + let jsii_class = jsii_class.unwrap(); + let mut is_resource = false; + + // Get the base class of the JSII class + let base_class = if let Some(base_class_name) = jsii_class.base { + // Hack: if the base class name is RESOURCE_CLASS_FQN then we treat this class as a resource and don't need to define its parent + if base_class_name == RESOURCE_CLASS_FQN { + println!("Hack: no need to define base class {}", base_class_name); + is_resource = true; + None + } else { + let base_class_name = base_class_name.strip_prefix(prefix).unwrap(); + println!("going to lookup base class {}", base_class_name); + let base_class_type = if let Some(base_class_type) = env.try_lookup(&base_class_name) { + Some(base_class_type) + } else { + // If the base class isn't defined yet then define it first (recursive call) + let base_class_type = self.import_jsii_type(&base_class_name, prefix, jsii_assembly, jsii_types, env); + if base_class_type.is_none() + /* && base_class_name != RESOURCE_CLASS_FQN*/ + { + panic!("Failed to define base class {}", base_class_name); + } + base_class_type + }; + + // Validate the base class is either a class or a resource + if let Some(base_class_type) = base_class_type { + if base_class_type.as_resource().is_none() && base_class_type.as_class().is_none() { + panic!("Base class {} of {} is not a resource", base_class_name, type_name); + } + } + base_class_type + } + } else { + None + }; + println!("got here! we can define {}", type_name); + + // Create class's environment + let mut class_env = TypeEnv::new(Some(env), None, true, env.flight); + + // Add methods to the class environment + if let Some(methods) = jsii_class.methods { + for m in methods { + if let Some(jsii_return_type) = m.returns { + //env.try_lookup(jsii_return_type.) + println!("Found method {} with return type {:?}", m.name, jsii_return_type); + } else { + println!("Method {} has no return type", m.name); + //None + } + // self.types.add_type(Type::Function(FunctionSignature { args: (), return_type: (), flight: env.flight })); + // env.define(m.name, type) + } + } + // Add properties to the class environment + if let Some(properties) = jsii_class.properties { + for p in properties { + println!("Found property {} with type {:?}", p.name, p.type_); + class_env.define( + &Self::jsii_name_to_symbol(&p.name, &p.location_in_module), + self.jsii_type_ref_to_wing_type(&p.type_), + ); + } + } + println!("Finally adding type {}", type_name); + let new_type_symbol = Self::jsii_name_to_symbol(type_name, &jsii_class.location_in_module); + + // If our base class is a resource then we are a resource + if let Some(base_class) = base_class { + is_resource = base_class.as_resource().is_some(); + } + + let new_type = if is_resource { + println!("{} is a resource", type_name); + Type::Resource(Class { + name: new_type_symbol.clone(), + env: class_env, + parent: base_class, + }) + } else { + println!("{} is a class", type_name); + Type::Class(Class { + name: new_type_symbol.clone(), + env: class_env, + parent: base_class, + }) + }; + + // Finally create a new type in the wing type system and define it in the module's namespace + let new_type = self.types.add_type(new_type); + env.define(&new_type_symbol, new_type); + Some(new_type) + } + + fn jsii_type_ref_to_wing_type(&self, jsii_type_ref: &jsii::TypeReference) -> TypeRef { + if let serde_json::Value::Object(obj) = jsii_type_ref { + if let Some(Value::String(primitive_name)) = obj.get("primitive") { + match primitive_name.as_str() { + "string" => self.types.string(), + "number" => self.types.number(), + "boolean" => self.types.bool(), + "any" => self.types.anything(), + _ => panic!("TODO: handle primitive type {}", primitive_name), + } + } else if let Some(Value::Object(o)) = obj.get("collection") { + // TODO: handle JSII to Wing collection type conversion, for now return any + self.types.anything() + } else { + panic!("TODO: handle non-primitive type {:?}", jsii_type_ref); + } + } else { + panic!("Expected JSII type reference {:?} to be an object", jsii_type_ref); + } + } + + fn jsii_name_to_symbol(name: &str, jsii_source_location: &Option) -> Symbol { + let span = if let Some(jsii_source_location) = jsii_source_location { + WingSpan { + start: CharacterLocation { + row: jsii_source_location.line as usize, + column: 0, + }, + end: CharacterLocation { + row: jsii_source_location.line as usize, + column: 0, + }, + start_byte: 0, + end_byte: 0, + file_id: (&jsii_source_location.filename).into(), + } + } else { + WingSpan { + start: CharacterLocation { row: 0, column: 0 }, + end: CharacterLocation { row: 0, column: 0 }, + start_byte: 0, + end_byte: 0, + file_id: "".into(), + } + }; + Symbol { + name: name.to_string(), + span, + } + } } diff --git a/package-lock.json b/package-lock.json index 202b2d4a3b2..3fd294d706f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ }, "apps/wingrt": { "version": "0.0.1", + "hasInstallScript": true, "license": "UNLICENSED", "dependencies": { "commander": "^9.4.0" From e84ce2f8e083a6041c909f62d48bcdb4f5430fa6 Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Thu, 22 Sep 2022 19:35:20 +0300 Subject: [PATCH 02/31] Removed `Nothing` from out type system. Merge jsii resource clients into generate wing resources. Handle JSII Void return types as no return type in wing. --- libs/wingc/src/type_check.rs | 167 ++++++++++++++++++++++++++--------- 1 file changed, 125 insertions(+), 42 deletions(-) diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index 5b54b6036d0..42a49f134e4 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -1,5 +1,6 @@ use std::fmt::{Debug, Display}; +use colored::Colorize; use derivative::Derivative; use serde_json::Value; use wingii::jsii; @@ -11,7 +12,6 @@ use crate::type_env::TypeEnv; #[derive(Debug)] pub enum Type { Anything, - Nothing, Number, String, Duration, @@ -111,7 +111,6 @@ impl Display for Type { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Type::Anything => write!(f, "anything"), - Type::Nothing => write!(f, "nothing"), Type::Number => write!(f, "number"), Type::String => write!(f, "string"), Type::Duration => write!(f, "duration"), @@ -682,8 +681,6 @@ impl<'a> TypeChecker<'a> { env: namespace_env, })); env.define(module_name, namespace); - - println!("Loaded assembly: {}", assembly.name); } } } @@ -911,7 +908,7 @@ impl<'a> TypeChecker<'a> { } let jsii_class = jsii_types.find_class(&fqn); - if jsii_class.is_none() { + if jsii_class.is_none() || jsii_class.as_ref().unwrap().kind != "class" { println!( "Type {} is not a class in the JSII assembly {}", fqn, jsii_assembly.name @@ -922,7 +919,7 @@ impl<'a> TypeChecker<'a> { let mut is_resource = false; // Get the base class of the JSII class - let base_class = if let Some(base_class_name) = jsii_class.base { + let base_class = if let Some(base_class_name) = &jsii_class.base { // Hack: if the base class name is RESOURCE_CLASS_FQN then we treat this class as a resource and don't need to define its parent if base_class_name == RESOURCE_CLASS_FQN { println!("Hack: no need to define base class {}", base_class_name); @@ -957,42 +954,35 @@ impl<'a> TypeChecker<'a> { }; println!("got here! we can define {}", type_name); + // If our base class is a resource then we are a resource + if let Some(base_class) = base_class { + is_resource = base_class.as_resource().is_some(); + } + // Create class's environment let mut class_env = TypeEnv::new(Some(env), None, true, env.flight); - // Add methods to the class environment - if let Some(methods) = jsii_class.methods { - for m in methods { - if let Some(jsii_return_type) = m.returns { - //env.try_lookup(jsii_return_type.) - println!("Found method {} with return type {:?}", m.name, jsii_return_type); + // Add methods and properties to the class environment + self.add_jsii_interface_members_to_class_env(&jsii_class, is_resource, Flight::Pre, &mut class_env); + let new_type_symbol = Self::jsii_name_to_symbol(type_name, &jsii_class.location_in_module); + + let new_type = if is_resource { + // Look for a client interface for this resource + let client_interface = jsii_types.find_interface(&format!("{}I{}Client", prefix, type_name)); + if let Some(client_interface) = client_interface { + if client_interface.kind == "interface" { + // Add client interface's methods to the class environment + self.add_jsii_interface_members_to_class_env(&client_interface, false, Flight::In, &mut class_env); } else { - println!("Method {} has no return type", m.name); - //None + println!( + "Hu?? {} is not an interface it's a {}", + client_interface.name, client_interface.kind + ); } - // self.types.add_type(Type::Function(FunctionSignature { args: (), return_type: (), flight: env.flight })); - // env.define(m.name, type) - } - } - // Add properties to the class environment - if let Some(properties) = jsii_class.properties { - for p in properties { - println!("Found property {} with type {:?}", p.name, p.type_); - class_env.define( - &Self::jsii_name_to_symbol(&p.name, &p.location_in_module), - self.jsii_type_ref_to_wing_type(&p.type_), - ); + } else { + println!("Resource {} doesn't not seem to have a client", type_name.bold()); } - } - println!("Finally adding type {}", type_name); - let new_type_symbol = Self::jsii_name_to_symbol(type_name, &jsii_class.location_in_module); - - // If our base class is a resource then we are a resource - if let Some(base_class) = base_class { - is_resource = base_class.as_resource().is_some(); - } - let new_type = if is_resource { println!("{} is a resource", type_name); Type::Resource(Class { name: new_type_symbol.clone(), @@ -1009,24 +999,94 @@ impl<'a> TypeChecker<'a> { }; // Finally create a new type in the wing type system and define it in the module's namespace + println!("Finally adding type {}", type_name.bold()); let new_type = self.types.add_type(new_type); env.define(&new_type_symbol, new_type); Some(new_type) } - fn jsii_type_ref_to_wing_type(&self, jsii_type_ref: &jsii::TypeReference) -> TypeRef { + fn add_jsii_interface_members_to_class_env( + &mut self, + jsii_class: &T, + is_resource: bool, + flight: Flight, + class_env: &mut TypeEnv, + ) { + assert!(!is_resource || flight == Flight::Pre); + if let Some(methods) = &jsii_class.methods() { + for m in methods { + // TODO: skip internal methods (for now we skip `capture` until we mark it as internal) + if is_resource && m.name == "capture" { + println!("Skipping capture method on resource"); + continue; + } + + println!("Adding method {} to class", m.name); + + let return_type = if let Some(jsii_return_type) = &m.returns { + self.jsii_optional_type_to_wing_type(&jsii_return_type) + } else { + None + }; + + let mut arg_types = vec![]; + if let Some(args) = &m.parameters { + for arg in args { + // TODO: handle arg.variadic and arg.optional + arg_types.push(self.jsii_type_ref_to_wing_type(&arg.type_).unwrap()); + } + } + + let method_sig = self.types.add_type(Type::Function(FunctionSignature { + args: arg_types, + return_type, + flight, + })); + class_env.define(&Self::jsii_name_to_symbol(&m.name, &m.location_in_module), method_sig) + } + } + // Add properties to the class environment + if let Some(properties) = &jsii_class.properties() { + for p in properties { + println!("Found property {} with type {:?}", p.name.bold(), p.type_); + if flight == Flight::In { + todo!("No support for inflight properties yet"); + } + class_env.define( + &Self::jsii_name_to_symbol(&p.name, &p.location_in_module), + self.jsii_type_ref_to_wing_type(&p.type_).unwrap(), + ); + } + } + } + + fn jsii_optional_type_to_wing_type(&self, jsii_optional_type: &jsii::OptionalValue) -> Option { + if let Some(true) = jsii_optional_type.optional { + // TODO: we assume Some(false) and None are both non-optional - verify!! + panic!("TODO: handle optional types"); + } + self.jsii_type_ref_to_wing_type(&jsii_optional_type.type_) + } + + fn jsii_type_ref_to_wing_type(&self, jsii_type_ref: &jsii::TypeReference) -> Option { if let serde_json::Value::Object(obj) = jsii_type_ref { if let Some(Value::String(primitive_name)) = obj.get("primitive") { match primitive_name.as_str() { - "string" => self.types.string(), - "number" => self.types.number(), - "boolean" => self.types.bool(), - "any" => self.types.anything(), + "string" => Some(self.types.string()), + "number" => Some(self.types.number()), + "boolean" => Some(self.types.bool()), + "any" => Some(self.types.anything()), _ => panic!("TODO: handle primitive type {}", primitive_name), } - } else if let Some(Value::Object(o)) = obj.get("collection") { + } else if let Some(Value::String(s)) = obj.get("fqn") { + if s == "@monadahq/wingsdk.cloud.Void" { + None + } else { + panic!("TODO: handle non-primitive type {:?}", jsii_type_ref); + } + } else if let Some(Value::Object(_)) = obj.get("collection") { // TODO: handle JSII to Wing collection type conversion, for now return any - self.types.anything() + Some(self.types.anything()) } else { panic!("TODO: handle non-primitive type {:?}", jsii_type_ref); } @@ -1065,3 +1125,26 @@ impl<'a> TypeChecker<'a> { } } } + +trait JsiiInterface { + fn methods<'a>(&'a self) -> &'a Option>; + fn properties<'a>(&'a self) -> &'a Option>; +} + +impl JsiiInterface for jsii::ClassType { + fn methods(&self) -> &Option> { + &self.methods + } + fn properties(&self) -> &Option> { + &self.properties + } +} + +impl JsiiInterface for jsii::InterfaceType { + fn methods(&self) -> &Option> { + &self.methods + } + fn properties(&self) -> &Option> { + &self.properties + } +} From d7348fb5f1ffbaaedb5ad050556b3805a09b97fe Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Tue, 27 Sep 2022 16:50:55 +0300 Subject: [PATCH 03/31] No need to double check jsii_reflection api following #159. --- libs/wingc/src/type_check.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index 07273e01ad2..01157d000ca 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -915,7 +915,7 @@ impl<'a> TypeChecker<'a> { } let jsii_class = jsii_types.find_class(&fqn); - if jsii_class.is_none() || jsii_class.as_ref().unwrap().kind != "class" { + if jsii_class.is_none() { println!( "Type {} is not a class in the JSII assembly {}", fqn, jsii_assembly.name @@ -977,15 +977,8 @@ impl<'a> TypeChecker<'a> { // Look for a client interface for this resource let client_interface = jsii_types.find_interface(&format!("{}I{}Client", prefix, type_name)); if let Some(client_interface) = client_interface { - if client_interface.kind == "interface" { - // Add client interface's methods to the class environment - self.add_jsii_interface_members_to_class_env(&client_interface, false, Flight::In, &mut class_env); - } else { - println!( - "Hu?? {} is not an interface it's a {}", - client_interface.name, client_interface.kind - ); - } + // Add client interface's methods to the class environment + self.add_jsii_interface_members_to_class_env(&client_interface, false, Flight::In, &mut class_env); } else { println!("Resource {} doesn't not seem to have a client", type_name.bold()); } From 9cf1d49319a057f50d32482e524a0d5b69e8c5ff Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Wed, 28 Sep 2022 12:58:58 +0300 Subject: [PATCH 04/31] WIP: JSII imports: * Added support fro `struct`s in parser/type_checker/jsification. * Import constructors (including skipping scope/id on resources). * Pass "self" argument to imported class methods. --- libs/wingc/src/ast.rs | 5 + libs/wingc/src/capture.rs | 5 + libs/wingc/src/jsify.rs | 19 ++++ libs/wingc/src/type_check.rs | 205 +++++++++++++++++++++++++++++------ 4 files changed, 203 insertions(+), 31 deletions(-) diff --git a/libs/wingc/src/ast.rs b/libs/wingc/src/ast.rs index e4ffd4efc93..d58735b1ff9 100644 --- a/libs/wingc/src/ast.rs +++ b/libs/wingc/src/ast.rs @@ -112,6 +112,11 @@ pub enum Statement { parent: Option, is_resource: bool, }, + Struct { + name: Symbol, + extends: Vec, + members: Vec, + }, } #[derive(Debug)] diff --git a/libs/wingc/src/capture.rs b/libs/wingc/src/capture.rs index 9bbeb8863cb..32ee6c4cba1 100644 --- a/libs/wingc/src/capture.rs +++ b/libs/wingc/src/capture.rs @@ -252,6 +252,11 @@ fn scan_captures_in_scope(scope: &Scope) -> Vec { } => { todo!() } + Statement::Struct { + name: _, + extends: _, + members: _, + } => {} } } res diff --git a/libs/wingc/src/jsify.rs b/libs/wingc/src/jsify.rs index e2a4de5550b..4c2cb987d6b 100644 --- a/libs/wingc/src/jsify.rs +++ b/libs/wingc/src/jsify.rs @@ -337,6 +337,25 @@ fn jsify_statement(statement: &Statement) -> String { .join("\n") ) } + Statement::Struct { name, extends, members } => { + format!( + "interface {}{} {{\n{}\n}}", + jsify_symbol(name), + if !extends.is_empty() { + format!( + " extends {}", + extends.iter().map(jsify_symbol).collect::>().join(", ") + ) + } else { + "".to_string() + }, + members + .iter() + .map(|m| jsify_class_member(m)) + .collect::>() + .join("\n") + ) + } } } diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index 01157d000ca..8b776b1e8b3 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -19,12 +19,16 @@ pub enum Type { Function(FunctionSignature), Class(Class), Resource(Class), + Struct(Struct), ResourceObject(TypeRef), // Reference to a Resource type ClassInstance(TypeRef), // Reference to a Class type + StructInstance(TypeRef), // Reference to a Struct type Namespace(Namespace), } const RESOURCE_CLASS_FQN: &'static str = "@monadahq/wingsdk.cloud.Resource"; +const WING_CONSTRUCTOR_NAME: &'static str = "constructor"; + #[derive(Derivative)] #[derivative(Debug)] pub struct Namespace { @@ -55,6 +59,15 @@ impl Class { } } +#[derive(Derivative)] +#[derivative(Debug)] +pub struct Struct { + pub name: Symbol, + extends: Vec, // Must be a Type::Struct type + #[derivative(Debug = "ignore")] + pub env: TypeEnv, +} + impl PartialEq for Type { fn eq(&self, other: &Self) -> bool { // If references are the same this is the same type, if not then compare content @@ -72,13 +85,23 @@ impl PartialEq for Type { false } (Self::Resource(l0), Self::Resource(_)) => { - // If our parent is equal to `other` then treat both classes as equal (inheritance) + // If our parent is equal to `other` then treat both resources as equal (inheritance) if let Some(parent) = l0.parent { let parent_type: &Type = parent.into(); return parent_type.eq(other); } false } + (Self::Struct(l0), Self::Struct(_)) => { + // If we extend from `other` then treat both structs as equal (inheritance) + for parent in l0.extends.iter() { + let parent_type: &Type = (*parent).into(); + if parent_type.eq(other) { + return true; + } + } + false + } (Self::ClassInstance(l0), Self::ClassInstance(r0)) => { // Class instances are of the same type if they are instances of the same Class let l: &Type = (*l0).into(); @@ -91,6 +114,12 @@ impl PartialEq for Type { let r: &Type = (*r0).into(); l == r } + (Self::StructInstance(l0), Self::StructInstance(r0)) => { + // Struct instances are of the same type if they are instances of the same Struct + let l: &Type = (*l0).into(); + let r: &Type = (*r0).into(); + l == r + } // For all other types (built-ins) we compare the enum value _ => core::mem::discriminant(self) == core::mem::discriminant(other), } @@ -157,6 +186,11 @@ impl Display for Type { write!(f, "instance of {}", class_type_name) } Type::Namespace(namespace) => write!(f, "{}", namespace.name), + Type::Struct(s) => write!(f, "{}", s.name), + Type::StructInstance(s) => { + let struct_type = s.as_struct().expect("Struct instance must reference to a struct"); + write!(f, "instance of {}", struct_type.name.name) + } } } } @@ -214,6 +248,14 @@ impl TypeRef { } } + fn as_struct(&self) -> Option<&Struct> { + if let &Type::Struct(ref s) = (*self).into() { + Some(s) + } else { + None + } + } + pub fn as_function_sig(&self) -> Option<&FunctionSignature> { if let &Type::Function(ref sig) = (*self).into() { Some(sig) @@ -405,7 +447,7 @@ impl<'a> TypeChecker<'a> { // Type check args against constructor let constructor_type = class_env.lookup(&Symbol { - name: "constructor".into(), + name: WING_CONSTRUCTOR_NAME.into(), span: class_symbol.span.clone(), }); @@ -789,7 +831,7 @@ impl<'a> TypeChecker<'a> { let constructor_type = self.resolve_type(&AstType::FunctionSignature(constructor.signature.clone()), env); class_env.define( &Symbol { - name: "constructor".into(), + name: WING_CONSTRUCTOR_NAME.into(), span: name.span.clone(), }, constructor_type, @@ -848,6 +890,58 @@ impl<'a> TypeChecker<'a> { self.type_check_scope(&mut method.statements); } } + Statement::Struct { name, extends, members } => { + // 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 ignore skip it, if the types are different we + // fail type checking. + + // Create an environment for the struct + let mut struct_env = TypeEnv::new(None, None, true, env.flight); + + // Add members to the struct env + for member in members.iter() { + let member_type = self.resolve_type(&member.member_type, env); + struct_env.define(&member.name, member_type); + } + + // Add members from the structs parents + let mut extends_types = vec![]; + for parent in extends.iter() { + let parent_type = env.lookup(parent); + let parent_struct = parent_type + .as_struct() + .expect(format!("Type {} extends {} which should be a struct", name.name, parent.name).as_str()); + for (parent_member_name, member_type) in parent_struct.env.iter() { + if let Some(existing_type) = struct_env.try_lookup(&parent_member_name) { + // We compare types in both directions to make sure they are exactly the same type and not inheriting from each other + if existing_type.ne(&member_type) && member_type.ne(&existing_type) { + panic!( + "Struct {} extends {} but has a conflicting member {} ({} != {})", + name, parent.name, parent_member_name, existing_type, member_type + ); + } + } else { + struct_env.define( + &Symbol { + name: parent_member_name, + span: name.span.clone(), + }, + member_type, + ); + } + } + // Store references to all parent types + extends_types.push(parent_type); + } + env.define( + name, + self.types.add_type(Type::Struct(Struct { + name: name.clone(), + extends: extends_types, + env: struct_env, + })), + ) + } } } @@ -904,8 +998,6 @@ impl<'a> TypeChecker<'a> { jsii_types: &wingii::type_system::TypeSystem, env: &mut TypeEnv, ) -> Option { - // TODO: avoid infinite recursion by keeping a set of type names we already tried to define - // Hack: if the base class name is RESOURCE_CLASS_FQN then we treat this class as a resource and don't need to define it println!("Defining type {}", type_name); let fqn = format!("{}{}", prefix, type_name); @@ -959,6 +1051,13 @@ impl<'a> TypeChecker<'a> { } else { None }; + + // Verify we have a constructor for this calss + let jsii_initializer = jsii_class + .initializer + .as_ref() + .expect("JSII classes must have a constructor"); + println!("got here! we can define {}", type_name); // If our base class is a resource then we are a resource @@ -966,42 +1065,81 @@ impl<'a> TypeChecker<'a> { is_resource = base_class.as_resource().is_some(); } - // Create class's environment + // Create environment representing this class, for now it'll be empty just so we can support referencing ourselves from the class definition. + let dummy_env = TypeEnv::new(None, None, true, env.flight); + + let new_type_symbol = Self::jsii_name_to_symbol(type_name, &jsii_class.location_in_module); + + // Create the new resource/class type and add it to the current environment. + // When adding the class methods below we'll be able to reference this type. + println!("Adding type {} to namespace", type_name.green()); + let class_spec = Class { + name: new_type_symbol.clone(), + env: dummy_env, + parent: base_class, + }; + let new_type = self.types.add_type(if is_resource { + Type::Resource(class_spec) + } else { + Type::Class(class_spec) + }); + env.define(&new_type_symbol, new_type); + + // Create class's actually environment before we add properties and methods to it let mut class_env = TypeEnv::new(Some(env), None, true, env.flight); + // Add constructor to the class environment + let mut arg_types = vec![]; + if let Some(args) = &jsii_initializer.parameters { + for (i, arg) in args.iter().enumerate() { + // TODO: handle arg.variadic and arg.optional + + // If this is a resource then skip scope and id arguments + if is_resource { + if i == 0 { + assert!(arg.name == "scope"); + continue; + } else if i == 1 { + assert!(arg.name == "id"); + continue; + } + } + arg_types.push(self.jsii_type_ref_to_wing_type(&arg.type_).unwrap()); + } + } + let method_sig = self.types.add_type(Type::Function(FunctionSignature { + args: arg_types, + return_type: Some(new_type), + flight: env.flight, + })); + class_env.define( + &Self::jsii_name_to_symbol(WING_CONSTRUCTOR_NAME, &jsii_initializer.location_in_module), + method_sig, + ); + println!("Created constructor for {}: {:?}", type_name, method_sig); + // Add methods and properties to the class environment - self.add_jsii_interface_members_to_class_env(&jsii_class, is_resource, Flight::Pre, &mut class_env); - let new_type_symbol = Self::jsii_name_to_symbol(type_name, &jsii_class.location_in_module); + self.add_jsii_interface_members_to_class_env(&jsii_class, is_resource, Flight::Pre, &mut class_env, new_type); - let new_type = if is_resource { + if is_resource { // Look for a client interface for this resource let client_interface = jsii_types.find_interface(&format!("{}I{}Client", prefix, type_name)); if let Some(client_interface) = client_interface { // Add client interface's methods to the class environment - self.add_jsii_interface_members_to_class_env(&client_interface, false, Flight::In, &mut class_env); + self.add_jsii_interface_members_to_class_env(&client_interface, false, Flight::In, &mut class_env, new_type); } else { - println!("Resource {} doesn't not seem to have a client", type_name.bold()); + println!("Resource {} doesn't not seem to have a client", type_name.green()); } + } - println!("{} is a resource", type_name); - Type::Resource(Class { - name: new_type_symbol.clone(), - env: class_env, - parent: base_class, - }) - } else { - println!("{} is a class", type_name); - Type::Class(Class { - name: new_type_symbol.clone(), - env: class_env, - parent: base_class, - }) + // Replace the dummy class environment with the real one before type checking the methods + match new_type.into() { + &mut Type::Class(ref mut class) | &mut Type::Resource(ref mut class) => { + class.env = class_env; + } + _ => panic!("Expected {} to be a class or resource ", type_name), }; - // Finally create a new type in the wing type system and define it in the module's namespace - println!("Finally adding type {}", type_name.bold()); - let new_type = self.types.add_type(new_type); - env.define(&new_type_symbol, new_type); Some(new_type) } @@ -1011,8 +1149,11 @@ impl<'a> TypeChecker<'a> { is_resource: bool, flight: Flight, class_env: &mut TypeEnv, + wing_type: TypeRef, ) { assert!(!is_resource || flight == Flight::Pre); + + // Add methods to the class environment if let Some(methods) = &jsii_class.methods() { for m in methods { // TODO: skip internal methods (for now we skip `capture` until we mark it as internal) @@ -1021,7 +1162,7 @@ impl<'a> TypeChecker<'a> { continue; } - println!("Adding method {} to class", m.name); + println!("Adding method {} to class", m.name.green()); let return_type = if let Some(jsii_return_type) = &m.returns { self.jsii_optional_type_to_wing_type(&jsii_return_type) @@ -1030,13 +1171,15 @@ impl<'a> TypeChecker<'a> { }; let mut arg_types = vec![]; + // Add my type as the first argument to all methods (this) + arg_types.push(wing_type); + // Define the rest of the arguments and create the method signature if let Some(args) = &m.parameters { for arg in args { // TODO: handle arg.variadic and arg.optional arg_types.push(self.jsii_type_ref_to_wing_type(&arg.type_).unwrap()); } } - let method_sig = self.types.add_type(Type::Function(FunctionSignature { args: arg_types, return_type, @@ -1048,7 +1191,7 @@ impl<'a> TypeChecker<'a> { // Add properties to the class environment if let Some(properties) = &jsii_class.properties() { for p in properties { - println!("Found property {} with type {:?}", p.name.bold(), p.type_); + println!("Found property {} with type {:?}", p.name.green(), p.type_); if flight == Flight::In { todo!("No support for inflight properties yet"); } From fd039f5d249e8e5aa5d3f5e9d8b1550d8ee88fd6 Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Thu, 29 Sep 2022 14:47:49 +0300 Subject: [PATCH 05/31] wip: Import JSII "datatype" interfaces as wing structs. --- libs/wingc/src/type_check.rs | 111 +++++++++++++++++++++++++---------- 1 file changed, 81 insertions(+), 30 deletions(-) diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index 8b776b1e8b3..7c458f827f5 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -256,6 +256,14 @@ impl TypeRef { } } + fn as_mut_struct(&self) -> Option<&mut Struct> { + if let &mut Type::Struct(ref mut s) = (*self).into() { + Some(s) + } else { + None + } + } + pub fn as_function_sig(&self) -> Option<&FunctionSignature> { if let &Type::Function(ref sig) = (*self).into() { Some(sig) @@ -702,7 +710,7 @@ impl<'a> TypeChecker<'a> { // TODO Hack: treat "cloud" as "cloud in wingsdk" until I figure out the path issue if module_name.name == "cloud" { let mut wingii_types = wingii::type_system::TypeSystem::new(); - let name = wingii_types.load("../../node_modules/@monadahq/wingsdk").unwrap(); + let name = wingii_types.load("../wingsdk").unwrap(); let prefix = format!("{}.{}.", name, module_name.name); println!("Loaded JSII assembly {}", name); let assembly = wingii_types.find_assembly(&name).unwrap(); @@ -721,7 +729,7 @@ impl<'a> TypeChecker<'a> { println!("Type {} already defined, skipping", type_name); continue; } - self.import_jsii_type(type_name, &prefix, assembly, &wingii_types, &mut namespace_env); + self.import_jsii_type(type_name, &prefix, &wingii_types, &mut namespace_env); } // Create a namespace for the imported module @@ -892,7 +900,7 @@ impl<'a> TypeChecker<'a> { } Statement::Struct { name, extends, members } => { // 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 ignore skip it, if the types are different we + // 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 an environment for the struct @@ -914,6 +922,8 @@ impl<'a> TypeChecker<'a> { for (parent_member_name, member_type) in parent_struct.env.iter() { if let Some(existing_type) = struct_env.try_lookup(&parent_member_name) { // We compare types in both directions to make sure they are exactly the same type and not inheriting from each other + // TODO: does this make sense? We should add an `is_a()` methdod to `Type` to check if a type is a subtype and use that + // when we want to check for subtypes and use equality for strict comparisons. if existing_type.ne(&member_type) && member_type.ne(&existing_type) { panic!( "Struct {} extends {} but has a conflicting member {} ({} != {})", @@ -994,11 +1004,10 @@ impl<'a> TypeChecker<'a> { &mut self, type_name: &str, prefix: &str, - jsii_assembly: &jsii::Assembly, jsii_types: &wingii::type_system::TypeSystem, env: &mut TypeEnv, ) -> Option { - // Hack: if the base class name is RESOURCE_CLASS_FQN then we treat this class as a resource and don't need to define it + // Hack: if the class name is RESOURCE_CLASS_FQN then we treat this class as a resource and don't need to define it println!("Defining type {}", type_name); let fqn = format!("{}{}", prefix, type_name); if fqn == RESOURCE_CLASS_FQN { @@ -1006,17 +1015,31 @@ impl<'a> TypeChecker<'a> { return None; } - let jsii_class = jsii_types.find_class(&fqn); - if jsii_class.is_none() { - println!( - "Type {} is not a class in the JSII assembly {}", - fqn, jsii_assembly.name - ); - return None; + // Check if this is a JSII interface and import it if it is + let jsii_interface = jsii_types.find_interface(&fqn); + if let Some(jsii_interface) = jsii_interface { + return self.import_jsii_interface(jsii_interface, type_name, env); + } else { + // Check if this is a JSII class and import it if it is + let jsii_class = jsii_types.find_class(&fqn); + if let Some(jsii_class) = jsii_class { + return self.import_jsii_class(jsii_class, prefix, env, jsii_types, type_name); + } else { + println!("Type {} is unsupported, skipping", type_name); + return None; + } } - let jsii_class = jsii_class.unwrap(); - let mut is_resource = false; + } + fn import_jsii_class( + &mut self, + jsii_class: wingii::jsii::ClassType, + prefix: &str, + env: &mut TypeEnv, + jsii_types: &wingii::type_system::TypeSystem, + type_name: &str, + ) -> Option { + let mut is_resource = false; // Get the base class of the JSII class let base_class = if let Some(base_class_name) = &jsii_class.base { // Hack: if the base class name is RESOURCE_CLASS_FQN then we treat this class as a resource and don't need to define its parent @@ -1031,7 +1054,7 @@ impl<'a> TypeChecker<'a> { Some(base_class_type) } else { // If the base class isn't defined yet then define it first (recursive call) - let base_class_type = self.import_jsii_type(&base_class_name, prefix, jsii_assembly, jsii_types, env); + let base_class_type = self.import_jsii_type(&base_class_name, prefix, jsii_types, env); if base_class_type.is_none() /* && base_class_name != RESOURCE_CLASS_FQN*/ { @@ -1051,25 +1074,19 @@ impl<'a> TypeChecker<'a> { } else { None }; - // Verify we have a constructor for this calss let jsii_initializer = jsii_class .initializer .as_ref() .expect("JSII classes must have a constructor"); - println!("got here! we can define {}", type_name); - // If our base class is a resource then we are a resource if let Some(base_class) = base_class { is_resource = base_class.as_resource().is_some(); } - // Create environment representing this class, for now it'll be empty just so we can support referencing ourselves from the class definition. let dummy_env = TypeEnv::new(None, None, true, env.flight); - let new_type_symbol = Self::jsii_name_to_symbol(type_name, &jsii_class.location_in_module); - // Create the new resource/class type and add it to the current environment. // When adding the class methods below we'll be able to reference this type. println!("Adding type {} to namespace", type_name.green()); @@ -1084,10 +1101,8 @@ impl<'a> TypeChecker<'a> { Type::Class(class_spec) }); env.define(&new_type_symbol, new_type); - // Create class's actually environment before we add properties and methods to it let mut class_env = TypeEnv::new(Some(env), None, true, env.flight); - // Add constructor to the class environment let mut arg_types = vec![]; if let Some(args) = &jsii_initializer.parameters { @@ -1117,10 +1132,8 @@ impl<'a> TypeChecker<'a> { method_sig, ); println!("Created constructor for {}: {:?}", type_name, method_sig); - // Add methods and properties to the class environment self.add_jsii_interface_members_to_class_env(&jsii_class, is_resource, Flight::Pre, &mut class_env, new_type); - if is_resource { // Look for a client interface for this resource let client_interface = jsii_types.find_interface(&format!("{}I{}Client", prefix, type_name)); @@ -1131,7 +1144,6 @@ impl<'a> TypeChecker<'a> { println!("Resource {} doesn't not seem to have a client", type_name.green()); } } - // Replace the dummy class environment with the real one before type checking the methods match new_type.into() { &mut Type::Class(ref mut class) | &mut Type::Resource(ref mut class) => { @@ -1139,13 +1151,52 @@ impl<'a> TypeChecker<'a> { } _ => panic!("Expected {} to be a class or resource ", type_name), }; - Some(new_type) } + fn import_jsii_interface( + &mut self, + jsii_interface: wingii::jsii::InterfaceType, + type_name: &str, + env: &mut TypeEnv, + ) -> Option { + match jsii_interface.datatype { + Some(true) => { + // If this datatype has methods something is unexpected in this JSII type definition, skip it. + if jsii_interface.methods.is_some() && !jsii_interface.methods.as_ref().unwrap().is_empty() { + println!("JSII datatype interface {} has methods, skipping", type_name); + return None; + } + } + _ => { + println!("The JSII interface {} is not a \"datatype\", skipping", type_name); + return None; + } + } + if jsii_interface.interfaces.is_some() && !jsii_interface.interfaces.as_ref().unwrap().is_empty() { + println!( + "JSII datatype interface {} extends other interfaces, skipping", + type_name + ); + return None; + } + let struct_env = TypeEnv::new(None, None, true, env.flight); + let new_type_symbol = Self::jsii_name_to_symbol(type_name, &jsii_interface.location_in_module); + let wing_type = self.types.add_type(Type::Struct(Struct { + name: new_type_symbol.clone(), + extends: vec![], + env: struct_env, + })); + let struct_env = &mut wing_type.as_mut_struct().unwrap().env; + self.add_jsii_interface_members_to_class_env(&jsii_interface, false, env.flight, struct_env, wing_type); + println!("Adding struct type {}", type_name.green()); + env.define(&new_type_symbol, wing_type); + Some(wing_type) + } + fn add_jsii_interface_members_to_class_env( &mut self, - jsii_class: &T, + jsii_interface: &T, is_resource: bool, flight: Flight, class_env: &mut TypeEnv, @@ -1154,7 +1205,7 @@ impl<'a> TypeChecker<'a> { assert!(!is_resource || flight == Flight::Pre); // Add methods to the class environment - if let Some(methods) = &jsii_class.methods() { + if let Some(methods) = &jsii_interface.methods() { for m in methods { // TODO: skip internal methods (for now we skip `capture` until we mark it as internal) if is_resource && m.name == "capture" { @@ -1189,7 +1240,7 @@ impl<'a> TypeChecker<'a> { } } // Add properties to the class environment - if let Some(properties) = &jsii_class.properties() { + if let Some(properties) = &jsii_interface.properties() { for p in properties { println!("Found property {} with type {:?}", p.name.green(), p.type_); if flight == Flight::In { From e614ca0fa75aa410586020de35691235c9cb821d Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Tue, 4 Oct 2022 18:11:36 +0300 Subject: [PATCH 06/31] Handle non primitive type references in JSII imports: * refactor: moved jsii importing logic into submodule under a new type_check module. * resolve namespaced custom types in type checker. * corretly handle parent resource in resource type checking. * JSII imported handles references to other JSII types (not only primitives). --- libs/wingc/src/ast.rs | 2 +- libs/wingc/src/capture.rs | 2 +- libs/wingc/src/lib.rs | 3 +- libs/wingc/src/type_check.rs | 408 ++------------------ libs/wingc/src/type_check/jsii_importer.rs | 392 +++++++++++++++++++ libs/wingc/src/{ => type_check}/type_env.rs | 19 + 6 files changed, 452 insertions(+), 374 deletions(-) create mode 100644 libs/wingc/src/type_check/jsii_importer.rs rename libs/wingc/src/{ => type_check}/type_env.rs (85%) diff --git a/libs/wingc/src/ast.rs b/libs/wingc/src/ast.rs index d58735b1ff9..e6290fc2760 100644 --- a/libs/wingc/src/ast.rs +++ b/libs/wingc/src/ast.rs @@ -7,8 +7,8 @@ use derivative::Derivative; use crate::capture::Captures; use crate::diagnostic::WingSpan; +use crate::type_check::type_env::TypeEnv; use crate::type_check::TypeRef; -use crate::type_env::TypeEnv; #[derive(Debug, Eq, Clone)] pub struct Symbol { diff --git a/libs/wingc/src/capture.rs b/libs/wingc/src/capture.rs index 32ee6c4cba1..b9d66609225 100644 --- a/libs/wingc/src/capture.rs +++ b/libs/wingc/src/capture.rs @@ -2,8 +2,8 @@ use std::collections::HashMap; use crate::{ ast::{ArgList, Expr, ExprType, Flight, Reference, Scope, Statement, Symbol}, + type_check::type_env::TypeEnv, type_check::Type, - type_env::TypeEnv, }; /* This is a definition of how a resource is captured. The most basic way to capture a resource diff --git a/libs/wingc/src/lib.rs b/libs/wingc/src/lib.rs index 523ba8e1b2f..6613133b37d 100644 --- a/libs/wingc/src/lib.rs +++ b/libs/wingc/src/lib.rs @@ -8,8 +8,8 @@ use std::path::PathBuf; use crate::ast::Flight; use crate::capture::scan_captures; +use crate::type_check::type_env::TypeEnv; use crate::type_check::{TypeChecker, Types}; -use crate::type_env::TypeEnv; pub mod ast; pub mod capture; @@ -17,7 +17,6 @@ pub mod diagnostic; pub mod jsify; pub mod parser; pub mod type_check; -pub mod type_env; pub fn parse(source_file: &str) -> Scope { let language = tree_sitter_wing::language(); diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index 7c458f827f5..ebc21bed058 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -1,13 +1,11 @@ -use std::fmt::{Debug, Display}; - -use colored::Colorize; -use derivative::Derivative; -use serde_json::Value; -use wingii::jsii; +mod jsii_importer; +pub mod type_env; use crate::ast::{Type as AstType, *}; -use crate::diagnostic::{CharacterLocation, WingSpan}; -use crate::type_env::TypeEnv; +use derivative::Derivative; +use jsii_importer::JsiiImporter; +use std::fmt::{Debug, Display}; +use type_env::TypeEnv; #[derive(Debug)] pub enum Type { @@ -26,7 +24,6 @@ pub enum Type { Namespace(Namespace), } -const RESOURCE_CLASS_FQN: &'static str = "@monadahq/wingsdk.cloud.Resource"; const WING_CONSTRUCTOR_NAME: &'static str = "constructor"; #[derive(Derivative)] @@ -612,13 +609,10 @@ impl<'a> TypeChecker<'a> { self.types.add_type(Type::Function(sig)) } AstType::CustomType { root, fields } => { - if fields.is_empty() { - // TODO Hack For classes in the current env - env.lookup(root) - } else { - // TODO This should be updated to support "bring" - self.types.anything() - } + // Resolve all types down the fields list and return the last one (which is likely to be a real type and not a namespace) + let mut nested_name = vec![root.name.as_str()]; + nested_name.extend(fields.iter().map(|f| f.name.as_str())); + env.lookup_nested(&nested_name) } } } @@ -705,7 +699,7 @@ impl<'a> TypeChecker<'a> { } => { _ = { // Create a new env for the imported module's namespace - let mut namespace_env = TypeEnv::new(Some(env), None, false, env.flight); + let mut namespace_env = TypeEnv::new(None, None, false, env.flight); // TODO Hack: treat "cloud" as "cloud in wingsdk" until I figure out the path issue if module_name.name == "cloud" { @@ -714,22 +708,30 @@ impl<'a> TypeChecker<'a> { let prefix = format!("{}.{}.", name, module_name.name); println!("Loaded JSII assembly {}", name); let assembly = wingii_types.find_assembly(&name).unwrap(); - for type_name in assembly.types.as_ref().unwrap().keys() { + + let mut jsii_importer = JsiiImporter { + jsii_types: &wingii_types, + assembly_name: name, + namespace_env: &mut namespace_env, + namespace_name: module_name.name.clone(), + wing_types: self.types, + }; + + for type_fqn in assembly.types.as_ref().unwrap().keys() { // Skip types outside the imported namespace - if !type_name.starts_with(&prefix) { - println!("Skipping {} (outside of module {})", type_name, module_name.name); + if !type_fqn.starts_with(&prefix) { + println!("Skipping {} (outside of module {})", type_fqn, module_name.name); continue; } - let type_name = type_name.strip_prefix(&prefix).unwrap(); // Lookup type before we attempt to import it, this is required because `import_jsii_type` is recursive // and might have already defined the current type internally - println!("Going to import {}", type_name); - if namespace_env.try_lookup(type_name).is_some() { + let type_name = jsii_importer.fqn_to_type_name(type_fqn); + if jsii_importer.namespace_env.try_lookup(&type_name).is_some() { println!("Type {} already defined, skipping", type_name); continue; } - self.import_jsii_type(type_name, &prefix, &wingii_types, &mut namespace_env); + jsii_importer.import_type(type_fqn); } // Create a namespace for the imported module @@ -776,13 +778,21 @@ impl<'a> TypeChecker<'a> { let env_flight = if *is_resource { Flight::Pre } else { Flight::In }; - // Verify parent is actually a known Class + // Verify parent is actually a known Class/Resource and get their env let (parent_class, parent_class_env) = if let Some(parent_symbol) = parent { let t = env.lookup(parent_symbol); - if let &Type::Class(ref class) = t.into() { - (Some(t), Some(&class.env as *const TypeEnv)) + if *is_resource { + if let &Type::Resource(ref class) = t.into() { + (Some(t), Some(&class.env as *const TypeEnv)) + } else { + panic!("Resource {}'s parent {} is not a resource", name, parent_symbol); + } } else { - panic!("Class {}'s parent {} is not a class", name, parent_symbol); + if let &Type::Class(ref class) = t.into() { + (Some(t), Some(&class.env as *const TypeEnv)) + } else { + panic!("Class {}'s parent {} is not a class", name, parent_symbol); + } } } else { (None, None) @@ -999,346 +1009,4 @@ impl<'a> TypeChecker<'a> { } } } - - fn import_jsii_type( - &mut self, - type_name: &str, - prefix: &str, - jsii_types: &wingii::type_system::TypeSystem, - env: &mut TypeEnv, - ) -> Option { - // Hack: if the class name is RESOURCE_CLASS_FQN then we treat this class as a resource and don't need to define it - println!("Defining type {}", type_name); - let fqn = format!("{}{}", prefix, type_name); - if fqn == RESOURCE_CLASS_FQN { - println!("Hack: no need to define {}", type_name); - return None; - } - - // Check if this is a JSII interface and import it if it is - let jsii_interface = jsii_types.find_interface(&fqn); - if let Some(jsii_interface) = jsii_interface { - return self.import_jsii_interface(jsii_interface, type_name, env); - } else { - // Check if this is a JSII class and import it if it is - let jsii_class = jsii_types.find_class(&fqn); - if let Some(jsii_class) = jsii_class { - return self.import_jsii_class(jsii_class, prefix, env, jsii_types, type_name); - } else { - println!("Type {} is unsupported, skipping", type_name); - return None; - } - } - } - - fn import_jsii_class( - &mut self, - jsii_class: wingii::jsii::ClassType, - prefix: &str, - env: &mut TypeEnv, - jsii_types: &wingii::type_system::TypeSystem, - type_name: &str, - ) -> Option { - let mut is_resource = false; - // Get the base class of the JSII class - let base_class = if let Some(base_class_name) = &jsii_class.base { - // Hack: if the base class name is RESOURCE_CLASS_FQN then we treat this class as a resource and don't need to define its parent - if base_class_name == RESOURCE_CLASS_FQN { - println!("Hack: no need to define base class {}", base_class_name); - is_resource = true; - None - } else { - let base_class_name = base_class_name.strip_prefix(prefix).unwrap(); - println!("going to lookup base class {}", base_class_name); - let base_class_type = if let Some(base_class_type) = env.try_lookup(&base_class_name) { - Some(base_class_type) - } else { - // If the base class isn't defined yet then define it first (recursive call) - let base_class_type = self.import_jsii_type(&base_class_name, prefix, jsii_types, env); - if base_class_type.is_none() - /* && base_class_name != RESOURCE_CLASS_FQN*/ - { - panic!("Failed to define base class {}", base_class_name); - } - base_class_type - }; - - // Validate the base class is either a class or a resource - if let Some(base_class_type) = base_class_type { - if base_class_type.as_resource().is_none() && base_class_type.as_class().is_none() { - panic!("Base class {} of {} is not a resource", base_class_name, type_name); - } - } - base_class_type - } - } else { - None - }; - // Verify we have a constructor for this calss - let jsii_initializer = jsii_class - .initializer - .as_ref() - .expect("JSII classes must have a constructor"); - println!("got here! we can define {}", type_name); - // If our base class is a resource then we are a resource - if let Some(base_class) = base_class { - is_resource = base_class.as_resource().is_some(); - } - // Create environment representing this class, for now it'll be empty just so we can support referencing ourselves from the class definition. - let dummy_env = TypeEnv::new(None, None, true, env.flight); - let new_type_symbol = Self::jsii_name_to_symbol(type_name, &jsii_class.location_in_module); - // Create the new resource/class type and add it to the current environment. - // When adding the class methods below we'll be able to reference this type. - println!("Adding type {} to namespace", type_name.green()); - let class_spec = Class { - name: new_type_symbol.clone(), - env: dummy_env, - parent: base_class, - }; - let new_type = self.types.add_type(if is_resource { - Type::Resource(class_spec) - } else { - Type::Class(class_spec) - }); - env.define(&new_type_symbol, new_type); - // Create class's actually environment before we add properties and methods to it - let mut class_env = TypeEnv::new(Some(env), None, true, env.flight); - // Add constructor to the class environment - let mut arg_types = vec![]; - if let Some(args) = &jsii_initializer.parameters { - for (i, arg) in args.iter().enumerate() { - // TODO: handle arg.variadic and arg.optional - - // If this is a resource then skip scope and id arguments - if is_resource { - if i == 0 { - assert!(arg.name == "scope"); - continue; - } else if i == 1 { - assert!(arg.name == "id"); - continue; - } - } - arg_types.push(self.jsii_type_ref_to_wing_type(&arg.type_).unwrap()); - } - } - let method_sig = self.types.add_type(Type::Function(FunctionSignature { - args: arg_types, - return_type: Some(new_type), - flight: env.flight, - })); - class_env.define( - &Self::jsii_name_to_symbol(WING_CONSTRUCTOR_NAME, &jsii_initializer.location_in_module), - method_sig, - ); - println!("Created constructor for {}: {:?}", type_name, method_sig); - // Add methods and properties to the class environment - self.add_jsii_interface_members_to_class_env(&jsii_class, is_resource, Flight::Pre, &mut class_env, new_type); - if is_resource { - // Look for a client interface for this resource - let client_interface = jsii_types.find_interface(&format!("{}I{}Client", prefix, type_name)); - if let Some(client_interface) = client_interface { - // Add client interface's methods to the class environment - self.add_jsii_interface_members_to_class_env(&client_interface, false, Flight::In, &mut class_env, new_type); - } else { - println!("Resource {} doesn't not seem to have a client", type_name.green()); - } - } - // Replace the dummy class environment with the real one before type checking the methods - match new_type.into() { - &mut Type::Class(ref mut class) | &mut Type::Resource(ref mut class) => { - class.env = class_env; - } - _ => panic!("Expected {} to be a class or resource ", type_name), - }; - Some(new_type) - } - - fn import_jsii_interface( - &mut self, - jsii_interface: wingii::jsii::InterfaceType, - type_name: &str, - env: &mut TypeEnv, - ) -> Option { - match jsii_interface.datatype { - Some(true) => { - // If this datatype has methods something is unexpected in this JSII type definition, skip it. - if jsii_interface.methods.is_some() && !jsii_interface.methods.as_ref().unwrap().is_empty() { - println!("JSII datatype interface {} has methods, skipping", type_name); - return None; - } - } - _ => { - println!("The JSII interface {} is not a \"datatype\", skipping", type_name); - return None; - } - } - if jsii_interface.interfaces.is_some() && !jsii_interface.interfaces.as_ref().unwrap().is_empty() { - println!( - "JSII datatype interface {} extends other interfaces, skipping", - type_name - ); - return None; - } - let struct_env = TypeEnv::new(None, None, true, env.flight); - let new_type_symbol = Self::jsii_name_to_symbol(type_name, &jsii_interface.location_in_module); - let wing_type = self.types.add_type(Type::Struct(Struct { - name: new_type_symbol.clone(), - extends: vec![], - env: struct_env, - })); - let struct_env = &mut wing_type.as_mut_struct().unwrap().env; - self.add_jsii_interface_members_to_class_env(&jsii_interface, false, env.flight, struct_env, wing_type); - println!("Adding struct type {}", type_name.green()); - env.define(&new_type_symbol, wing_type); - Some(wing_type) - } - - fn add_jsii_interface_members_to_class_env( - &mut self, - jsii_interface: &T, - is_resource: bool, - flight: Flight, - class_env: &mut TypeEnv, - wing_type: TypeRef, - ) { - assert!(!is_resource || flight == Flight::Pre); - - // Add methods to the class environment - if let Some(methods) = &jsii_interface.methods() { - for m in methods { - // TODO: skip internal methods (for now we skip `capture` until we mark it as internal) - if is_resource && m.name == "capture" { - println!("Skipping capture method on resource"); - continue; - } - - println!("Adding method {} to class", m.name.green()); - - let return_type = if let Some(jsii_return_type) = &m.returns { - self.jsii_optional_type_to_wing_type(&jsii_return_type) - } else { - None - }; - - let mut arg_types = vec![]; - // Add my type as the first argument to all methods (this) - arg_types.push(wing_type); - // Define the rest of the arguments and create the method signature - if let Some(args) = &m.parameters { - for arg in args { - // TODO: handle arg.variadic and arg.optional - arg_types.push(self.jsii_type_ref_to_wing_type(&arg.type_).unwrap()); - } - } - let method_sig = self.types.add_type(Type::Function(FunctionSignature { - args: arg_types, - return_type, - flight, - })); - class_env.define(&Self::jsii_name_to_symbol(&m.name, &m.location_in_module), method_sig) - } - } - // Add properties to the class environment - if let Some(properties) = &jsii_interface.properties() { - for p in properties { - println!("Found property {} with type {:?}", p.name.green(), p.type_); - if flight == Flight::In { - todo!("No support for inflight properties yet"); - } - class_env.define( - &Self::jsii_name_to_symbol(&p.name, &p.location_in_module), - self.jsii_type_ref_to_wing_type(&p.type_).unwrap(), - ); - } - } - } - - fn jsii_optional_type_to_wing_type(&self, jsii_optional_type: &jsii::OptionalValue) -> Option { - if let Some(true) = jsii_optional_type.optional { - // TODO: we assume Some(false) and None are both non-optional - verify!! - panic!("TODO: handle optional types"); - } - self.jsii_type_ref_to_wing_type(&jsii_optional_type.type_) - } - - fn jsii_type_ref_to_wing_type(&self, jsii_type_ref: &jsii::TypeReference) -> Option { - if let serde_json::Value::Object(obj) = jsii_type_ref { - if let Some(Value::String(primitive_name)) = obj.get("primitive") { - match primitive_name.as_str() { - "string" => Some(self.types.string()), - "number" => Some(self.types.number()), - "boolean" => Some(self.types.bool()), - "any" => Some(self.types.anything()), - _ => panic!("TODO: handle primitive type {}", primitive_name), - } - } else if let Some(Value::String(s)) = obj.get("fqn") { - if s == "@monadahq/wingsdk.cloud.Void" { - None - } else { - panic!("TODO: handle non-primitive type {:?}", jsii_type_ref); - } - } else if let Some(Value::Object(_)) = obj.get("collection") { - // TODO: handle JSII to Wing collection type conversion, for now return any - Some(self.types.anything()) - } else { - panic!("TODO: handle non-primitive type {:?}", jsii_type_ref); - } - } else { - panic!("Expected JSII type reference {:?} to be an object", jsii_type_ref); - } - } - - fn jsii_name_to_symbol(name: &str, jsii_source_location: &Option) -> Symbol { - let span = if let Some(jsii_source_location) = jsii_source_location { - WingSpan { - start: CharacterLocation { - row: jsii_source_location.line as usize, - column: 0, - }, - end: CharacterLocation { - row: jsii_source_location.line as usize, - column: 0, - }, - start_byte: 0, - end_byte: 0, - file_id: (&jsii_source_location.filename).into(), - } - } else { - WingSpan { - start: CharacterLocation { row: 0, column: 0 }, - end: CharacterLocation { row: 0, column: 0 }, - start_byte: 0, - end_byte: 0, - file_id: "".into(), - } - }; - Symbol { - name: name.to_string(), - span, - } - } -} - -trait JsiiInterface { - fn methods<'a>(&'a self) -> &'a Option>; - fn properties<'a>(&'a self) -> &'a Option>; -} - -impl JsiiInterface for jsii::ClassType { - fn methods(&self) -> &Option> { - &self.methods - } - fn properties(&self) -> &Option> { - &self.properties - } -} - -impl JsiiInterface for jsii::InterfaceType { - fn methods(&self) -> &Option> { - &self.methods - } - fn properties(&self) -> &Option> { - &self.properties - } } diff --git a/libs/wingc/src/type_check/jsii_importer.rs b/libs/wingc/src/type_check/jsii_importer.rs new file mode 100644 index 00000000000..e45876e8621 --- /dev/null +++ b/libs/wingc/src/type_check/jsii_importer.rs @@ -0,0 +1,392 @@ +use crate::{ + ast::{Flight, Symbol}, + diagnostic::{CharacterLocation, WingSpan}, + type_check::type_env::TypeEnv, + type_check::{Class, FunctionSignature, Struct, Type, TypeRef, Types, WING_CONSTRUCTOR_NAME}, +}; +use colored::Colorize; +use serde_json::Value; +use wingii::jsii; + +const RESOURCE_CLASS_FQN: &'static str = "@monadahq/wingsdk.cloud.Resource"; + +trait JsiiInterface { + fn methods<'a>(&'a self) -> &'a Option>; + fn properties<'a>(&'a self) -> &'a Option>; +} + +impl JsiiInterface for jsii::ClassType { + fn methods(&self) -> &Option> { + &self.methods + } + fn properties(&self) -> &Option> { + &self.properties + } +} + +impl JsiiInterface for jsii::InterfaceType { + fn methods(&self) -> &Option> { + &self.methods + } + fn properties(&self) -> &Option> { + &self.properties + } +} + +pub struct JsiiImporter<'a> { + pub jsii_types: &'a wingii::type_system::TypeSystem, + pub assembly_name: String, + pub namespace_env: &'a mut TypeEnv, + pub namespace_name: String, + pub wing_types: &'a mut Types, +} + +impl<'a> JsiiImporter<'a> { + fn type_ref_to_wing_type(&mut self, jsii_type_ref: &jsii::TypeReference) -> Option { + if let serde_json::Value::Object(obj) = jsii_type_ref { + if let Some(Value::String(primitive_name)) = obj.get("primitive") { + match primitive_name.as_str() { + "string" => Some(self.wing_types.string()), + "number" => Some(self.wing_types.number()), + "boolean" => Some(self.wing_types.bool()), + "any" => Some(self.wing_types.anything()), + _ => panic!("TODO: handle primitive type {}", primitive_name), + } + } else if let Some(Value::String(type_fqn)) = obj.get("fqn") { + if type_fqn == "@monadahq/wingsdk.cloud.Void" { + None + } else { + println!("Getting wing type for {}", type_fqn); + let type_name = &self.fqn_to_type_name(type_fqn); + // Check if this type is already define in this module's namespace + if let Some(t) = self.namespace_env.try_lookup(type_name) { + println!("Found type {} in wing env", type_name); + return Some(t); + } + + // Define new type and return it + Some(self.import_type(type_fqn).unwrap()) + } + } else if let Some(Value::Object(_)) = obj.get("collection") { + // TODO: handle JSII to Wing collection type conversion, for now return any + Some(self.wing_types.anything()) + } else { + panic!( + "Expected JSII type reference {:?} to be a collection, fqn or primitive", + jsii_type_ref + ); + } + } else { + panic!("Expected JSII type reference {:?} to be an object", jsii_type_ref); + } + } + + pub fn fqn_to_type_name(&self, fqn: &str) -> String { + let parts = fqn.split('.').collect::>(); + let assembly_name = parts[0]; + let namespace_name = parts[1]; + // Make sure this type is part of our assembly and in our namespace + if assembly_name != self.assembly_name || namespace_name != self.namespace_name { + panic!( + "Encountered JSII type {} which isn't part of imported JSII namespace {}.{}", + fqn, self.assembly_name, self.namespace_name + ); + } + fqn + .strip_prefix(format!("{}.{}.", self.assembly_name, self.namespace_name).as_str()) + .unwrap() + .to_string() + } + + pub fn import_type(&mut self, type_fqn: &str) -> Option { + // Hack: if the class name is RESOURCE_CLASS_FQN then we treat this class as a resource and don't need to define it + let type_name = self.fqn_to_type_name(type_fqn); + println!("Defining type {}", type_name); + if type_fqn == RESOURCE_CLASS_FQN { + println!("Hack: no need to define {}", type_name); + return None; + } + + // Check if this is a JSII interface and import it if it is + let jsii_interface = self.jsii_types.find_interface(&type_fqn); + if let Some(jsii_interface) = jsii_interface { + return self.import_interface(jsii_interface); + } else { + // Check if this is a JSII class and import it if it is + let jsii_class = self.jsii_types.find_class(&type_fqn); + if let Some(jsii_class) = jsii_class { + return self.import_class(jsii_class); + } else { + println!("Type {} is unsupported, skipping", type_fqn); + return None; + } + } + } + + fn import_interface(&mut self, jsii_interface: wingii::jsii::InterfaceType) -> Option { + let type_name = self.fqn_to_type_name(&jsii_interface.fqn); + match jsii_interface.datatype { + Some(true) => { + // If this datatype has methods something is unexpected in this JSII type definition, skip it. + if jsii_interface.methods.is_some() && !jsii_interface.methods.as_ref().unwrap().is_empty() { + println!("JSII datatype interface {} has methods, skipping", type_name); + return None; + } + } + _ => { + println!("The JSII interface {} is not a \"datatype\", skipping", type_name); + return None; + } + } + if jsii_interface.interfaces.is_some() && !jsii_interface.interfaces.as_ref().unwrap().is_empty() { + println!( + "JSII datatype interface {} extends other interfaces, skipping", + type_name + ); + return None; + } + let struct_env = TypeEnv::new(None, None, true, self.namespace_env.flight); + let new_type_symbol = Self::jsii_name_to_symbol(&type_name, &jsii_interface.location_in_module); + let wing_type = self.wing_types.add_type(Type::Struct(Struct { + name: new_type_symbol.clone(), + extends: vec![], + env: struct_env, + })); + let struct_env = &mut wing_type.as_mut_struct().unwrap().env; + self.add_members_to_class_env(&jsii_interface, false, self.namespace_env.flight, struct_env, wing_type); + println!("Adding struct type {}", type_name.green()); + self.namespace_env.define(&new_type_symbol, wing_type); + Some(wing_type) + } + + fn add_members_to_class_env( + &mut self, + jsii_interface: &T, + is_resource: bool, + flight: Flight, + class_env: &mut TypeEnv, + wing_type: TypeRef, + ) { + assert!(!is_resource || flight == Flight::Pre); + + // Add methods to the class environment + if let Some(methods) = &jsii_interface.methods() { + for m in methods { + // TODO: skip internal methods (for now we skip `capture` until we mark it as internal) + if is_resource && m.name == "capture" { + println!("Skipping capture method on resource"); + continue; + } + + println!("Adding method {} to class", m.name.green()); + + let return_type = if let Some(jsii_return_type) = &m.returns { + self.optional_type_to_wing_type(&jsii_return_type) + } else { + None + }; + + let mut arg_types = vec![]; + // Add my type as the first argument to all methods (this) + arg_types.push(wing_type); + // Define the rest of the arguments and create the method signature + if let Some(args) = &m.parameters { + for arg in args { + // TODO: handle arg.variadic and arg.optional + arg_types.push(self.type_ref_to_wing_type(&arg.type_).unwrap()); + } + } + let method_sig = self.wing_types.add_type(Type::Function(FunctionSignature { + args: arg_types, + return_type, + flight, + })); + class_env.define(&Self::jsii_name_to_symbol(&m.name, &m.location_in_module), method_sig) + } + } + // Add properties to the class environment + if let Some(properties) = &jsii_interface.properties() { + for p in properties { + println!("Found property {} with type {:?}", p.name.green(), p.type_); + if flight == Flight::In { + todo!("No support for inflight properties yet"); + } + class_env.define( + &Self::jsii_name_to_symbol(&p.name, &p.location_in_module), + self.type_ref_to_wing_type(&p.type_).unwrap(), + ); + } + } + } + + fn jsii_name_to_symbol(name: &str, jsii_source_location: &Option) -> Symbol { + let span = if let Some(jsii_source_location) = jsii_source_location { + WingSpan { + start: CharacterLocation { + row: jsii_source_location.line as usize, + column: 0, + }, + end: CharacterLocation { + row: jsii_source_location.line as usize, + column: 0, + }, + start_byte: 0, + end_byte: 0, + file_id: (&jsii_source_location.filename).into(), + } + } else { + WingSpan { + start: CharacterLocation { row: 0, column: 0 }, + end: CharacterLocation { row: 0, column: 0 }, + start_byte: 0, + end_byte: 0, + file_id: "".into(), + } + }; + Symbol { + name: name.to_string(), + span, + } + } + + fn import_class(&mut self, jsii_class: wingii::jsii::ClassType) -> Option { + let mut is_resource = false; + let type_name = &self.fqn_to_type_name(&jsii_class.fqn); + + // Get the base class of the JSII class + let base_class = if let Some(base_class_fqn) = &jsii_class.base { + // Hack: if the base class name is RESOURCE_CLASS_FQN then we treat this class as a resource and don't need to define its parent + if base_class_fqn == RESOURCE_CLASS_FQN { + println!("Hack: no need to define base class {}", base_class_fqn); + is_resource = true; + None + } else { + let base_class_name = self.fqn_to_type_name(base_class_fqn); + println!("going to lookup base class {}", base_class_name); + let base_class_type = if let Some(base_class_type) = self.namespace_env.try_lookup(&base_class_name) { + Some(base_class_type) + } else { + // If the base class isn't defined yet then define it first (recursive call) + let base_class_type = self.import_type(base_class_fqn); + if base_class_type.is_none() + /* && base_class_name != RESOURCE_CLASS_FQN*/ + { + panic!("Failed to define base class {}", base_class_name); + } + base_class_type + }; + + // Validate the base class is either a class or a resource + if let Some(base_class_type) = base_class_type { + if base_class_type.as_resource().is_none() && base_class_type.as_class().is_none() { + panic!("Base class {} of {} is not a resource", base_class_name, type_name); + } + } + base_class_type + } + } else { + None + }; + // Verify we have a constructor for this calss + let jsii_initializer = jsii_class + .initializer + .as_ref() + .expect("JSII classes must have a constructor"); + println!("got here! we can define {}", type_name); + + // Get env of base class/resource + let base_class_env = if let Some(base_class) = base_class { + match base_class.into() { + &Type::Class(ref c) => Some(&c.env as *const TypeEnv), + &Type::Resource(ref c) => { + // If our base class is a resource then we are a resource + is_resource = true; + Some(&c.env as *const TypeEnv) + } + _ => panic!("Base class {} of {} is not a class or resource", base_class, type_name), + } + } else { + None + }; + + // Create environment representing this class, for now it'll be empty just so we can support referencing ourselves from the class definition. + let dummy_env = TypeEnv::new(None, None, true, self.namespace_env.flight); + let new_type_symbol = Self::jsii_name_to_symbol(type_name, &jsii_class.location_in_module); + // Create the new resource/class type and add it to the current environment. + // When adding the class methods below we'll be able to reference this type. + println!("Adding type {} to namespace", type_name.green()); + let class_spec = Class { + name: new_type_symbol.clone(), + env: dummy_env, + parent: base_class, + }; + let new_type = self.wing_types.add_type(if is_resource { + Type::Resource(class_spec) + } else { + Type::Class(class_spec) + }); + self.namespace_env.define(&new_type_symbol, new_type); + // Create class's actually environment before we add properties and methods to it + let mut class_env = TypeEnv::new(base_class_env, None, true, self.namespace_env.flight); + // Add constructor to the class environment + let mut arg_types = vec![]; + if let Some(args) = &jsii_initializer.parameters { + for (i, arg) in args.iter().enumerate() { + // TODO: handle arg.variadic and arg.optional + + // If this is a resource then skip scope and id arguments + if is_resource { + if i == 0 { + assert!(arg.name == "scope"); + continue; + } else if i == 1 { + assert!(arg.name == "id"); + continue; + } + } + arg_types.push(self.type_ref_to_wing_type(&arg.type_).unwrap()); + } + } + let method_sig = self.wing_types.add_type(Type::Function(FunctionSignature { + args: arg_types, + return_type: Some(new_type), + flight: class_env.flight, + })); + class_env.define( + &Self::jsii_name_to_symbol(WING_CONSTRUCTOR_NAME, &jsii_initializer.location_in_module), + method_sig, + ); + println!("Created constructor for {}: {:?}", type_name, method_sig); + // Add methods and properties to the class environment + self.add_members_to_class_env(&jsii_class, is_resource, Flight::Pre, &mut class_env, new_type); + if is_resource { + // Look for a client interface for this resource + let client_interface = self.jsii_types.find_interface(&format!( + "{}.{}.I{}Client", + self.assembly_name, self.namespace_name, type_name + )); + if let Some(client_interface) = client_interface { + // Add client interface's methods to the class environment + self.add_members_to_class_env(&client_interface, false, Flight::In, &mut class_env, new_type); + } else { + println!("Resource {} doesn't not seem to have a client", type_name.green()); + } + } + // Replace the dummy class environment with the real one before type checking the methods + match new_type.into() { + &mut Type::Class(ref mut class) | &mut Type::Resource(ref mut class) => { + class.env = class_env; + } + _ => panic!("Expected {} to be a class or resource ", type_name), + }; + Some(new_type) + } + + fn optional_type_to_wing_type(&mut self, jsii_optional_type: &jsii::OptionalValue) -> Option { + if let Some(true) = jsii_optional_type.optional { + // TODO: we assume Some(false) and None are both non-optional - verify!! + panic!("TODO: handle optional types"); + } + self.type_ref_to_wing_type(&jsii_optional_type.type_) + } +} diff --git a/libs/wingc/src/type_env.rs b/libs/wingc/src/type_check/type_env.rs similarity index 85% rename from libs/wingc/src/type_env.rs rename to libs/wingc/src/type_check/type_env.rs index f683bf83c5a..defaf06be24 100644 --- a/libs/wingc/src/type_env.rs +++ b/libs/wingc/src/type_check/type_env.rs @@ -76,6 +76,25 @@ impl TypeEnv { .expect(&format!("Unknown symbol {} at {}", &symbol.name, &symbol.span)) } + pub fn lookup_nested(&self, nested_vec: &[&str]) -> TypeRef { + let mut it = nested_vec.iter(); + + let mut symb = *it.next().unwrap(); + let mut t = self.try_lookup(symb).expect(&format!("Unkonwn symbol {}", symb)); + + while let Some(next_symb) = it.next() { + let ns = t + .as_namespace() + .expect(&format!("Symbol {} should be a namespace", symb)); + t = ns + .env + .try_lookup(*next_symb) + .expect(&format!("Unkonwn symbol {}", *next_symb)); + symb = *next_symb; + } + t + } + pub fn iter(&self) -> TypeEnvIter { TypeEnvIter::new(self) } From 69166885b9d9f2ba74cf595b54f2dfea6c92d9c9 Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Tue, 4 Oct 2022 23:32:38 +0300 Subject: [PATCH 07/31] Treat wingsdk `cloud.Inflight` as `~(any):any` inflight function. * Also added support for multiple struct inheritance in when importing JSII props. * Support importing wingsdk `Duration` types. --- libs/wingc/src/type_check.rs | 63 +++++++++++----------- libs/wingc/src/type_check/jsii_importer.rs | 55 ++++++++++++------- 2 files changed, 70 insertions(+), 48 deletions(-) diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index ebc21bed058..b39bc758fd3 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -881,6 +881,8 @@ impl<'a> TypeChecker<'a> { // Check function scope self.type_check_scope(&mut constructor.statements); + // TODO: handle member/method overrides in our env based on whatever rules we define in our spec + // Type check methods for method in methods.iter_mut() { // Lookup the method in the class_env @@ -923,36 +925,8 @@ impl<'a> TypeChecker<'a> { } // Add members from the structs parents - let mut extends_types = vec![]; - for parent in extends.iter() { - let parent_type = env.lookup(parent); - let parent_struct = parent_type - .as_struct() - .expect(format!("Type {} extends {} which should be a struct", name.name, parent.name).as_str()); - for (parent_member_name, member_type) in parent_struct.env.iter() { - if let Some(existing_type) = struct_env.try_lookup(&parent_member_name) { - // We compare types in both directions to make sure they are exactly the same type and not inheriting from each other - // TODO: does this make sense? We should add an `is_a()` methdod to `Type` to check if a type is a subtype and use that - // when we want to check for subtypes and use equality for strict comparisons. - if existing_type.ne(&member_type) && member_type.ne(&existing_type) { - panic!( - "Struct {} extends {} but has a conflicting member {} ({} != {})", - name, parent.name, parent_member_name, existing_type, member_type - ); - } - } else { - struct_env.define( - &Symbol { - name: parent_member_name, - span: name.span.clone(), - }, - member_type, - ); - } - } - // Store references to all parent types - extends_types.push(parent_type); - } + let extends_types = extends.iter().map(|parent| env.lookup(&parent)).collect::>(); + add_parent_members_to_struct_env(&extends_types, name, &mut struct_env); env.define( name, self.types.add_type(Type::Struct(Struct { @@ -1010,3 +984,32 @@ impl<'a> TypeChecker<'a> { } } } + +fn add_parent_members_to_struct_env(extends_types: &Vec, name: &Symbol, struct_env: &mut TypeEnv) { + for parent_type in extends_types.iter() { + let parent_struct = parent_type + .as_struct() + .expect(format!("Type {} extends {} which should be a struct", name.name, parent_type).as_str()); + for (parent_member_name, member_type) in parent_struct.env.iter() { + if let Some(existing_type) = struct_env.try_lookup(&parent_member_name) { + // We compare types in both directions to make sure they are exactly the same type and not inheriting from each other + // TODO: does this make sense? We should add an `is_a()` methdod to `Type` to check if a type is a subtype and use that + // when we want to check for subtypes and use equality for strict comparisons. + if existing_type.ne(&member_type) && member_type.ne(&existing_type) { + panic!( + "Struct {} extends {} but has a conflicting member {} ({} != {})", + name, parent_type, parent_member_name, existing_type, member_type + ); + } + } else { + struct_env.define( + &Symbol { + name: parent_member_name, + span: name.span.clone(), + }, + member_type, + ); + } + } + } +} diff --git a/libs/wingc/src/type_check/jsii_importer.rs b/libs/wingc/src/type_check/jsii_importer.rs index e45876e8621..e0039b16bee 100644 --- a/libs/wingc/src/type_check/jsii_importer.rs +++ b/libs/wingc/src/type_check/jsii_importer.rs @@ -1,7 +1,7 @@ use crate::{ ast::{Flight, Symbol}, diagnostic::{CharacterLocation, WingSpan}, - type_check::type_env::TypeEnv, + type_check::{self, type_env::TypeEnv}, type_check::{Class, FunctionSignature, Struct, Type, TypeRef, Types, WING_CONSTRUCTOR_NAME}, }; use colored::Colorize; @@ -53,19 +53,20 @@ impl<'a> JsiiImporter<'a> { _ => panic!("TODO: handle primitive type {}", primitive_name), } } else if let Some(Value::String(type_fqn)) = obj.get("fqn") { + // TODO: we can probably get rid of this special handling for cloud.Void if type_fqn == "@monadahq/wingsdk.cloud.Void" { None + } else if type_fqn == "@monadahq/wingsdk.core.Inflight" { + Some(self.wing_types.add_type(Type::Function(FunctionSignature { + args: vec![self.wing_types.anything()], + return_type: Some(self.wing_types.anything()), + flight: Flight::In, + }))) + } else if type_fqn == "@monadahq/wingsdk.core.Duration" { + Some(self.wing_types.duration()) } else { println!("Getting wing type for {}", type_fqn); - let type_name = &self.fqn_to_type_name(type_fqn); - // Check if this type is already define in this module's namespace - if let Some(t) = self.namespace_env.try_lookup(type_name) { - println!("Found type {} in wing env", type_name); - return Some(t); - } - - // Define new type and return it - Some(self.import_type(type_fqn).unwrap()) + Some(self.lookup_or_create_type(type_fqn)) } } else if let Some(Value::Object(_)) = obj.get("collection") { // TODO: handle JSII to Wing collection type conversion, for now return any @@ -81,6 +82,17 @@ impl<'a> JsiiImporter<'a> { } } + fn lookup_or_create_type(&mut self, type_fqn: &String) -> TypeRef { + let type_name = &self.fqn_to_type_name(type_fqn); + // Check if this type is already define in this module's namespace + if let Some(t) = self.namespace_env.try_lookup(type_name) { + println!("Found type {} in wing env", type_name); + return t; + } + // Define new type and return it + self.import_type(type_fqn).unwrap() + } + pub fn fqn_to_type_name(&self, fqn: &str) -> String { let parts = fqn.split('.').collect::>(); let assembly_name = parts[0]; @@ -138,22 +150,29 @@ impl<'a> JsiiImporter<'a> { return None; } } - if jsii_interface.interfaces.is_some() && !jsii_interface.interfaces.as_ref().unwrap().is_empty() { - println!( - "JSII datatype interface {} extends other interfaces, skipping", - type_name - ); - return None; - } + + let extends = if let Some(interfaces) = &jsii_interface.interfaces { + interfaces + .iter() + .map(|fqn| self.lookup_or_create_type(fqn)) + .collect::>() + } else { + vec![] + }; + let struct_env = TypeEnv::new(None, None, true, self.namespace_env.flight); let new_type_symbol = Self::jsii_name_to_symbol(&type_name, &jsii_interface.location_in_module); let wing_type = self.wing_types.add_type(Type::Struct(Struct { name: new_type_symbol.clone(), - extends: vec![], + extends: extends.clone(), env: struct_env, })); let struct_env = &mut wing_type.as_mut_struct().unwrap().env; self.add_members_to_class_env(&jsii_interface, false, self.namespace_env.flight, struct_env, wing_type); + + // Add properties from our parents to the new structs env + type_check::add_parent_members_to_struct_env(&extends, &new_type_symbol, struct_env); + println!("Adding struct type {}", type_name.green()); self.namespace_env.define(&new_type_symbol, wing_type); Some(wing_type) From 7e23309d24c180e9e2160a25e17a2adff3be99c4 Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Thu, 6 Oct 2022 17:48:13 +0300 Subject: [PATCH 08/31] Struct literals (currently with a grammar hack `@TypeName`) also: Cleaned up method_call vs function_call in grammar/ast Fixed return type parsing --- libs/tree-sitter-wing/grammar.js | 39 ++++---- .../test/corpus/expressions.txt | 45 +++++++-- .../test/corpus/references.txt | 16 ++-- .../test/corpus/statements.txt | 2 +- libs/wingc/src/ast.rs | 12 +-- libs/wingc/src/capture.rs | 8 +- libs/wingc/src/jsify.rs | 19 ++-- libs/wingc/src/parser.rs | 30 ++++-- libs/wingc/src/type_check.rs | 93 ++++++++++--------- 9 files changed, 163 insertions(+), 101 deletions(-) diff --git a/libs/tree-sitter-wing/grammar.js b/libs/tree-sitter-wing/grammar.js index 79b7badc5c3..0dd10bdb940 100644 --- a/libs/tree-sitter-wing/grammar.js +++ b/libs/tree-sitter-wing/grammar.js @@ -21,11 +21,13 @@ module.exports = grammar({ word: ($) => $.identifier, precedences: ($) => [ - [$.builtin_type, $.identifier], - [$.custom_type, $.nested_identifier, $.reference], + // Handle ambiguity in case of empty literal: `a = {}` + // In this case tree-sitter doesn't know if it's a set or a map literal so just assume its a map [$.map_literal, $.set_literal], - [$.new_expression, $.function_call], - [$.nested_identifier, $.method_call, $.reference], + + // A type (`cloud.Bucket`) may look like a reference (`bucket.public`). This is relevant + // when we expect an expression and we specify a type for a struct literal. + //[$.reference, $.custom_type], ], supertypes: ($) => [$.expression, $._literal], @@ -41,15 +43,16 @@ module.exports = grammar({ // Identifiers reference: ($) => +// prec.right(choice($.identifier, $.nested_identifier)), choice($.identifier, $.nested_identifier), identifier: ($) => /([A-Za-z_$][A-Za-z_$0-9]*|[A-Z][A-Z0-9_]*)/, - custom_type: ($) => - prec.right(seq( + custom_type: ($) => + seq( field("object", $.identifier), repeat(seq(".", field("fields", $.identifier))) - )), + ), nested_identifier: ($) => seq( @@ -200,17 +203,18 @@ module.exports = grammar({ $.new_expression, $._literal, $.reference, - $.function_call, - $.method_call, + $.call, $.preflight_closure, $.inflight_closure, $.pure_closure, $.await_expression, $._collection_literal, $.parenthesized_expression, - $.structured_access_expression + $.structured_access_expression, + $.struct_literal, ), + // Primitives _literal: ($) => choice($.string, $.number, $.bool, $.duration), @@ -253,15 +257,9 @@ module.exports = grammar({ ) ), - function_call: ($) => + call: ($) => seq(field("call_name", $.reference), field("args", $.argument_list)), - method_call: ($) => - seq( - field("call_name", $.nested_identifier), - field("args", $.argument_list) - ), - argument_list: ($) => seq( "(", @@ -315,7 +313,7 @@ module.exports = grammar({ seq( optional(field("inflight", $._inflight_specifier)), field("parameter_types", $.parameter_type_list), - optional(seq("->", field("return_type", $._type))) + optional(seq(":", field("return_type", $._type))) ) ), @@ -347,6 +345,7 @@ module.exports = grammar({ optional("async"), field("name", $.identifier), field("parameter_list", $.parameter_list), + optional(field("return_type", $._type_annotation)), field("block", $.block) ), @@ -441,9 +440,13 @@ module.exports = grammar({ array_literal: ($) => seq("[", commaSep($.expression), "]"), set_literal: ($) => seq("{", commaSep($.expression), "}"), map_literal: ($) => seq("{", commaSep($.map_literal_member), "}"), + // TODO: remove the "@" hack, this was done just to get the grammar not to conflict with nested references + struct_literal: ($) => seq("@", field("type", $.custom_type), "{", field("fields", commaSep($.struct_literal_member)), "}"), map_literal_member: ($) => seq(choice($.identifier, $.string), ":", $.expression), + struct_literal_member: ($) => + seq($.identifier, ":", $.expression), structured_access_expression: ($) => prec.right(seq($.expression, "[", $.expression, "]")), }, diff --git a/libs/tree-sitter-wing/test/corpus/expressions.txt b/libs/tree-sitter-wing/test/corpus/expressions.txt index e0c2d37ae12..93583a4fe6f 100644 --- a/libs/tree-sitter-wing/test/corpus/expressions.txt +++ b/libs/tree-sitter-wing/test/corpus/expressions.txt @@ -73,7 +73,7 @@ hello(1,a); (source (expression_statement - (function_call + (call call_name: (reference (identifier)) args: (argument_list (positional_argument @@ -97,12 +97,14 @@ obj.method(); (source (expression_statement - (method_call - call_name: (nested_identifier - object: (reference (identifier)) - property: (identifier) - ) - args: (argument_list) + (call + call_name: (reference + (nested_identifier + object: (reference (identifier)) + property: (identifier) + ) + ) + args: (argument_list) ) ) ) @@ -347,4 +349,31 @@ let a = new Map(); (argument_list) ) ) -) \ No newline at end of file +) + +================================ +Struct literal +================================ + +let a = A { f1: 3, f2: true }; + +--- + +(source + (variable_definition_statement + name: (identifier) + value: (struct_literal + struct_type: (custom_type + object: (identifier) + ) + struct_fields: (struct_literal_member + (identifier) + (number) + ) + struct_fields: (struct_literal_member + (identifier) + (bool) + ) + ) + ) +) diff --git a/libs/tree-sitter-wing/test/corpus/references.txt b/libs/tree-sitter-wing/test/corpus/references.txt index 2bace72ee97..96d358ca4b1 100644 --- a/libs/tree-sitter-wing/test/corpus/references.txt +++ b/libs/tree-sitter-wing/test/corpus/references.txt @@ -32,15 +32,17 @@ test1.test2.test3(); (source (expression_statement - (method_call - call_name: (nested_identifier - object: (reference - (nested_identifier - object: (reference (identifier)) - property: (identifier) + (call + call_name: (reference + (nested_identifier + object: (reference + (nested_identifier + object: (reference (identifier)) + property: (identifier) + ) ) + property: (identifier) ) - property: (identifier) ) args: (argument_list) ) diff --git a/libs/tree-sitter-wing/test/corpus/statements.txt b/libs/tree-sitter-wing/test/corpus/statements.txt index 54b3cffc8d0..494a48dec93 100644 --- a/libs/tree-sitter-wing/test/corpus/statements.txt +++ b/libs/tree-sitter-wing/test/corpus/statements.txt @@ -94,7 +94,7 @@ inflight hi(a: num, b: str) {} Inflight Function with function parameter ================================== -~ f(callback: (num,num)->bool) {} +~ f(callback: (num,num):bool) {} --- (source diff --git a/libs/wingc/src/ast.rs b/libs/wingc/src/ast.rs index e6290fc2760..e396f558ca9 100644 --- a/libs/wingc/src/ast.rs +++ b/libs/wingc/src/ast.rs @@ -142,11 +142,10 @@ pub enum ExprType { }, Literal(Literal), Reference(Reference), - FunctionCall { + Call { function: Reference, args: ArgList, }, - MethodCall(MethodCall), Unary { // TODO: Split to LogicalUnary, NumericUnary op: UnaryOperator, @@ -158,6 +157,10 @@ pub enum ExprType { lexp: Box, rexp: Box, }, + StructLiteral { + type_: Type, + fields: HashMap, + }, } #[derive(Debug)] @@ -213,11 +216,6 @@ impl Scope { } } -#[derive(Debug)] -pub struct MethodCall { - pub method: Reference, - pub args: ArgList, -} #[derive(Debug)] pub enum UnaryOperator { Plus, diff --git a/libs/wingc/src/capture.rs b/libs/wingc/src/capture.rs index b9d66609225..dc305121014 100644 --- a/libs/wingc/src/capture.rs +++ b/libs/wingc/src/capture.rs @@ -179,14 +179,18 @@ fn scan_captures_in_expression(exp: &Expr, env: &TypeEnv) -> Vec { // } } }, - ExprType::FunctionCall { function, args } => res.extend(scan_captures_in_call(&function, &args, env)), - ExprType::MethodCall(mc) => res.extend(scan_captures_in_call(&mc.method, &mc.args, env)), + ExprType::Call { function, args } => res.extend(scan_captures_in_call(&function, &args, env)), ExprType::Unary { op: _, exp } => res.extend(scan_captures_in_expression(exp, env)), ExprType::Binary { op: _, lexp, rexp } => { scan_captures_in_expression(lexp, env); scan_captures_in_expression(rexp, env); } ExprType::Literal(_) => {} + ExprType::StructLiteral { fields, .. } => { + for (_, v) in fields.iter() { + res.extend(scan_captures_in_expression(&v, env)); + } + } } res } diff --git a/libs/wingc/src/jsify.rs b/libs/wingc/src/jsify.rs index 4c2cb987d6b..b8cd7f0d06b 100644 --- a/libs/wingc/src/jsify.rs +++ b/libs/wingc/src/jsify.rs @@ -189,16 +189,9 @@ fn jsify_expression(expression: &Expr) -> String { Literal::Boolean(b) => format!("{}", if *b { "true" } else { "false" }), }, ExprType::Reference(_ref) => jsify_reference(&_ref), - ExprType::FunctionCall { function, args } => { + ExprType::Call { function, args } => { format!("{}({})", jsify_reference(&function), jsify_arg_list(&args, None, None)) } - ExprType::MethodCall(method_call) => { - format!( - "{}({})", - jsify_reference(&method_call.method), - jsify_arg_list(&method_call.args, None, None) - ) - } ExprType::Unary { op, exp } => { let op = match op { UnaryOperator::Plus => "+", @@ -225,6 +218,16 @@ fn jsify_expression(expression: &Expr) -> String { }; format!("({} {} {})", jsify_expression(lexp), op, jsify_expression(rexp)) } + ExprType::StructLiteral { fields, .. } => { + format!( + "{{\n{}}}\n", + fields + .iter() + .map(|(name, expr)| format!("\"{}\": {},", name.name, jsify_expression(expr))) + .collect::>() + .join("\n") + ) + } } } diff --git a/libs/wingc/src/parser.rs b/libs/wingc/src/parser.rs index 37e1c3ee886..30aeaf8d8a4 100644 --- a/libs/wingc/src/parser.rs +++ b/libs/wingc/src/parser.rs @@ -5,7 +5,7 @@ use tree_sitter::Node; use crate::ast::{ ArgList, BinaryOperator, ClassMember, Constructor, Expr, ExprType, Flight, FunctionDefinition, FunctionSignature, - Literal, MethodCall, ParameterDefinition, Reference, Scope, Statement, Symbol, Type, UnaryOperator, + Literal, ParameterDefinition, Reference, Scope, Statement, Symbol, Type, UnaryOperator, }; use crate::diagnostic::{Diagnostic, DiagnosticLevel, DiagnosticResult, Diagnostics, WingSpan}; @@ -268,7 +268,7 @@ impl Parser<'_> { signature: FunctionSignature { parameters: parameters.iter().map(|p| p.parameter_type.clone()).collect(), return_type: func_def_node - .child_by_field_name("return_type") + .child_by_field_name("type") .map(|rt| Box::new(self.build_type(&rt).unwrap())), flight, }, @@ -458,18 +458,34 @@ impl Parser<'_> { "reference" => Ok(Expr::new(ExprType::Reference(self.build_reference(&expression_node)?))), "positional_argument" => self.build_expression(&expression_node.named_child(0).unwrap()), "keyword_argument_value" => self.build_expression(&expression_node.named_child(0).unwrap()), - "function_call" => Ok(Expr::new(ExprType::FunctionCall { + "call" => Ok(Expr::new(ExprType::Call { function: self.build_reference(&expression_node.child_by_field_name("call_name").unwrap())?, args: self.build_arg_list(&expression_node.child_by_field_name("args").unwrap())?, })), - "method_call" => Ok(Expr::new(ExprType::MethodCall(MethodCall { - method: self.build_nested_identifier(&expression_node.child_by_field_name("call_name").unwrap())?, - args: self.build_arg_list(&expression_node.child_by_field_name("args").unwrap())?, - }))), "parenthesized_expression" => self.build_expression(&expression_node.named_child(0).unwrap()), "preflight_closure" => self.add_error(format!("Anonymous closures not implemented yet"), expression_node), "inflight_closure" => self.add_error(format!("Anonymous closures not implemented yet"), expression_node), "pure_closure" => self.add_error(format!("Anonymous closures not implemented yet"), expression_node), + "map_literal" => self.add_error(format!("Map literals not implemented yet"), expression_node), + "struct_literal" => { + let type_ = self.build_type(&expression_node.child_by_field_name("type").unwrap()); + let mut fields = HashMap::new(); + let mut cursor = expression_node.walk(); + for field in expression_node.children_by_field_name("fields", &mut cursor) { + let field_name = self.node_symbol(&field.named_child(0).unwrap()); + let field_value = self.build_expression(&field.named_child(1).unwrap()); + // Add fields to our struct literal, if some are missing or aren't part of the type we'll fail on type checking + if let (Ok(k), Ok(v)) = (field_name, field_value) { + if fields.contains_key(&k) { + // TODO: ugly, we need to change add_error to not return anything and have a wrapper `raise_error` that returns a Result + _ = self.add_error::<()>(format!("Duplicate field {} in struct literal", k), expression_node); + } else { + fields.insert(k, v); + } + } + } + Ok(Expr::new(ExprType::StructLiteral { type_: type_?, fields })) + } other => { if expression_node.has_error() { self.add_error(format!("Expected expression"), expression_node) diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index b39bc758fd3..b44686fd26e 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -72,6 +72,10 @@ impl PartialEq for Type { return true; } match (self, other) { + (Self::Anything, _) | (_, Self::Anything) => { + // TODO: Hack to make anything's compatible with all other types, specifically useful for handling core.Inflight handlers + true + } (Self::Function(l0), Self::Function(r0)) => l0 == r0, (Self::Class(l0), Self::Class(_)) => { // If our parent is equal to `other` then treat both classes as equal (inheritance) @@ -148,7 +152,7 @@ impl Display for Type { if let Some(ret_val) = &func_sig.return_type { write!( f, - "fn({}) -> {}", + "fn({}):{}", func_sig .args .iter() @@ -514,63 +518,66 @@ impl<'a> TypeChecker<'a> { Some(self.types.add_type(Type::ClassInstance(type_))) // TODO: don't create new type if one already exists. } } - ExprType::FunctionCall { function, args } => { + ExprType::Call { function, args } => { + // Resolve the function's reference (either a method in the class's env or a function in the current env) let func_type = self.resolve_reference(function, env); - - if let &Type::Function(ref func_type) = func_type.into() { - // TODO: named args - // Argument arity check - if args.pos_args.len() != func_type.args.len() { - panic!( - "Expected {} arguments for function {:?}, but got {} instead.", - func_type.args.len(), - function, - args.pos_args.len() - ) - } - // Argument type check - for (passed_arg, expected_arg) in args.pos_args.iter().zip(func_type.args.iter()) { - let passed_arg_type = self.type_check_exp(passed_arg, env).unwrap(); - self.validate_type(passed_arg_type, *expected_arg, passed_arg); - } - - func_type.return_type + let extra_args = if matches!(function, Reference::NestedIdentifier { .. }) { + 1 } else { - panic!("Identifier {:?} is not a function", function) - } - } - ExprType::MethodCall(method_call) => { - // Find method in class's environment - let method_type = self.resolve_reference(&method_call.method, env); + 0 + }; // TODO: hack to support methods of stdlib object we don't know their types yet (basically stuff like cloud.Bucket().upload()) - if matches!(method_type.into(), &Type::Anything) { + if matches!(func_type.into(), &Type::Anything) { return Some(self.types.anything()); } - let method_sig = if let &Type::Function(ref sig) = method_type.into() { - sig - } else { - panic!("Identifier {:?} is not a method", method_call.method); - }; + // Make sure this is a function signature type + let func_sig = func_type + .as_function_sig() + .expect(&format!("{:?} should be a function or method", function)); - // Verify arity - // TODO named args - if method_sig.args.len() != method_call.args.pos_args.len() + 1 { + // TODO: named args + // Argument arity check + if args.pos_args.len() + extra_args != func_sig.args.len() { panic!( - "Expected {} arguments when calling {:?} but got {}", - method_sig.args.len() - 1, - method_call.method, - method_call.args.pos_args.len() - ); + "Expected {} arguments for {:?}, but got {} instead.", + func_sig.args.len() - extra_args, + function, + args.pos_args.len() + ) } // Verify argument types (we run from last to first to skip "this" argument) - for (arg_type, param_exp) in method_sig.args.iter().rev().zip(method_call.args.pos_args.iter().rev()) { + for (arg_type, param_exp) in func_sig.args.iter().rev().zip(args.pos_args.iter().rev()) { let param_type = self.type_check_exp(param_exp, env).unwrap(); self.validate_type(param_type, *arg_type, param_exp); } - method_sig.return_type + func_sig.return_type + } + ExprType::StructLiteral { type_, fields } => { + // Find this struct's type in the environment + let struct_type = self.resolve_type(type_, env); + + // Make it really is a a struct type + let st = struct_type + .as_struct() + .expect(&format!("Expected {} to be a struct type", struct_type)); + + // Verify that all fields are present and are of the right type + if st.env.iter().count() > fields.len() { + panic!("Not all fields of {} are initialized.", struct_type); + } + for (k, v) in fields.iter() { + let field_type = st + .env + .try_lookup(&k.name) + .expect(&format!("{} is not a field of {}", k.name, struct_type)); + let t = self.type_check_exp(v, env).unwrap(); + self.validate_type(t, field_type, v); + } + + Some(struct_type) } }; *exp.evaluated_type.borrow_mut() = t; From 2b33b9a18c488595a0f3d32fb03a9932c3c79fce Mon Sep 17 00:00:00 2001 From: Sepehr Laal <5657848+3p3r@users.noreply.github.com> Date: Thu, 6 Oct 2022 10:25:54 -0700 Subject: [PATCH 09/31] fix: removed Yoav's @ in struct literals --- libs/tree-sitter-wing/grammar.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/libs/tree-sitter-wing/grammar.js b/libs/tree-sitter-wing/grammar.js index 0dd10bdb940..03e508d0999 100644 --- a/libs/tree-sitter-wing/grammar.js +++ b/libs/tree-sitter-wing/grammar.js @@ -24,14 +24,14 @@ module.exports = grammar({ // Handle ambiguity in case of empty literal: `a = {}` // In this case tree-sitter doesn't know if it's a set or a map literal so just assume its a map [$.map_literal, $.set_literal], - - // A type (`cloud.Bucket`) may look like a reference (`bucket.public`). This is relevant - // when we expect an expression and we specify a type for a struct literal. - //[$.reference, $.custom_type], ], supertypes: ($) => [$.expression, $._literal], + conflicts: $ => [ + [$.reference, $.custom_type], + ], + rules: { // Basics source: ($) => repeat($._statement), @@ -440,8 +440,7 @@ module.exports = grammar({ array_literal: ($) => seq("[", commaSep($.expression), "]"), set_literal: ($) => seq("{", commaSep($.expression), "}"), map_literal: ($) => seq("{", commaSep($.map_literal_member), "}"), - // TODO: remove the "@" hack, this was done just to get the grammar not to conflict with nested references - struct_literal: ($) => seq("@", field("type", $.custom_type), "{", field("fields", commaSep($.struct_literal_member)), "}"), + struct_literal: ($) => seq(field("type", $.custom_type), "{", field("fields", commaSep($.struct_literal_member)), "}"), map_literal_member: ($) => seq(choice($.identifier, $.string), ":", $.expression), From 8f5396864438eb9a0d030888bab5e3a0cb7588b3 Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Sun, 9 Oct 2022 16:43:37 +0300 Subject: [PATCH 10/31] wingsdk cloud JSII import * Updated sdk_capture tests to use explicit Props ars used by sdk resources. * Support type annotation in container literals. * Added map literals support. * de-dup captured method names in capture mechanism. * removed auto capturing of everything looking like a mehtod call on an anything object (AKA wingsdk support hack). --- examples/simple/sdk_capture_test.w | 19 ++++-- libs/tree-sitter-wing/grammar.js | 49 ++++++++++----- .../test/corpus/expressions.txt | 33 ++++++++-- libs/wingc/src/ast.rs | 5 ++ libs/wingc/src/capture.rs | 30 ++++----- libs/wingc/src/jsify.rs | 10 +++ libs/wingc/src/parser.rs | 47 +++++++++++++- libs/wingc/src/type_check.rs | 61 ++++++++++++++++--- libs/wingc/src/type_check/jsii_importer.rs | 14 +---- 9 files changed, 205 insertions(+), 63 deletions(-) diff --git a/examples/simple/sdk_capture_test.w b/examples/simple/sdk_capture_test.w index faf0dc3711e..ac40ca9bf50 100644 --- a/examples/simple/sdk_capture_test.w +++ b/examples/simple/sdk_capture_test.w @@ -1,10 +1,19 @@ bring cloud; -let bucket = new cloud.Bucket(); +let p = cloud.BucketProps { + public: false +}; -inflight handler() { - bucket.upload("file.txt", "data"); - bucket.delete("file.txt"); +let bucket = new cloud.Bucket(p); + +inflight handler(event: str):str { + bucket.put("file.txt", "data"); + bucket.get("file.txt"); } -new cloud.Function(handler); +new cloud.Function( + handler, + cloud.FunctionProps { + env: Map {} + } +); diff --git a/libs/tree-sitter-wing/grammar.js b/libs/tree-sitter-wing/grammar.js index 03e508d0999..b2a1ba5afb3 100644 --- a/libs/tree-sitter-wing/grammar.js +++ b/libs/tree-sitter-wing/grammar.js @@ -290,7 +290,7 @@ module.exports = grammar({ new_expression: ($) => seq( "new", - field("class", choice($.custom_type, $.builtin_container_type)), + field("class", choice($.custom_type, $.mutable_container_type)), field("args", $.argument_list), field("id", optional($.new_object_id)), field("scope", optional($.new_object_scope)) @@ -304,7 +304,7 @@ module.exports = grammar({ choice( seq($.custom_type, optional("?")), seq($.builtin_type, optional("?")), - seq($.builtin_container_type, optional("?")), + seq($._builtin_container_type, optional("?")), $.function_type ), @@ -356,25 +356,39 @@ module.exports = grammar({ parameter_list: ($) => seq("(", commaSep($.parameter_definition), ")"), - builtin_container_type: ($) => + immutable_container_type: ($) => seq( field( "collection_type", choice( + "Array", "Set", "Map", - "Array", - "MutSet", - "MutMap", - "MutArray", - "Promise" - ) + ), ), - "<", - field("type_parameter", $._type), - ">" + $._container_value_type ), + mutable_container_type: ($) => + choice( + seq( + field( + "collection_type", + choice( + "MutSet", + "MutMap", + "MutArray", + "Promise" + ) + ), + $._container_value_type + ), + ), + + _builtin_container_type: ($) => choice($.immutable_container_type, $.mutable_container_type), + + _container_value_type: ($) => seq("<", field("type_parameter", $._type), ">"), + unary_expression: ($) => choice( ...[ @@ -438,11 +452,18 @@ module.exports = grammar({ _collection_literal: ($) => choice($.array_literal, $.set_literal, $.map_literal), array_literal: ($) => seq("[", commaSep($.expression), "]"), - set_literal: ($) => seq("{", commaSep($.expression), "}"), - map_literal: ($) => seq("{", commaSep($.map_literal_member), "}"), + set_literal: ($) => seq( + optional(field("type", $.immutable_container_type)), + "{", commaSep($.expression), "}" + ), + map_literal: ($) => seq( + optional(field("type", $.immutable_container_type)), + "{", commaSep($.map_literal_member), "}" + ), struct_literal: ($) => seq(field("type", $.custom_type), "{", field("fields", commaSep($.struct_literal_member)), "}"), map_literal_member: ($) => + // TODO: make sure $.string here conforms to valid keys in a map seq(choice($.identifier, $.string), ":", $.expression), struct_literal_member: ($) => seq($.identifier, ":", $.expression), diff --git a/libs/tree-sitter-wing/test/corpus/expressions.txt b/libs/tree-sitter-wing/test/corpus/expressions.txt index 93583a4fe6f..1f889086c4d 100644 --- a/libs/tree-sitter-wing/test/corpus/expressions.txt +++ b/libs/tree-sitter-wing/test/corpus/expressions.txt @@ -254,6 +254,27 @@ Set Literal ) ) +================================ +Typed Set Literal +================================ + +Set { 1, 2, 3 }; + +--- + +(source + (expression_statement + (set_literal + (immutable_container_type + (builtin_type) + ) + (number) + (number) + (number) + ) + ) +) + ================================ Empty Map Literal ================================ @@ -324,7 +345,7 @@ let a: Array = []; (source (variable_definition_statement (identifier) - (builtin_container_type + (immutable_container_type (builtin_type) ) (array_literal) @@ -335,7 +356,7 @@ let a: Array = []; Container Type Constructor Invocation ================================ -let a = new Map(); +let a = new MutMap(); --- @@ -343,7 +364,7 @@ let a = new Map(); (variable_definition_statement (identifier) (new_expression - (builtin_container_type + (mutable_container_type (builtin_type) ) (argument_list) @@ -363,14 +384,14 @@ let a = A { f1: 3, f2: true }; (variable_definition_statement name: (identifier) value: (struct_literal - struct_type: (custom_type + type: (custom_type object: (identifier) ) - struct_fields: (struct_literal_member + fields: (struct_literal_member (identifier) (number) ) - struct_fields: (struct_literal_member + fields: (struct_literal_member (identifier) (bool) ) diff --git a/libs/wingc/src/ast.rs b/libs/wingc/src/ast.rs index e396f558ca9..d93c7630ce3 100644 --- a/libs/wingc/src/ast.rs +++ b/libs/wingc/src/ast.rs @@ -46,6 +46,7 @@ pub enum Type { String, Bool, Duration, + Map(Box), FunctionSignature(FunctionSignature), CustomType { root: Symbol, fields: Vec }, } @@ -161,6 +162,10 @@ pub enum ExprType { type_: Type, fields: HashMap, }, + MapLiteral { + type_: Option, + fields: HashMap, + }, } #[derive(Debug)] diff --git a/libs/wingc/src/capture.rs b/libs/wingc/src/capture.rs index dc305121014..4bab6291047 100644 --- a/libs/wingc/src/capture.rs +++ b/libs/wingc/src/capture.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use crate::{ ast::{ArgList, Expr, ExprType, Flight, Reference, Scope, Statement, Symbol}, @@ -10,7 +10,7 @@ use crate::{ is to use a subset of its client's methods. In that case we need to specify the name of the method used in the capture. Currently this is the only capture definition supported. In the future we might want add more verbose capture definitions like regexes on method parameters etc. */ -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, Hash)] pub struct CaptureDef { pub method: String, } @@ -20,12 +20,15 @@ struct Capture { pub def: CaptureDef, } -pub type Captures = HashMap>; +pub type Captures = HashMap>; fn collect_captures(capture_list: Vec) -> Captures { let mut captures: Captures = HashMap::new(); for capture in capture_list { - captures.entry(capture.object).or_insert(vec![]).push(capture.def); + captures + .entry(capture.object) + .or_insert(HashSet::new()) + .insert(capture.def); } captures } @@ -89,20 +92,6 @@ pub fn scan_captures(ast_root: &Scope) { fn scan_captures_in_call(reference: &Reference, args: &ArgList, env: &TypeEnv) -> Vec { let mut res = vec![]; if let Reference::NestedIdentifier { object, property } = reference { - // TODO: hack to check if this is reference to an imported cloud object (since we don't have typing for that yet) - if let ExprType::Reference(Reference::Identifier(symbol)) = &object.variant { - let (t, f) = env.lookup_ext(symbol); - if t.is_anything() && matches!(f, Flight::Pre) { - // for now we assume all "anythings" are imported cloud resources, remove this once we have `bring` support and typing - res.push(Capture { - object: symbol.clone(), - def: CaptureDef { - method: property.name.clone(), - }, - }); - } - } - res.extend(scan_captures_in_expression(&object, env)); // If the expression evaluates to a resource we should check what method of the resource we're accessing @@ -191,6 +180,11 @@ fn scan_captures_in_expression(exp: &Expr, env: &TypeEnv) -> Vec { res.extend(scan_captures_in_expression(&v, env)); } } + ExprType::MapLiteral { fields, .. } => { + for (_, v) in fields.iter() { + res.extend(scan_captures_in_expression(&v, env)); + } + } } res } diff --git a/libs/wingc/src/jsify.rs b/libs/wingc/src/jsify.rs index b8cd7f0d06b..a7538e80cb6 100644 --- a/libs/wingc/src/jsify.rs +++ b/libs/wingc/src/jsify.rs @@ -228,6 +228,16 @@ fn jsify_expression(expression: &Expr) -> String { .join("\n") ) } + ExprType::MapLiteral { fields, .. } => { + format!( + "{{\n{}\n}}", + fields + .iter() + .map(|(key, expr)| format!("\"{}\": {},", key, jsify_expression(expr))) + .collect::>() + .join("\n") + ) + } } } diff --git a/libs/wingc/src/parser.rs b/libs/wingc/src/parser.rs index 30aeaf8d8a4..c8cd6c54706 100644 --- a/libs/wingc/src/parser.rs +++ b/libs/wingc/src/parser.rs @@ -320,6 +320,19 @@ impl Parser<'_> { }, })) } + "mutable_container_type" | "immutable_container_type" => { + let container_type = self.node_text(&type_node.child_by_field_name("collection_type").unwrap()); + let element_type = type_node.child_by_field_name("type_parameter").unwrap(); + match container_type { + "Map" => Ok(Type::Map(Box::new(self.build_type(&element_type)?))), + "Set" | "Array" | "MutSet" | "MutMap" | "MutArray" | "Promise" => self.add_error( + format!("{} container type currently unsupported", container_type), + type_node, + )?, + "ERROR" => self.add_error(format!("Expected builtin container type"), type_node)?, + other => panic!("Unexpected container type {} || {:#?}", other, type_node), + } + } "ERROR" => self.add_error(format!("Expected type"), type_node), other => panic!("Unexpected type {} || {:#?}", other, type_node), } @@ -466,7 +479,39 @@ impl Parser<'_> { "preflight_closure" => self.add_error(format!("Anonymous closures not implemented yet"), expression_node), "inflight_closure" => self.add_error(format!("Anonymous closures not implemented yet"), expression_node), "pure_closure" => self.add_error(format!("Anonymous closures not implemented yet"), expression_node), - "map_literal" => self.add_error(format!("Map literals not implemented yet"), expression_node), + "map_literal" => { + let map_type = if let Some(type_node) = expression_node.child_by_field_name("type") { + Some(self.build_type(&type_node)?) + } else { + None + }; + + let mut fields = HashMap::new(); + let mut cursor = expression_node.walk(); + for field_node in expression_node.children_by_field_name("map_literal_member", &mut cursor) { + let key_node = field_node.named_child(0).unwrap(); + let key = match key_node.kind() { + "string" => { + let s = self.node_text(&key_node); + // Remove quotes, we assume this is a valid key for a map + s[1..s.len() - 1].to_string() + } + "identifier" => self.node_text(&key_node).to_string(), + other => panic!("Unexpected map key type {} at {:?}", other, key_node), + }; + let value_node = field_node.named_child(1).unwrap(); + if fields.contains_key(&key) { + _ = self.add_error::<()>(format!("Duplicate key {} in map literal", key), &key_node); + } else { + fields.insert(key, self.build_expression(&value_node)?); + } + } + + Ok(Expr::new(ExprType::MapLiteral { + fields, + type_: map_type, + })) + } "struct_literal" => { let type_ = self.build_type(&expression_node.child_by_field_name("type").unwrap()); let mut fields = HashMap::new(); diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index b44686fd26e..632bb6cba5d 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -14,6 +14,8 @@ pub enum Type { String, Duration, Boolean, + Map(TypeRef), + Set(TypeRef), Function(FunctionSignature), Class(Class), Resource(Class), @@ -121,6 +123,18 @@ impl PartialEq for Type { let r: &Type = (*r0).into(); l == r } + (Self::Map(l0), Self::Map(r0)) => { + // Maps are of the same type if they have the same value type + let l: &Type = (*l0).into(); + let r: &Type = (*r0).into(); + l == r + } + (Self::Set(l0), Self::Set(r0)) => { + // Sets are of the same type if they have the same value type + let l: &Type = (*l0).into(); + let r: &Type = (*r0).into(); + l == r + } // For all other types (built-ins) we compare the enum value _ => core::mem::discriminant(self) == core::mem::discriminant(other), } @@ -192,6 +206,8 @@ impl Display for Type { let struct_type = s.as_struct().expect("Struct instance must reference to a struct"); write!(f, "instance of {}", struct_type.name.name) } + Type::Map(v) => write!(f, "Map<{}>", v), + Type::Set(v) => write!(f, "Set<{}>", v), } } } @@ -397,6 +413,7 @@ impl<'a> TypeChecker<'a> { } } + // Validates types in the expression make sense and returns the expression's inferred type fn type_check_exp(&mut self, exp: &Expr, env: &TypeEnv) -> Option { let t = match &exp.variant { ExprType::Literal(lit) => match lit { @@ -579,6 +596,33 @@ impl<'a> TypeChecker<'a> { Some(struct_type) } + ExprType::MapLiteral { fields, type_ } => { + // Infer type based on either the explicit type or the value in one of the fields + let container_type = if let Some(type_) = type_ { + self.resolve_type(type_, env) + } else if !fields.is_empty() { + let some_val_type = self.type_check_exp(fields.iter().next().unwrap().1, env).unwrap(); + self.types.add_type(Type::Map(some_val_type)) + } else { + panic!( + "Cannot infer type of empty map literal with no type annotation: {:?}", + exp + ); + }; + + let value_type = match container_type.into() { + &Type::Map(t) => t, + _ => panic!("Expected map type, found {}", container_type), + }; + + // Verify all types are the same as the inferred type + for (_, v) in fields.iter() { + let t = self.type_check_exp(v, env).unwrap(); + self.validate_type(t, value_type, v); + } + + Some(container_type) + } }; *exp.evaluated_type.borrow_mut() = t; t @@ -621,6 +665,11 @@ impl<'a> TypeChecker<'a> { nested_name.extend(fields.iter().map(|f| f.name.as_str())); env.lookup_nested(&nested_name) } + AstType::Map(v) => { + let value_type = self.resolve_type(v, env); + // TODO: avoid creating a new type for each map resolution + self.types.add_type(Type::Map(value_type)) + } } } @@ -631,13 +680,13 @@ impl<'a> TypeChecker<'a> { initial_value, type_, } => { - let exp_type = self.type_check_exp(initial_value, env).unwrap(); - if let Some(t) = type_ { - let explicit_type = self.resolve_type(t, env); - self.validate_type(exp_type, explicit_type, initial_value); + let explicit_type = type_.as_ref().map(|t| self.resolve_type(t, env)); + let inferred_type = self.type_check_exp(initial_value, env).unwrap(); + if let Some(explicit_type) = explicit_type { + self.validate_type(inferred_type, explicit_type, initial_value); env.define(var_name, explicit_type); } else { - env.define(var_name, exp_type); + env.define(var_name, inferred_type); } } Statement::FunctionDefinition(func_def) => { @@ -727,7 +776,6 @@ impl<'a> TypeChecker<'a> { for type_fqn in assembly.types.as_ref().unwrap().keys() { // Skip types outside the imported namespace if !type_fqn.starts_with(&prefix) { - println!("Skipping {} (outside of module {})", type_fqn, module_name.name); continue; } @@ -735,7 +783,6 @@ impl<'a> TypeChecker<'a> { // and might have already defined the current type internally let type_name = jsii_importer.fqn_to_type_name(type_fqn); if jsii_importer.namespace_env.try_lookup(&type_name).is_some() { - println!("Type {} already defined, skipping", type_name); continue; } jsii_importer.import_type(type_fqn); diff --git a/libs/wingc/src/type_check/jsii_importer.rs b/libs/wingc/src/type_check/jsii_importer.rs index e0039b16bee..53b568b7a8f 100644 --- a/libs/wingc/src/type_check/jsii_importer.rs +++ b/libs/wingc/src/type_check/jsii_importer.rs @@ -65,7 +65,6 @@ impl<'a> JsiiImporter<'a> { } else if type_fqn == "@monadahq/wingsdk.core.Duration" { Some(self.wing_types.duration()) } else { - println!("Getting wing type for {}", type_fqn); Some(self.lookup_or_create_type(type_fqn)) } } else if let Some(Value::Object(_)) = obj.get("collection") { @@ -86,7 +85,6 @@ impl<'a> JsiiImporter<'a> { let type_name = &self.fqn_to_type_name(type_fqn); // Check if this type is already define in this module's namespace if let Some(t) = self.namespace_env.try_lookup(type_name) { - println!("Found type {} in wing env", type_name); return t; } // Define new type and return it @@ -112,10 +110,7 @@ impl<'a> JsiiImporter<'a> { pub fn import_type(&mut self, type_fqn: &str) -> Option { // Hack: if the class name is RESOURCE_CLASS_FQN then we treat this class as a resource and don't need to define it - let type_name = self.fqn_to_type_name(type_fqn); - println!("Defining type {}", type_name); if type_fqn == RESOURCE_CLASS_FQN { - println!("Hack: no need to define {}", type_name); return None; } @@ -276,20 +271,16 @@ impl<'a> JsiiImporter<'a> { let base_class = if let Some(base_class_fqn) = &jsii_class.base { // Hack: if the base class name is RESOURCE_CLASS_FQN then we treat this class as a resource and don't need to define its parent if base_class_fqn == RESOURCE_CLASS_FQN { - println!("Hack: no need to define base class {}", base_class_fqn); is_resource = true; None } else { let base_class_name = self.fqn_to_type_name(base_class_fqn); - println!("going to lookup base class {}", base_class_name); let base_class_type = if let Some(base_class_type) = self.namespace_env.try_lookup(&base_class_name) { Some(base_class_type) } else { // If the base class isn't defined yet then define it first (recursive call) let base_class_type = self.import_type(base_class_fqn); - if base_class_type.is_none() - /* && base_class_name != RESOURCE_CLASS_FQN*/ - { + if base_class_type.is_none() { panic!("Failed to define base class {}", base_class_name); } base_class_type @@ -311,7 +302,6 @@ impl<'a> JsiiImporter<'a> { .initializer .as_ref() .expect("JSII classes must have a constructor"); - println!("got here! we can define {}", type_name); // Get env of base class/resource let base_class_env = if let Some(base_class) = base_class { @@ -375,7 +365,7 @@ impl<'a> JsiiImporter<'a> { &Self::jsii_name_to_symbol(WING_CONSTRUCTOR_NAME, &jsii_initializer.location_in_module), method_sig, ); - println!("Created constructor for {}: {:?}", type_name, method_sig); + // Add methods and properties to the class environment self.add_members_to_class_env(&jsii_class, is_resource, Flight::Pre, &mut class_env, new_type); if is_resource { From 0c2e469963d88dd073d0ed9c9a453bfda470c3b7 Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Sun, 9 Oct 2022 16:59:36 +0300 Subject: [PATCH 11/31] oops --- libs/wingc/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/wingc/src/lib.rs b/libs/wingc/src/lib.rs index 6613133b37d..850ca916572 100644 --- a/libs/wingc/src/lib.rs +++ b/libs/wingc/src/lib.rs @@ -92,7 +92,7 @@ mod sanity { for entry in paths { if let Ok(entry) = entry { if let Some(source) = entry.path().to_str() { - if source.ends_with("sdk_capture_test.w") { + if source.ends_with(".w") { println!("\n=== {} ===\n", source); println!("{}\n---", compile(source, None)); } From d1b2eb0a629e831868a5396eb81143445d90366e Mon Sep 17 00:00:00 2001 From: yoav-steinberg Date: Mon, 10 Oct 2022 13:57:41 +0300 Subject: [PATCH 12/31] Apply suggestions from code review Co-authored-by: Chris Rybicki --- libs/wingc/src/capture.rs | 4 ++-- libs/wingc/src/type_check/type_env.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/wingc/src/capture.rs b/libs/wingc/src/capture.rs index 4bab6291047..1efacf7eb89 100644 --- a/libs/wingc/src/capture.rs +++ b/libs/wingc/src/capture.rs @@ -176,12 +176,12 @@ fn scan_captures_in_expression(exp: &Expr, env: &TypeEnv) -> Vec { } ExprType::Literal(_) => {} ExprType::StructLiteral { fields, .. } => { - for (_, v) in fields.iter() { + for v in fields.values() { res.extend(scan_captures_in_expression(&v, env)); } } ExprType::MapLiteral { fields, .. } => { - for (_, v) in fields.iter() { + for v in fields.values() { res.extend(scan_captures_in_expression(&v, env)); } } diff --git a/libs/wingc/src/type_check/type_env.rs b/libs/wingc/src/type_check/type_env.rs index defaf06be24..ee7b2f949af 100644 --- a/libs/wingc/src/type_check/type_env.rs +++ b/libs/wingc/src/type_check/type_env.rs @@ -80,7 +80,7 @@ impl TypeEnv { let mut it = nested_vec.iter(); let mut symb = *it.next().unwrap(); - let mut t = self.try_lookup(symb).expect(&format!("Unkonwn symbol {}", symb)); + let mut t = self.try_lookup(symb).expect(&format!("Unknown symbol {}", symb)); while let Some(next_symb) = it.next() { let ns = t @@ -89,7 +89,7 @@ impl TypeEnv { t = ns .env .try_lookup(*next_symb) - .expect(&format!("Unkonwn symbol {}", *next_symb)); + .expect(&format!("Unknown symbol {}", *next_symb)); symb = *next_symb; } t From 424902f0697b77f9d3fa7a56af941620953937f9 Mon Sep 17 00:00:00 2001 From: Sepehr Laal <5657848+3p3r@users.noreply.github.com> Date: Tue, 11 Oct 2022 12:20:36 -0700 Subject: [PATCH 13/31] feat(wingc): bring local wing files --- examples/invalid/circular-bring.w | 1 + examples/invalid/circular/mod.w | 1 + examples/simple/bring.w | 2 + examples/simple/modules/mod1.w | 2 + examples/simple/modules/mod2.w | 4 ++ examples/simple/modules/sub/mod.w | 1 + .../test/corpus/statements.txt | 15 +++++ libs/wingc/.vscode/launch.json | 19 ++++++ libs/wingc/src/ast.rs | 5 ++ libs/wingc/src/capture.rs | 6 ++ libs/wingc/src/jsify.rs | 9 +++ libs/wingc/src/lib.rs | 45 ++----------- libs/wingc/src/parser.rs | 41 +++++++++--- libs/wingc/src/parser/bring.rs | 67 +++++++++++++++++++ libs/wingc/src/type_check.rs | 3 + 15 files changed, 173 insertions(+), 48 deletions(-) create mode 100644 examples/invalid/circular-bring.w create mode 100644 examples/invalid/circular/mod.w create mode 100644 examples/simple/bring.w create mode 100644 examples/simple/modules/mod1.w create mode 100644 examples/simple/modules/mod2.w create mode 100644 examples/simple/modules/sub/mod.w create mode 100644 libs/wingc/.vscode/launch.json create mode 100644 libs/wingc/src/parser/bring.rs diff --git a/examples/invalid/circular-bring.w b/examples/invalid/circular-bring.w new file mode 100644 index 00000000000..bb663e04632 --- /dev/null +++ b/examples/invalid/circular-bring.w @@ -0,0 +1 @@ +bring "./circular/mod.w"; diff --git a/examples/invalid/circular/mod.w b/examples/invalid/circular/mod.w new file mode 100644 index 00000000000..3d5195e0dce --- /dev/null +++ b/examples/invalid/circular/mod.w @@ -0,0 +1 @@ +bring "../circular-bring.w"; diff --git a/examples/simple/bring.w b/examples/simple/bring.w new file mode 100644 index 00000000000..4cf3b12eb04 --- /dev/null +++ b/examples/simple/bring.w @@ -0,0 +1,2 @@ +bring "./modules/mod1.w"; +bring "./modules/mod2.w"; diff --git a/examples/simple/modules/mod1.w b/examples/simple/modules/mod1.w new file mode 100644 index 00000000000..fdfddbaadf6 --- /dev/null +++ b/examples/simple/modules/mod1.w @@ -0,0 +1,2 @@ +let x = 1; +let y = 2; diff --git a/examples/simple/modules/mod2.w b/examples/simple/modules/mod2.w new file mode 100644 index 00000000000..41ccf215dc0 --- /dev/null +++ b/examples/simple/modules/mod2.w @@ -0,0 +1,4 @@ +bring "./sub/mod.w"; + +let z = 3; +let q = 4; diff --git a/examples/simple/modules/sub/mod.w b/examples/simple/modules/sub/mod.w new file mode 100644 index 00000000000..e717e1f04e2 --- /dev/null +++ b/examples/simple/modules/sub/mod.w @@ -0,0 +1 @@ +let w = "what"; diff --git a/libs/tree-sitter-wing/test/corpus/statements.txt b/libs/tree-sitter-wing/test/corpus/statements.txt index 494a48dec93..b80e9cc6a46 100644 --- a/libs/tree-sitter-wing/test/corpus/statements.txt +++ b/libs/tree-sitter-wing/test/corpus/statements.txt @@ -237,3 +237,18 @@ struct Test { ) ) ) + +================================== +Bring statement +================================== + +bring cloud; +bring "./mod.w"; + +--- +(source + (short_import_statement + module_name: (identifier)) + (short_import_statement + module_name: (string)) +) diff --git a/libs/wingc/.vscode/launch.json b/libs/wingc/.vscode/launch.json new file mode 100644 index 00000000000..2807f414df2 --- /dev/null +++ b/libs/wingc/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "library 'wingc' debugger target", + "cargo": { + "args": ["test", "--no-run", "--lib", "--package=wingc"], + "filter": { + "name": "wingc", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/libs/wingc/src/ast.rs b/libs/wingc/src/ast.rs index d93c7630ce3..97a52ebf897 100644 --- a/libs/wingc/src/ast.rs +++ b/libs/wingc/src/ast.rs @@ -78,10 +78,15 @@ pub struct Constructor { #[derive(Debug)] pub enum Statement { + // TODO: merge these statements into a single one Use { module_name: Symbol, // Reference? identifier: Option, }, + Bring { + module_path: String, + statements: Scope, + }, VariableDef { var_name: Symbol, initial_value: Expr, diff --git a/libs/wingc/src/capture.rs b/libs/wingc/src/capture.rs index 1efacf7eb89..2fec8fe4683 100644 --- a/libs/wingc/src/capture.rs +++ b/libs/wingc/src/capture.rs @@ -250,6 +250,12 @@ fn scan_captures_in_scope(scope: &Scope) -> Vec { } => { todo!() } + Statement::Bring { + module_path: _, + statements: _, + } => { + todo!() + } Statement::Struct { name: _, extends: _, diff --git a/libs/wingc/src/jsify.rs b/libs/wingc/src/jsify.rs index b8b87b5477e..f549728e276 100644 --- a/libs/wingc/src/jsify.rs +++ b/libs/wingc/src/jsify.rs @@ -369,6 +369,15 @@ fn jsify_statement(statement: &Statement) -> String { .join("\n") ) } + Statement::Bring { + module_path, + statements, + } => format!( + "/* start bring module: {module} */\n{}\n/* end bring module: {module} */", + jsify_scope(statements), + module = module_path + ) + .to_string(), } } diff --git a/libs/wingc/src/lib.rs b/libs/wingc/src/lib.rs index 54b2f4da183..b1d400ee937 100644 --- a/libs/wingc/src/lib.rs +++ b/libs/wingc/src/lib.rs @@ -1,8 +1,10 @@ +use crate::parser::bring; use ast::Scope; use diagnostic::Diagnostics; use crate::parser::Parser; use std::cell::RefCell; +use std::collections::HashSet; use std::fs; use std::path::PathBuf; @@ -18,45 +20,6 @@ pub mod jsify; pub mod parser; pub mod type_check; -pub fn parse(source_file: &str) -> Scope { - let language = tree_sitter_wing::language(); - let mut parser = tree_sitter::Parser::new(); - parser.set_language(language).unwrap(); - - let source = match fs::read(&source_file) { - Ok(source) => source, - Err(err) => { - panic!("Error reading source file: {}: {:?}", &source_file, err); - } - }; - - let tree = match parser.parse(&source[..], None) { - Some(tree) => tree, - None => { - println!("Failed parsing source file: {}", source_file); - std::process::exit(1); - } - }; - - let wing_parser = Parser { - source: &source[..], - source_name: source_file.to_string(), - diagnostics: RefCell::new(Diagnostics::new()), - }; - - let scope = wing_parser.wingit(&tree.root_node()); - - for diagnostic in wing_parser.diagnostics.borrow().iter() { - println!("{}", diagnostic); - } - - if wing_parser.diagnostics.borrow().len() > 0 { - std::process::exit(1); - } - - scope -} - pub fn type_check(scope: &mut Scope, types: &mut Types) { scope.set_env(TypeEnv::new(None, None, false, Flight::Pre)); let mut tc = TypeChecker::new(types); @@ -64,10 +27,12 @@ pub fn type_check(scope: &mut Scope, types: &mut Types) { } pub fn compile(source_file: &str, out_dir: Option<&str>) -> String { + // create a new hashmap to manage imports + let mut imports = HashSet::new(); // Create universal types collection (need to keep this alive during entire compilation) let mut types = Types::new(); // Build our AST - let mut scope = parse(source_file); + let mut scope = bring::bring(source_file, None, &mut imports).unwrap(); // Type check everything and build typed symbol environment type_check(&mut scope, &mut types); // Analyze inflight captures diff --git a/libs/wingc/src/parser.rs b/libs/wingc/src/parser.rs index c8cd6c54706..cf81560767f 100644 --- a/libs/wingc/src/parser.rs +++ b/libs/wingc/src/parser.rs @@ -1,5 +1,9 @@ +pub mod bring; + +use bring::bring; use std::cell::RefCell; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; +use std::path::PathBuf; use std::{str, vec}; use tree_sitter::Node; @@ -13,6 +17,7 @@ pub struct Parser<'a> { pub source: &'a [u8], pub source_name: String, pub diagnostics: RefCell, + pub imports: RefCell<&'a mut HashSet>, } impl Parser<'_> { @@ -108,14 +113,34 @@ impl Parser<'_> { fn build_statement(&self, statement_node: &Node) -> DiagnosticResult { match statement_node.kind() { - "short_import_statement" => Ok(Statement::Use { - module_name: self.node_symbol(&statement_node.child_by_field_name("module_name").unwrap())?, - identifier: if let Some(identifier) = statement_node.child_by_field_name("alias") { - Some(self.node_symbol(&identifier)?) + "short_import_statement" => { + let module_name_node = statement_node.child_by_field_name("module_name").unwrap(); + if module_name_node.kind() == "identifier" { + Ok(Statement::Use { + module_name: self.node_symbol(&module_name_node)?, + identifier: if let Some(identifier) = statement_node.child_by_field_name("alias") { + Some(self.node_symbol(&identifier)?) + } else { + None + }, + }) + } else if module_name_node.kind() == "string" { + let bring_target = self.node_text(&module_name_node); + let bring_target = &bring_target[1..bring_target.len() - 1]; + let context_dir = PathBuf::from(&self.source_name); + let context_dir = context_dir.parent().unwrap().to_str().unwrap(); + let brought = bring(bring_target, Some(context_dir), *self.imports.borrow_mut()); + Ok(Statement::Bring { + module_path: bring_target.to_string(), + statements: brought.unwrap_or(Scope { + statements: vec![], + env: None, + }), + }) } else { - None - }, - }), + panic!("Unexpected bring type {}", module_name_node.kind()) + } + } "variable_definition_statement" => { let type_ = if let Some(type_node) = statement_node.child_by_field_name("type") { Some(self.build_type(&type_node)?) diff --git a/libs/wingc/src/parser/bring.rs b/libs/wingc/src/parser/bring.rs new file mode 100644 index 00000000000..8f16147f262 --- /dev/null +++ b/libs/wingc/src/parser/bring.rs @@ -0,0 +1,67 @@ +use crate::Diagnostics; +use crate::Scope; + +use crate::parser::Parser; +use std::cell::RefCell; +use std::collections::HashSet; +use std::fs; +use std::path::PathBuf; + +use crate::ast::Flight; +use crate::capture::scan_captures; +use crate::type_check::type_env::TypeEnv; +use crate::type_check::{TypeChecker, Types}; + +pub fn bring(source_file: &str, context: Option<&str>, imports: &mut HashSet) -> Option { + // default context directory to the current directory + let context_dir = PathBuf::from(&context.unwrap_or(".")); + // turn "source_file" into a canonical path relative to context_dir + let source_file = context_dir.join(source_file).canonicalize().unwrap(); + let source_file = source_file.to_str().unwrap(); + + // check if "imports" already contains "source_file", panic if it does + if imports.contains(source_file) { + return None; + } + + let language = tree_sitter_wing::language(); + let mut parser = tree_sitter::Parser::new(); + parser.set_language(language).unwrap(); + + let source = match fs::read(&source_file) { + Ok(source) => source, + Err(err) => { + panic!("Error reading source file: {}: {:?}", &source_file, err); + } + }; + + let tree = match parser.parse(&source[..], None) { + Some(tree) => tree, + None => { + println!("Failed parsing source file: {}", source_file); + std::process::exit(1); + } + }; + + // keep track of imports to break recursive import cycles + imports.insert(source_file.to_string()); + + let wing_parser = Parser { + imports: RefCell::new(imports), + source: &source[..], + source_name: source_file.to_string(), + diagnostics: RefCell::new(Diagnostics::new()), + }; + + let scope = wing_parser.wingit(&tree.root_node()); + + for diagnostic in wing_parser.diagnostics.borrow().iter() { + println!("{}", diagnostic); + } + + if wing_parser.diagnostics.borrow().len() > 0 { + std::process::exit(1); + } + + Some(scope) +} diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index 632bb6cba5d..103f5885be8 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -749,6 +749,9 @@ impl<'a> TypeChecker<'a> { let var_type = self.resolve_reference(variable, env); self.validate_type(exp_type, var_type, value); } + Statement::Bring { module_path: _, statements: _ } => { + unimplemented_type(); + } Statement::Use { module_name, identifier: _, From cbe71e75c66c7ab6fe76c1b0f08a1e0db522ccd7 Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Wed, 12 Oct 2022 10:49:20 +0300 Subject: [PATCH 14/31] Added support for namespace aliases on JSII brings. Also: * Fix inflight.w example based on real imported cloud.Bucket type. * Removed skipping type checking on `new` expressions when the type is resolved to an `Anything`, we don't expect custom types to be `Anything`'s anymore. --- examples/simple/inflight.w | 10 ++++++---- libs/wingc/src/type_check.rs | 15 ++++++--------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/examples/simple/inflight.w b/examples/simple/inflight.w index 35cd0abbd9c..28a31fc6ae2 100644 --- a/examples/simple/inflight.w +++ b/examples/simple/inflight.w @@ -1,11 +1,13 @@ bring cloud as c; -// type checking for captures not currently supported -// TODO Update once supported -let bucket = new c.Bucket(); +let p = c.BucketProps { + public: false +}; + +let bucket = new c.Bucket(p); inflight test() { let x = -1; let z = 11 + x; - bucket.upload(); + bucket.put("foo", "bar"); } diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index 632bb6cba5d..114a88ee096 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -456,12 +456,6 @@ impl<'a> TypeChecker<'a> { // Lookup the type in the env let type_ = self.resolve_type(class, env); - // TODO: hack to support custom types (basically stdlib cloud.Bucket), we skip type-checking and resolve them to Anything - if matches!(type_.into(), &Type::Anything) { - _ = unimplemented_type(); - return Some(type_); - } - let (class_env, class_symbol) = match type_.into() { &Type::Class(ref class) => (&class.env, &class.name), &Type::Resource(ref class) => (&class.env, &class.name), // TODO: don't allow resource instantiation inflight @@ -751,7 +745,7 @@ impl<'a> TypeChecker<'a> { } Statement::Use { module_name, - identifier: _, + identifier, } => { _ = { // Create a new env for the imported module's namespace @@ -788,12 +782,15 @@ impl<'a> TypeChecker<'a> { jsii_importer.import_type(type_fqn); } + // If provided use alias identifier as the namespace name + let namespace_name = identifier.as_ref().unwrap_or(module_name); + // Create a namespace for the imported module let namespace = self.types.add_type(Type::Namespace(Namespace { - name: module_name.name.clone(), + name: namespace_name.name.clone(), env: namespace_env, })); - env.define(module_name, namespace); + env.define(namespace_name, namespace); } } } From 08ff9acd12fdeff31f05c6191c2bf2c747240cbe Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Wed, 12 Oct 2022 11:02:18 +0300 Subject: [PATCH 15/31] jisify: Remoce hack to always add scope and id to anything objects. This was there because we didn't really know the type of the `new` expression. Now the type is available through a the `Expr.evaluated_type`. --- libs/wingc/src/jsify.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/libs/wingc/src/jsify.rs b/libs/wingc/src/jsify.rs index b8b87b5477e..28437f85a13 100644 --- a/libs/wingc/src/jsify.rs +++ b/libs/wingc/src/jsify.rs @@ -132,15 +132,6 @@ fn jsify_arg_list(arg_list: &ArgList, scope: Option<&str>, id: Option<&str>) -> } } -fn is_resource_type(typ: &Type) -> bool { - // TODO: for now anything under "cloud" is a resource - if let Type::CustomType { root, fields: _ } = typ { - root.name == "cloud" - } else { - false - } -} - fn jsify_type(typ: &Type) -> String { match typ { Type::CustomType { root, fields } => { @@ -170,8 +161,15 @@ fn jsify_expression(expression: &Expr) -> String { arg_list, obj_scope: _, // TODO } => { - if is_resource_type(class) { - // If this is a resource then add the scope and id to the arg list + let is_resource = expression + .evaluated_type + .borrow() + .unwrap() + .as_resource_object() + .is_some(); + + // If this is a resource then add the scope and id to the arg list + if is_resource { format!( "new {}({})", jsify_type(class), From e1271e5f48484b21ae0d122af0ff995de5846c7f Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Wed, 12 Oct 2022 11:08:44 +0300 Subject: [PATCH 16/31] cr fix --- libs/tree-sitter-wing/grammar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/tree-sitter-wing/grammar.js b/libs/tree-sitter-wing/grammar.js index b2a1ba5afb3..5c3db9b6039 100644 --- a/libs/tree-sitter-wing/grammar.js +++ b/libs/tree-sitter-wing/grammar.js @@ -364,7 +364,8 @@ module.exports = grammar({ "Array", "Set", "Map", - ), + "Promise", + ), ), $._container_value_type ), @@ -378,7 +379,6 @@ module.exports = grammar({ "MutSet", "MutMap", "MutArray", - "Promise" ) ), $._container_value_type From 3dbae32302433d44c9542d764a9c59ca828d51c6 Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Wed, 12 Oct 2022 11:34:31 +0300 Subject: [PATCH 17/31] cr fix --- libs/tree-sitter-wing/grammar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/tree-sitter-wing/grammar.js b/libs/tree-sitter-wing/grammar.js index 5c3db9b6039..2e58f01ec8a 100644 --- a/libs/tree-sitter-wing/grammar.js +++ b/libs/tree-sitter-wing/grammar.js @@ -453,11 +453,11 @@ module.exports = grammar({ choice($.array_literal, $.set_literal, $.map_literal), array_literal: ($) => seq("[", commaSep($.expression), "]"), set_literal: ($) => seq( - optional(field("type", $.immutable_container_type)), + optional(field("type", $._builtin_container_type)), "{", commaSep($.expression), "}" ), map_literal: ($) => seq( - optional(field("type", $.immutable_container_type)), + optional(field("type", $._builtin_container_type)), "{", commaSep($.map_literal_member), "}" ), struct_literal: ($) => seq(field("type", $.custom_type), "{", field("fields", commaSep($.struct_literal_member)), "}"), From 2f3c6aff4b73b6616b5496b6f8476eb6ffe2024f Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Wed, 12 Oct 2022 11:39:38 +0300 Subject: [PATCH 18/31] cr fix --- libs/tree-sitter-wing/grammar.js | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/tree-sitter-wing/grammar.js b/libs/tree-sitter-wing/grammar.js index 2e58f01ec8a..44c572a0a4f 100644 --- a/libs/tree-sitter-wing/grammar.js +++ b/libs/tree-sitter-wing/grammar.js @@ -463,7 +463,6 @@ module.exports = grammar({ struct_literal: ($) => seq(field("type", $.custom_type), "{", field("fields", commaSep($.struct_literal_member)), "}"), map_literal_member: ($) => - // TODO: make sure $.string here conforms to valid keys in a map seq(choice($.identifier, $.string), ":", $.expression), struct_literal_member: ($) => seq($.identifier, ":", $.expression), From 798f1fca404754ca515a66eb6cbaa7bc766741b6 Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Wed, 12 Oct 2022 12:35:47 +0300 Subject: [PATCH 19/31] CR: Fix constructor name. --- libs/wingc/src/type_check.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index 114a88ee096..05d82706794 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -26,7 +26,7 @@ pub enum Type { Namespace(Namespace), } -const WING_CONSTRUCTOR_NAME: &'static str = "constructor"; +const WING_CONSTRUCTOR_NAME: &'static str = "init"; #[derive(Derivative)] #[derivative(Debug)] From e04275b3979f1679f3beda0ec9e7e25181df42fc Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Wed, 12 Oct 2022 12:46:47 +0300 Subject: [PATCH 20/31] CR fix --- libs/wingc/src/type_check/jsii_importer.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/libs/wingc/src/type_check/jsii_importer.rs b/libs/wingc/src/type_check/jsii_importer.rs index 53b568b7a8f..44acc3a94c8 100644 --- a/libs/wingc/src/type_check/jsii_importer.rs +++ b/libs/wingc/src/type_check/jsii_importer.rs @@ -53,10 +53,7 @@ impl<'a> JsiiImporter<'a> { _ => panic!("TODO: handle primitive type {}", primitive_name), } } else if let Some(Value::String(type_fqn)) = obj.get("fqn") { - // TODO: we can probably get rid of this special handling for cloud.Void - if type_fqn == "@monadahq/wingsdk.cloud.Void" { - None - } else if type_fqn == "@monadahq/wingsdk.core.Inflight" { + if type_fqn == "@monadahq/wingsdk.core.Inflight" { Some(self.wing_types.add_type(Type::Function(FunctionSignature { args: vec![self.wing_types.anything()], return_type: Some(self.wing_types.anything()), From e4388e5760ed9def6dc6943944c6f0d4a555ba3b Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Wed, 12 Oct 2022 12:51:38 +0300 Subject: [PATCH 21/31] cr fix --- libs/wingc/src/type_check/jsii_importer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/wingc/src/type_check/jsii_importer.rs b/libs/wingc/src/type_check/jsii_importer.rs index 44acc3a94c8..c5e14530c13 100644 --- a/libs/wingc/src/type_check/jsii_importer.rs +++ b/libs/wingc/src/type_check/jsii_importer.rs @@ -264,7 +264,7 @@ impl<'a> JsiiImporter<'a> { let mut is_resource = false; let type_name = &self.fqn_to_type_name(&jsii_class.fqn); - // Get the base class of the JSII class + // Get the base class of the JSII class, define it via recursive call if it's not define yet let base_class = if let Some(base_class_fqn) = &jsii_class.base { // Hack: if the base class name is RESOURCE_CLASS_FQN then we treat this class as a resource and don't need to define its parent if base_class_fqn == RESOURCE_CLASS_FQN { From 0c83f751462f00df68546d4249c9475a3cea58d1 Mon Sep 17 00:00:00 2001 From: yoav-steinberg Date: Wed, 12 Oct 2022 13:18:26 +0300 Subject: [PATCH 22/31] Update libs/wingc/src/type_check/jsii_importer.rs Co-authored-by: Elad Ben-Israel --- libs/wingc/src/type_check/jsii_importer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/wingc/src/type_check/jsii_importer.rs b/libs/wingc/src/type_check/jsii_importer.rs index c5e14530c13..031fe128370 100644 --- a/libs/wingc/src/type_check/jsii_importer.rs +++ b/libs/wingc/src/type_check/jsii_importer.rs @@ -332,7 +332,7 @@ impl<'a> JsiiImporter<'a> { Type::Class(class_spec) }); self.namespace_env.define(&new_type_symbol, new_type); - // Create class's actually environment before we add properties and methods to it + // Create class's actual environment before we add properties and methods to it let mut class_env = TypeEnv::new(base_class_env, None, true, self.namespace_env.flight); // Add constructor to the class environment let mut arg_types = vec![]; From c4c357567aacb3b878db185186efb4e77f9135cf Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Wed, 12 Oct 2022 15:01:29 +0300 Subject: [PATCH 23/31] Support inheriting from from wingsdk. --- libs/tree-sitter-wing/grammar.js | 4 ++-- libs/wingc/src/ast.rs | 2 +- libs/wingc/src/jsify.rs | 2 +- libs/wingc/src/parser.rs | 8 +++++--- libs/wingc/src/type_check.rs | 8 ++++---- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/libs/tree-sitter-wing/grammar.js b/libs/tree-sitter-wing/grammar.js index 44c572a0a4f..9912c757651 100644 --- a/libs/tree-sitter-wing/grammar.js +++ b/libs/tree-sitter-wing/grammar.js @@ -124,7 +124,7 @@ module.exports = grammar({ seq( "class", field("name", $.identifier), - optional(seq("extends", field("parent", $.identifier))), + optional(seq("extends", field("parent", $.custom_type))), field("implementation", $.class_implementation) ), class_implementation: ($) => @@ -161,7 +161,7 @@ module.exports = grammar({ seq( "resource", field("name", $.identifier), - optional(seq("extends", field("parent", $.identifier))), + optional(seq("extends", field("parent", $.custom_type))), field("implementation", $.resource_implementation) ), resource_implementation: ($) => diff --git a/libs/wingc/src/ast.rs b/libs/wingc/src/ast.rs index d93c7630ce3..58e7b4f4711 100644 --- a/libs/wingc/src/ast.rs +++ b/libs/wingc/src/ast.rs @@ -110,7 +110,7 @@ pub enum Statement { members: Vec, methods: Vec, constructor: Constructor, - parent: Option, + parent: Option, is_resource: bool, }, Struct { diff --git a/libs/wingc/src/jsify.rs b/libs/wingc/src/jsify.rs index 139eb5ae89b..392522266a5 100644 --- a/libs/wingc/src/jsify.rs +++ b/libs/wingc/src/jsify.rs @@ -337,7 +337,7 @@ fn jsify_statement(statement: &Statement, out_dir: &PathBuf) -> String { "class {}{}\n{{\n{}\n{}\n{}\n}}", jsify_symbol(name), if let Some(parent) = parent { - format!(" extends {}", jsify_symbol(parent)) + format!(" extends {}", jsify_type(parent)) } else { "".to_string() }, diff --git a/libs/wingc/src/parser.rs b/libs/wingc/src/parser.rs index c8cd6c54706..838aba5ad30 100644 --- a/libs/wingc/src/parser.rs +++ b/libs/wingc/src/parser.rs @@ -246,9 +246,11 @@ impl Parser<'_> { )?; } - let parent = statement_node - .child_by_field_name("parent") - .map(|n| self.node_symbol(&n).unwrap()); + let parent = if let Some(parent_node) = statement_node.child_by_field_name("parent") { + Some(self.build_type(&parent_node)?) + } else { + None + }; Ok(Statement::Class { name, members, diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index 05d82706794..bc2960746a4 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -830,19 +830,19 @@ impl<'a> TypeChecker<'a> { let env_flight = if *is_resource { Flight::Pre } else { Flight::In }; // Verify parent is actually a known Class/Resource and get their env - let (parent_class, parent_class_env) = if let Some(parent_symbol) = parent { - let t = env.lookup(parent_symbol); + let (parent_class, parent_class_env) = if let Some(parent_type) = parent { + let t = self.resolve_type(parent_type, env); if *is_resource { if let &Type::Resource(ref class) = t.into() { (Some(t), Some(&class.env as *const TypeEnv)) } else { - panic!("Resource {}'s parent {} is not a resource", name, parent_symbol); + panic!("Resource {}'s parent {} is not a resource", name, t); } } else { if let &Type::Class(ref class) = t.into() { (Some(t), Some(&class.env as *const TypeEnv)) } else { - panic!("Class {}'s parent {} is not a class", name, parent_symbol); + panic!("Class {}'s parent {} is not a class", name, t); } } } else { From 4185d9b2d4320d5a79808a0af225ca60045be139 Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Wed, 12 Oct 2022 15:05:15 +0300 Subject: [PATCH 24/31] fix grammar test --- libs/tree-sitter-wing/test/corpus/statements.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libs/tree-sitter-wing/test/corpus/statements.txt b/libs/tree-sitter-wing/test/corpus/statements.txt index 494a48dec93..c55e9c82cb3 100644 --- a/libs/tree-sitter-wing/test/corpus/statements.txt +++ b/libs/tree-sitter-wing/test/corpus/statements.txt @@ -157,7 +157,9 @@ class A extends B {} (source (class_definition name: (identifier) - parent: (identifier) + parent: (custom_type + object: (identifier) + ) implementation: (class_implementation) ) ) @@ -214,7 +216,9 @@ resource A extends B {} (source (resource_definition name: (identifier) - parent: (identifier) + parent: (custom_type + object: (identifier) + ) implementation: (resource_implementation) ) ) From cfc50fd21553e157df79ead0e1d6984bd65516f0 Mon Sep 17 00:00:00 2001 From: Sepehr Laal <5657848+3p3r@users.noreply.github.com> Date: Wed, 12 Oct 2022 20:54:51 -0700 Subject: [PATCH 25/31] feat: hacked together a jsii-ify to experiment with the idea of compiling modules in-place --- libs/wingc/src/jsiiify.rs | 198 ++++++++++++++++++++++++++++++++++++++ libs/wingc/src/lib.rs | 5 + 2 files changed, 203 insertions(+) create mode 100644 libs/wingc/src/jsiiify.rs diff --git a/libs/wingc/src/jsiiify.rs b/libs/wingc/src/jsiiify.rs new file mode 100644 index 00000000000..cb79173092f --- /dev/null +++ b/libs/wingc/src/jsiiify.rs @@ -0,0 +1,198 @@ +use crate::{ast::*, type_check::Types}; +use serde_json::json; +use std::{collections::BTreeMap, fs}; +use wingii::jsii; + +/// generates a JSII manifest for the given scope +/// todo: JSII does not support functions and free variables, but... +/// we can support it by assigning them to a global "exports" class as statics +pub fn jsiiify(scope: &Scope, types: &Types) -> String { + let mut jsii_types: BTreeMap = BTreeMap::new(); + for statement in scope.statements.iter() { + match statement { + Statement::VariableDef { .. } => {} + Statement::FunctionDefinition(func_def) => match func_def.signature.flight { + Flight::In => {} + Flight::Pre => {} + }, + Statement::Class { + constructor, + is_resource, + members, + methods, + name, + parent, + } => { + assert!(!is_resource, "resources are not supported yet"); + let manifest = jsii::ClassType { + abstract_: Some(false), + assembly: "wingc".to_string(), + base: if parent.is_some() { + Some(parent.as_ref().unwrap().name.to_string()) + } else { + None + }, + docs: None, + fqn: format!("wingc.{}", name.name), + interfaces: None, + kind: "class".to_string(), + namespace: None, + location_in_module: None, + symbol_id: None, + name: name.name.to_string(), + initializer: Some(jsii::Callable { + docs: None, + location_in_module: None, + overrides: None, + protected: None, + variadic: None, + parameters: Some( + constructor + .parameters + .iter() + .map(|param| jsii::Parameter { + docs: None, + name: param.name.to_string(), + optional: None, + type_: json!({ "primitive": "any" }), + variadic: None, + }) + .collect(), + ), + }), + properties: Some( + members + .iter() + .map(|member| jsii::Property { + docs: None, + location_in_module: None, + name: member.name.name.to_string(), + optional: None, + protected: None, + abstract_: None, + const_: None, + immutable: None, + overrides: None, + static_: None, + type_: json!({ "primitive": "any" }), + }) + .collect(), + ), + methods: Some( + methods + .iter() + .map(|method| jsii::Method { + docs: None, + async_: None, + variadic: None, + location_in_module: None, + // remove everything after first space (?) + name: method.name.name.to_string(), + protected: None, + abstract_: None, + overrides: None, + static_: None, + returns: Some(jsii::OptionalValue { + optional: None, + type_: json!({ "primitive": "any" }), + }), + parameters: Some( + method + .parameters + .iter() + .map(|param| jsii::Parameter { + docs: None, + name: param.name.to_string(), + optional: None, + type_: json!({ "primitive": "any" }), + variadic: None, + }) + .collect(), + ), + }) + .collect(), + ), + }; + jsii_types.insert(name.name.clone(), serde_json::to_value(manifest).unwrap()); + } + Statement::Struct { extends, members, name } => { + let manifest = jsii::ClassType { + abstract_: Some(false), + base: match extends.len() { + 0 => None, + 1 => Some(extends[0].name.clone()), + _ => panic!("multi inheritance not supported yet"), + }, + assembly: "wing".to_string(), + docs: None, + fqn: format!("wing.{}", name.name), + initializer: None, + interfaces: None, + kind: "class".to_string(), + location_in_module: None, + name: name.name.clone(), + namespace: None, + symbol_id: None, + methods: None, + properties: Some( + members + .iter() + .map(|member| jsii::Property { + abstract_: None, + docs: None, + immutable: Some(true), + location_in_module: None, + name: member.name.name.clone(), + optional: None, + overrides: None, + protected: None, + const_: None, + static_: None, + type_: json!({ + "primitive": "any" + }), + }) + .collect(), + ), + }; + jsii_types.insert(name.name.clone(), serde_json::to_value(manifest).unwrap()); + } + _ => {} + } + } + let assembly = jsii::Assembly { + name: "wing".to_string(), + version: "0.1.0".to_string(), + description: "description".to_string(), + homepage: "homepage".to_string(), + license: "MIT".to_string(), + repository: jsii::AssemblyRepository { + directory: None, + type_: "git".to_string(), + url: "#blank".to_string(), + }, + author: jsii::Person { + email: None, + name: "wingc".to_string(), + organization: None, + url: None, + roles: vec![], + }, + fingerprint: "fingerprint".to_string(), + jsii_version: "3.20.120".to_string(), + schema: "jsii/0.10.0".to_string(), + targets: None, + types: Some(jsii_types), + bin: None, + bundled: None, + contributors: None, + dependencies: None, + dependency_closure: None, + docs: None, + keywords: None, + metadata: None, + readme: None, + submodules: None, + }; + return serde_json::to_string_pretty(&assembly).unwrap(); +} diff --git a/libs/wingc/src/lib.rs b/libs/wingc/src/lib.rs index b1d400ee937..c16fae7786e 100644 --- a/libs/wingc/src/lib.rs +++ b/libs/wingc/src/lib.rs @@ -17,6 +17,7 @@ pub mod ast; pub mod capture; pub mod diagnostic; pub mod jsify; +pub mod jsiiify; pub mod parser; pub mod type_check; @@ -46,6 +47,10 @@ pub fn compile(source_file: &str, out_dir: Option<&str>) -> String { let intermediate_file = out_dir.join("intermediate.js"); fs::write(&intermediate_file, &intermediate_js).expect("Write intermediate JS to disk"); + let jsii_manifest = jsiiify::jsiiify(&scope, &types); + let jsii_manifest_file = out_dir.join(".jsii"); + fs::write(&jsii_manifest_file, &jsii_manifest).expect("Write JSII manifest to disk"); + return intermediate_js; } From 9c597fe1b6ad2ed7633dc50c9d4310295667ea42 Mon Sep 17 00:00:00 2001 From: Yoav Steinberg Date: Thu, 13 Oct 2022 11:07:51 +0300 Subject: [PATCH 26/31] cr --- libs/wingc/src/type_check/jsii_importer.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/wingc/src/type_check/jsii_importer.rs b/libs/wingc/src/type_check/jsii_importer.rs index 031fe128370..3d12e72c8a0 100644 --- a/libs/wingc/src/type_check/jsii_importer.rs +++ b/libs/wingc/src/type_check/jsii_importer.rs @@ -294,7 +294,9 @@ impl<'a> JsiiImporter<'a> { } else { None }; + // Verify we have a constructor for this calss + // TODO: Do we really require a constructor? Wing does but maybe we need some default behavior here if there isn't one. let jsii_initializer = jsii_class .initializer .as_ref() From b7ba6e5b63273be68069a831a73303d6d5829666 Mon Sep 17 00:00:00 2001 From: Sepehr Laal <5657848+3p3r@users.noreply.github.com> Date: Tue, 11 Oct 2022 12:20:36 -0700 Subject: [PATCH 27/31] feat(wingc): bring local wing files --- examples/invalid/circular-bring.w | 1 + examples/invalid/circular/mod.w | 1 + examples/simple/bring.w | 2 + examples/simple/modules/mod1.w | 2 + examples/simple/modules/mod2.w | 4 ++ examples/simple/modules/sub/mod.w | 1 + .../test/corpus/statements.txt | 15 +++++ libs/wingc/.vscode/launch.json | 19 ++++++ libs/wingc/src/ast.rs | 5 ++ libs/wingc/src/capture.rs | 6 ++ libs/wingc/src/jsify.rs | 9 +++ libs/wingc/src/lib.rs | 45 ++----------- libs/wingc/src/parser.rs | 41 +++++++++--- libs/wingc/src/parser/bring.rs | 67 +++++++++++++++++++ libs/wingc/src/type_check.rs | 3 + 15 files changed, 173 insertions(+), 48 deletions(-) create mode 100644 examples/invalid/circular-bring.w create mode 100644 examples/invalid/circular/mod.w create mode 100644 examples/simple/bring.w create mode 100644 examples/simple/modules/mod1.w create mode 100644 examples/simple/modules/mod2.w create mode 100644 examples/simple/modules/sub/mod.w create mode 100644 libs/wingc/.vscode/launch.json create mode 100644 libs/wingc/src/parser/bring.rs diff --git a/examples/invalid/circular-bring.w b/examples/invalid/circular-bring.w new file mode 100644 index 00000000000..bb663e04632 --- /dev/null +++ b/examples/invalid/circular-bring.w @@ -0,0 +1 @@ +bring "./circular/mod.w"; diff --git a/examples/invalid/circular/mod.w b/examples/invalid/circular/mod.w new file mode 100644 index 00000000000..3d5195e0dce --- /dev/null +++ b/examples/invalid/circular/mod.w @@ -0,0 +1 @@ +bring "../circular-bring.w"; diff --git a/examples/simple/bring.w b/examples/simple/bring.w new file mode 100644 index 00000000000..4cf3b12eb04 --- /dev/null +++ b/examples/simple/bring.w @@ -0,0 +1,2 @@ +bring "./modules/mod1.w"; +bring "./modules/mod2.w"; diff --git a/examples/simple/modules/mod1.w b/examples/simple/modules/mod1.w new file mode 100644 index 00000000000..fdfddbaadf6 --- /dev/null +++ b/examples/simple/modules/mod1.w @@ -0,0 +1,2 @@ +let x = 1; +let y = 2; diff --git a/examples/simple/modules/mod2.w b/examples/simple/modules/mod2.w new file mode 100644 index 00000000000..41ccf215dc0 --- /dev/null +++ b/examples/simple/modules/mod2.w @@ -0,0 +1,4 @@ +bring "./sub/mod.w"; + +let z = 3; +let q = 4; diff --git a/examples/simple/modules/sub/mod.w b/examples/simple/modules/sub/mod.w new file mode 100644 index 00000000000..e717e1f04e2 --- /dev/null +++ b/examples/simple/modules/sub/mod.w @@ -0,0 +1 @@ +let w = "what"; diff --git a/libs/tree-sitter-wing/test/corpus/statements.txt b/libs/tree-sitter-wing/test/corpus/statements.txt index c55e9c82cb3..ab934959b6b 100644 --- a/libs/tree-sitter-wing/test/corpus/statements.txt +++ b/libs/tree-sitter-wing/test/corpus/statements.txt @@ -241,3 +241,18 @@ struct Test { ) ) ) + +================================== +Bring statement +================================== + +bring cloud; +bring "./mod.w"; + +--- +(source + (short_import_statement + module_name: (identifier)) + (short_import_statement + module_name: (string)) +) diff --git a/libs/wingc/.vscode/launch.json b/libs/wingc/.vscode/launch.json new file mode 100644 index 00000000000..2807f414df2 --- /dev/null +++ b/libs/wingc/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "library 'wingc' debugger target", + "cargo": { + "args": ["test", "--no-run", "--lib", "--package=wingc"], + "filter": { + "name": "wingc", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/libs/wingc/src/ast.rs b/libs/wingc/src/ast.rs index 58e7b4f4711..d70e792782c 100644 --- a/libs/wingc/src/ast.rs +++ b/libs/wingc/src/ast.rs @@ -78,10 +78,15 @@ pub struct Constructor { #[derive(Debug)] pub enum Statement { + // TODO: merge these statements into a single one Use { module_name: Symbol, // Reference? identifier: Option, }, + Bring { + module_path: String, + statements: Scope, + }, VariableDef { var_name: Symbol, initial_value: Expr, diff --git a/libs/wingc/src/capture.rs b/libs/wingc/src/capture.rs index 1efacf7eb89..2fec8fe4683 100644 --- a/libs/wingc/src/capture.rs +++ b/libs/wingc/src/capture.rs @@ -250,6 +250,12 @@ fn scan_captures_in_scope(scope: &Scope) -> Vec { } => { todo!() } + Statement::Bring { + module_path: _, + statements: _, + } => { + todo!() + } Statement::Struct { name: _, extends: _, diff --git a/libs/wingc/src/jsify.rs b/libs/wingc/src/jsify.rs index 392522266a5..30238bcabfd 100644 --- a/libs/wingc/src/jsify.rs +++ b/libs/wingc/src/jsify.rs @@ -378,6 +378,15 @@ fn jsify_statement(statement: &Statement, out_dir: &PathBuf) -> String { .join("\n") ) } + Statement::Bring { + module_path, + statements, + } => format!( + "/* start bring module: {module} */\n{}\n/* end bring module: {module} */", + jsify_scope(statements), + module = module_path + ) + .to_string(), } } diff --git a/libs/wingc/src/lib.rs b/libs/wingc/src/lib.rs index 4363367f2d9..8dba8c34a75 100644 --- a/libs/wingc/src/lib.rs +++ b/libs/wingc/src/lib.rs @@ -1,8 +1,10 @@ +use crate::parser::bring; use ast::Scope; use diagnostic::Diagnostics; use crate::parser::Parser; use std::cell::RefCell; +use std::collections::HashSet; use std::fs; use std::path::PathBuf; @@ -18,45 +20,6 @@ pub mod jsify; pub mod parser; pub mod type_check; -pub fn parse(source_file: &str) -> Scope { - let language = tree_sitter_wing::language(); - let mut parser = tree_sitter::Parser::new(); - parser.set_language(language).unwrap(); - - let source = match fs::read(&source_file) { - Ok(source) => source, - Err(err) => { - panic!("Error reading source file: {}: {:?}", &source_file, err); - } - }; - - let tree = match parser.parse(&source[..], None) { - Some(tree) => tree, - None => { - println!("Failed parsing source file: {}", source_file); - std::process::exit(1); - } - }; - - let wing_parser = Parser { - source: &source[..], - source_name: source_file.to_string(), - diagnostics: RefCell::new(Diagnostics::new()), - }; - - let scope = wing_parser.wingit(&tree.root_node()); - - for diagnostic in wing_parser.diagnostics.borrow().iter() { - println!("{}", diagnostic); - } - - if wing_parser.diagnostics.borrow().len() > 0 { - std::process::exit(1); - } - - scope -} - pub fn type_check(scope: &mut Scope, types: &mut Types) { scope.set_env(TypeEnv::new(None, None, false, Flight::Pre)); let mut tc = TypeChecker::new(types); @@ -64,10 +27,12 @@ pub fn type_check(scope: &mut Scope, types: &mut Types) { } pub fn compile(source_file: &str, out_dir: Option<&str>) -> String { + // create a new hashmap to manage imports + let mut imports = HashSet::new(); // Create universal types collection (need to keep this alive during entire compilation) let mut types = Types::new(); // Build our AST - let mut scope = parse(source_file); + let mut scope = bring::bring(source_file, None, &mut imports).unwrap(); // Type check everything and build typed symbol environment type_check(&mut scope, &mut types); // Analyze inflight captures diff --git a/libs/wingc/src/parser.rs b/libs/wingc/src/parser.rs index 838aba5ad30..1e15f707a7e 100644 --- a/libs/wingc/src/parser.rs +++ b/libs/wingc/src/parser.rs @@ -1,5 +1,9 @@ +pub mod bring; + +use bring::bring; use std::cell::RefCell; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; +use std::path::PathBuf; use std::{str, vec}; use tree_sitter::Node; @@ -13,6 +17,7 @@ pub struct Parser<'a> { pub source: &'a [u8], pub source_name: String, pub diagnostics: RefCell, + pub imports: RefCell<&'a mut HashSet>, } impl Parser<'_> { @@ -108,14 +113,34 @@ impl Parser<'_> { fn build_statement(&self, statement_node: &Node) -> DiagnosticResult { match statement_node.kind() { - "short_import_statement" => Ok(Statement::Use { - module_name: self.node_symbol(&statement_node.child_by_field_name("module_name").unwrap())?, - identifier: if let Some(identifier) = statement_node.child_by_field_name("alias") { - Some(self.node_symbol(&identifier)?) + "short_import_statement" => { + let module_name_node = statement_node.child_by_field_name("module_name").unwrap(); + if module_name_node.kind() == "identifier" { + Ok(Statement::Use { + module_name: self.node_symbol(&module_name_node)?, + identifier: if let Some(identifier) = statement_node.child_by_field_name("alias") { + Some(self.node_symbol(&identifier)?) + } else { + None + }, + }) + } else if module_name_node.kind() == "string" { + let bring_target = self.node_text(&module_name_node); + let bring_target = &bring_target[1..bring_target.len() - 1]; + let context_dir = PathBuf::from(&self.source_name); + let context_dir = context_dir.parent().unwrap().to_str().unwrap(); + let brought = bring(bring_target, Some(context_dir), *self.imports.borrow_mut()); + Ok(Statement::Bring { + module_path: bring_target.to_string(), + statements: brought.unwrap_or(Scope { + statements: vec![], + env: None, + }), + }) } else { - None - }, - }), + panic!("Unexpected bring type {}", module_name_node.kind()) + } + } "variable_definition_statement" => { let type_ = if let Some(type_node) = statement_node.child_by_field_name("type") { Some(self.build_type(&type_node)?) diff --git a/libs/wingc/src/parser/bring.rs b/libs/wingc/src/parser/bring.rs new file mode 100644 index 00000000000..8f16147f262 --- /dev/null +++ b/libs/wingc/src/parser/bring.rs @@ -0,0 +1,67 @@ +use crate::Diagnostics; +use crate::Scope; + +use crate::parser::Parser; +use std::cell::RefCell; +use std::collections::HashSet; +use std::fs; +use std::path::PathBuf; + +use crate::ast::Flight; +use crate::capture::scan_captures; +use crate::type_check::type_env::TypeEnv; +use crate::type_check::{TypeChecker, Types}; + +pub fn bring(source_file: &str, context: Option<&str>, imports: &mut HashSet) -> Option { + // default context directory to the current directory + let context_dir = PathBuf::from(&context.unwrap_or(".")); + // turn "source_file" into a canonical path relative to context_dir + let source_file = context_dir.join(source_file).canonicalize().unwrap(); + let source_file = source_file.to_str().unwrap(); + + // check if "imports" already contains "source_file", panic if it does + if imports.contains(source_file) { + return None; + } + + let language = tree_sitter_wing::language(); + let mut parser = tree_sitter::Parser::new(); + parser.set_language(language).unwrap(); + + let source = match fs::read(&source_file) { + Ok(source) => source, + Err(err) => { + panic!("Error reading source file: {}: {:?}", &source_file, err); + } + }; + + let tree = match parser.parse(&source[..], None) { + Some(tree) => tree, + None => { + println!("Failed parsing source file: {}", source_file); + std::process::exit(1); + } + }; + + // keep track of imports to break recursive import cycles + imports.insert(source_file.to_string()); + + let wing_parser = Parser { + imports: RefCell::new(imports), + source: &source[..], + source_name: source_file.to_string(), + diagnostics: RefCell::new(Diagnostics::new()), + }; + + let scope = wing_parser.wingit(&tree.root_node()); + + for diagnostic in wing_parser.diagnostics.borrow().iter() { + println!("{}", diagnostic); + } + + if wing_parser.diagnostics.borrow().len() > 0 { + std::process::exit(1); + } + + Some(scope) +} diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index bc2960746a4..4ad005193e1 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -743,6 +743,9 @@ impl<'a> TypeChecker<'a> { let var_type = self.resolve_reference(variable, env); self.validate_type(exp_type, var_type, value); } + Statement::Bring { module_path: _, statements: _ } => { + unimplemented_type(); + } Statement::Use { module_name, identifier, From feb6579b4c1cf496191752354e3da1a395b9c48e Mon Sep 17 00:00:00 2001 From: Sepehr Laal <5657848+3p3r@users.noreply.github.com> Date: Wed, 12 Oct 2022 20:54:51 -0700 Subject: [PATCH 28/31] feat: hacked together a jsii-ify to experiment with the idea of compiling modules in-place --- libs/wingc/src/jsiiify.rs | 198 ++++++++++++++++++++++++++++++++++++++ libs/wingc/src/lib.rs | 5 + 2 files changed, 203 insertions(+) create mode 100644 libs/wingc/src/jsiiify.rs diff --git a/libs/wingc/src/jsiiify.rs b/libs/wingc/src/jsiiify.rs new file mode 100644 index 00000000000..cb79173092f --- /dev/null +++ b/libs/wingc/src/jsiiify.rs @@ -0,0 +1,198 @@ +use crate::{ast::*, type_check::Types}; +use serde_json::json; +use std::{collections::BTreeMap, fs}; +use wingii::jsii; + +/// generates a JSII manifest for the given scope +/// todo: JSII does not support functions and free variables, but... +/// we can support it by assigning them to a global "exports" class as statics +pub fn jsiiify(scope: &Scope, types: &Types) -> String { + let mut jsii_types: BTreeMap = BTreeMap::new(); + for statement in scope.statements.iter() { + match statement { + Statement::VariableDef { .. } => {} + Statement::FunctionDefinition(func_def) => match func_def.signature.flight { + Flight::In => {} + Flight::Pre => {} + }, + Statement::Class { + constructor, + is_resource, + members, + methods, + name, + parent, + } => { + assert!(!is_resource, "resources are not supported yet"); + let manifest = jsii::ClassType { + abstract_: Some(false), + assembly: "wingc".to_string(), + base: if parent.is_some() { + Some(parent.as_ref().unwrap().name.to_string()) + } else { + None + }, + docs: None, + fqn: format!("wingc.{}", name.name), + interfaces: None, + kind: "class".to_string(), + namespace: None, + location_in_module: None, + symbol_id: None, + name: name.name.to_string(), + initializer: Some(jsii::Callable { + docs: None, + location_in_module: None, + overrides: None, + protected: None, + variadic: None, + parameters: Some( + constructor + .parameters + .iter() + .map(|param| jsii::Parameter { + docs: None, + name: param.name.to_string(), + optional: None, + type_: json!({ "primitive": "any" }), + variadic: None, + }) + .collect(), + ), + }), + properties: Some( + members + .iter() + .map(|member| jsii::Property { + docs: None, + location_in_module: None, + name: member.name.name.to_string(), + optional: None, + protected: None, + abstract_: None, + const_: None, + immutable: None, + overrides: None, + static_: None, + type_: json!({ "primitive": "any" }), + }) + .collect(), + ), + methods: Some( + methods + .iter() + .map(|method| jsii::Method { + docs: None, + async_: None, + variadic: None, + location_in_module: None, + // remove everything after first space (?) + name: method.name.name.to_string(), + protected: None, + abstract_: None, + overrides: None, + static_: None, + returns: Some(jsii::OptionalValue { + optional: None, + type_: json!({ "primitive": "any" }), + }), + parameters: Some( + method + .parameters + .iter() + .map(|param| jsii::Parameter { + docs: None, + name: param.name.to_string(), + optional: None, + type_: json!({ "primitive": "any" }), + variadic: None, + }) + .collect(), + ), + }) + .collect(), + ), + }; + jsii_types.insert(name.name.clone(), serde_json::to_value(manifest).unwrap()); + } + Statement::Struct { extends, members, name } => { + let manifest = jsii::ClassType { + abstract_: Some(false), + base: match extends.len() { + 0 => None, + 1 => Some(extends[0].name.clone()), + _ => panic!("multi inheritance not supported yet"), + }, + assembly: "wing".to_string(), + docs: None, + fqn: format!("wing.{}", name.name), + initializer: None, + interfaces: None, + kind: "class".to_string(), + location_in_module: None, + name: name.name.clone(), + namespace: None, + symbol_id: None, + methods: None, + properties: Some( + members + .iter() + .map(|member| jsii::Property { + abstract_: None, + docs: None, + immutable: Some(true), + location_in_module: None, + name: member.name.name.clone(), + optional: None, + overrides: None, + protected: None, + const_: None, + static_: None, + type_: json!({ + "primitive": "any" + }), + }) + .collect(), + ), + }; + jsii_types.insert(name.name.clone(), serde_json::to_value(manifest).unwrap()); + } + _ => {} + } + } + let assembly = jsii::Assembly { + name: "wing".to_string(), + version: "0.1.0".to_string(), + description: "description".to_string(), + homepage: "homepage".to_string(), + license: "MIT".to_string(), + repository: jsii::AssemblyRepository { + directory: None, + type_: "git".to_string(), + url: "#blank".to_string(), + }, + author: jsii::Person { + email: None, + name: "wingc".to_string(), + organization: None, + url: None, + roles: vec![], + }, + fingerprint: "fingerprint".to_string(), + jsii_version: "3.20.120".to_string(), + schema: "jsii/0.10.0".to_string(), + targets: None, + types: Some(jsii_types), + bin: None, + bundled: None, + contributors: None, + dependencies: None, + dependency_closure: None, + docs: None, + keywords: None, + metadata: None, + readme: None, + submodules: None, + }; + return serde_json::to_string_pretty(&assembly).unwrap(); +} diff --git a/libs/wingc/src/lib.rs b/libs/wingc/src/lib.rs index 8dba8c34a75..c84d7ada71b 100644 --- a/libs/wingc/src/lib.rs +++ b/libs/wingc/src/lib.rs @@ -17,6 +17,7 @@ pub mod ast; pub mod capture; pub mod diagnostic; pub mod jsify; +pub mod jsiiify; pub mod parser; pub mod type_check; @@ -46,6 +47,10 @@ pub fn compile(source_file: &str, out_dir: Option<&str>) -> String { let intermediate_file = out_dir.join("intermediate.js"); fs::write(&intermediate_file, &intermediate_js).expect("Write intermediate JS to disk"); + let jsii_manifest = jsiiify::jsiiify(&scope, &types); + let jsii_manifest_file = out_dir.join(".jsii"); + fs::write(&jsii_manifest_file, &jsii_manifest).expect("Write JSII manifest to disk"); + return intermediate_js; } From abdb66b33b8ebf461f551f246c288a3339286723 Mon Sep 17 00:00:00 2001 From: Sepehr Laal <5657848+3p3r@users.noreply.github.com> Date: Thu, 13 Oct 2022 12:14:47 -0700 Subject: [PATCH 29/31] PR feedback addressed --- libs/wingc/Cargo.toml | 3 +++ libs/wingc/src/capture.rs | 4 ++-- libs/wingc/src/jsiiify.rs | 2 -- libs/wingc/src/parser/bring.rs | 1 - 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/wingc/Cargo.toml b/libs/wingc/Cargo.toml index 14c643bd44e..fd4bb8bf9ef 100644 --- a/libs/wingc/Cargo.toml +++ b/libs/wingc/Cargo.toml @@ -23,3 +23,6 @@ path = "src/bin.rs" [package.metadata] # Currently wasm-opt is not changing the size of the wasm file, so it's just adding unnecessary time to the build. wasm-opt = false + +[profile.bench] +debug = true diff --git a/libs/wingc/src/capture.rs b/libs/wingc/src/capture.rs index 2fec8fe4683..0da7ce7858d 100644 --- a/libs/wingc/src/capture.rs +++ b/libs/wingc/src/capture.rs @@ -252,9 +252,9 @@ fn scan_captures_in_scope(scope: &Scope) -> Vec { } Statement::Bring { module_path: _, - statements: _, + statements, } => { - todo!() + res.extend(scan_captures_in_scope(statements)); } Statement::Struct { name: _, diff --git a/libs/wingc/src/jsiiify.rs b/libs/wingc/src/jsiiify.rs index cb79173092f..6fc17647e19 100644 --- a/libs/wingc/src/jsiiify.rs +++ b/libs/wingc/src/jsiiify.rs @@ -4,8 +4,6 @@ use std::{collections::BTreeMap, fs}; use wingii::jsii; /// generates a JSII manifest for the given scope -/// todo: JSII does not support functions and free variables, but... -/// we can support it by assigning them to a global "exports" class as statics pub fn jsiiify(scope: &Scope, types: &Types) -> String { let mut jsii_types: BTreeMap = BTreeMap::new(); for statement in scope.statements.iter() { diff --git a/libs/wingc/src/parser/bring.rs b/libs/wingc/src/parser/bring.rs index 8f16147f262..6293e0aafb0 100644 --- a/libs/wingc/src/parser/bring.rs +++ b/libs/wingc/src/parser/bring.rs @@ -19,7 +19,6 @@ pub fn bring(source_file: &str, context: Option<&str>, imports: &mut HashSet Date: Thu, 13 Oct 2022 12:26:12 -0700 Subject: [PATCH 30/31] fixed builds --- libs/wingc/src/jsify.rs | 2 +- libs/wingc/src/jsiiify.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/wingc/src/jsify.rs b/libs/wingc/src/jsify.rs index 30238bcabfd..c15fde1c920 100644 --- a/libs/wingc/src/jsify.rs +++ b/libs/wingc/src/jsify.rs @@ -383,7 +383,7 @@ fn jsify_statement(statement: &Statement, out_dir: &PathBuf) -> String { statements, } => format!( "/* start bring module: {module} */\n{}\n/* end bring module: {module} */", - jsify_scope(statements), + jsify_scope(statements, &out_dir), module = module_path ) .to_string(), diff --git a/libs/wingc/src/jsiiify.rs b/libs/wingc/src/jsiiify.rs index 6fc17647e19..fda3dc301c9 100644 --- a/libs/wingc/src/jsiiify.rs +++ b/libs/wingc/src/jsiiify.rs @@ -26,7 +26,7 @@ pub fn jsiiify(scope: &Scope, types: &Types) -> String { abstract_: Some(false), assembly: "wingc".to_string(), base: if parent.is_some() { - Some(parent.as_ref().unwrap().name.to_string()) + None } else { None }, From 085c4a1e9132085af36dbcde78c145fb1d75f4e5 Mon Sep 17 00:00:00 2001 From: Sepehr Laal <5657848+3p3r@users.noreply.github.com> Date: Mon, 31 Oct 2022 10:44:11 -0700 Subject: [PATCH 31/31] fix lang server's build error with the new imports on parser instances --- apps/wing-language-server/src/prep.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/wing-language-server/src/prep.rs b/apps/wing-language-server/src/prep.rs index d0a915152ab..d8ae42351c5 100644 --- a/apps/wing-language-server/src/prep.rs +++ b/apps/wing-language-server/src/prep.rs @@ -1,4 +1,4 @@ -use std::cell::RefCell; +use std::{cell::RefCell, collections::HashSet}; use tree_sitter::Tree; use wingc::{diagnostic::Diagnostics, parser::Parser, type_check}; @@ -21,7 +21,9 @@ pub fn parse_text(source_file: &str, text: &[u8]) -> ParseResult { } }; + let mut imports = HashSet::new(); let wing_parser = Parser { + imports: RefCell::new(&mut imports), source: &text[..], source_name: source_file.to_string(), diagnostics: RefCell::new(Diagnostics::new()),