diff --git a/examples/tests/valid/std_containers.w b/examples/tests/valid/std_containers.w index 9f77385c5a4..3cb1190b880 100644 --- a/examples/tests/valid/std_containers.w +++ b/examples/tests/valid/std_containers.w @@ -40,4 +40,26 @@ let immutMap = mutMap.copy(); assert(sMap.get("one") == 1); assert(sMap.size() == 2); assert(immutMap.size() == 3); -assert(nestedMap.get("a").get("b").get("c") == "hello"); \ No newline at end of file +assert(nestedMap.get("a").get("b").get("c") == "hello"); + +class Animal {} +class Cat extends Animal {} +class Dog extends Animal {} + +let heterogeneousArray = Array[ + new Cat() as "C1", + new Dog() as "D1", +]; +let heterogeneousDoubleArray = Array>[ + [new Cat() as "C2"], + Array[new Cat() as "C3", new Dog() as "D2"], + [new Animal() as "A1"], +]; +let heterogeneousSet = Set{ + new Cat() as "C4", + new Dog() as "D3", +}; +let heterogeneousMap = Map{ + "cat" => new Cat() as "C5", + "dog" => new Dog() as "D4", +}; diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index 4264bb328c2..10fcd015c75 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -2109,43 +2109,114 @@ impl<'a> TypeChecker<'a> { } ExprKind::ArrayLiteral { type_, items } => { // Infer type based on either the explicit type or the value in one of the items - let container_type = if let Some(type_) = type_ { - self.resolve_type_annotation(type_, env) + let (container_type, mut element_type) = if let Some(type_) = type_ { + let container_type = self.resolve_type_annotation(type_, env); + let element_type = match *container_type { + Type::Array(t) | Type::MutArray(t) => t, + _ => { + self.spanned_error( + &type_.span, + format!("Expected \"Array\" or \"MutArray\", found \"{container_type}\""), + ); + self.types.error() + } + }; + (container_type, element_type) + } else if self.in_json > 0 { + let json_data = JsonData { + expression_id: exp.id, + kind: JsonDataKind::List(vec![]), + }; + let inner_type = self.types.add_type(Type::Json(Some(json_data))); + (self.types.add_type(Type::Array(inner_type)), inner_type) } else { - if self.in_json > 0 { - let json_data = JsonData { - expression_id: exp.id, - kind: JsonDataKind::List(vec![]), - }; - let inner_type = self.types.add_type(Type::Json(Some(json_data))); - self.types.add_type(Type::Array(inner_type)) - } else { - let inner_type = self.types.make_inference(); - self.types.add_type(Type::Array(inner_type)) - } - }; - - let mut element_type = match *container_type { - Type::Array(t) => t, - Type::MutArray(t) => t, - _ => { - self.spanned_error(exp, format!("Expected \"Array\" type, found \"{}\"", container_type)); - self.types.error() - } + let inner_type = self.types.make_inference(); + (self.types.add_type(Type::Array(inner_type)), inner_type) }; // Verify all types are the same as the inferred type - for v in items.iter() { - let (t, _) = self.type_check_exp(v, env); + for item in items { + let (t, _) = self.type_check_exp(item, env); + + // Augment the json list data with the new element type if let Type::Json(Some(JsonData { ref mut kind, .. })) = &mut *element_type { if let JsonDataKind::List(ref mut json_list) = kind { json_list.push(SpannedTypeInfo { type_: t, - span: v.span(), + span: item.span(), }); } } - element_type = self.check_json_serializable_or_validate_type(t, element_type, v); + + if self.in_json == 0 { + // If we're not in a Json literal, validate the type of each element + self.validate_type(t, element_type, item); + element_type = self.types.maybe_unwrap_inference(element_type); + } else if self.is_in_mut_json && !t.is_json_legal_value() { + // if we're in a MutJson literal, we only need to check that each field is legal json + self.spanned_error( + item, + format!("Expected a valid Json value (https://www.json.org/json-en.html), but got \"{t}\""), + ); + } + } + + (container_type, env.phase) + } + ExprKind::MapLiteral { fields, type_ } => { + // Infer type based on either the explicit type or the value in one of the fields + let (container_type, mut element_type) = if let Some(type_) = type_ { + let container_type = self.resolve_type_annotation(type_, env); + let element_type = match *container_type { + Type::Map(t) | Type::MutMap(t) => t, + _ => { + self.spanned_error( + &type_.span, + format!("Expected \"Map\" or \"MutMap\", found \"{container_type}\""), + ); + self.types.error() + } + }; + (container_type, element_type) + } else { + let inner_type = self.types.make_inference(); + (self.types.add_type(Type::Map(inner_type)), inner_type) + }; + + // Verify all types are the same as the inferred type + for field in fields.values() { + let (t, _) = self.type_check_exp(field, env); + self.validate_type(t, element_type, field); + element_type = self.types.maybe_unwrap_inference(element_type); + } + + (container_type, env.phase) + } + ExprKind::SetLiteral { type_, items } => { + // Infer type based on either the explicit type or the value in one of the items + let (container_type, mut element_type) = if let Some(type_) = type_ { + let container_type = self.resolve_type_annotation(type_, env); + let element_type = match *container_type { + Type::Set(t) | Type::MutSet(t) => t, + _ => { + self.spanned_error( + &type_.span, + format!("Expected \"Set\" or \"MutSet\", found \"{container_type}\""), + ); + self.types.error() + } + }; + (container_type, element_type) + } else { + let inner_type = self.types.make_inference(); + (self.types.add_type(Type::Set(inner_type)), inner_type) + }; + + // Verify all types are the same as the inferred type + for item in items { + let (t, _) = self.type_check_exp(item, env); + self.validate_type(t, element_type, item); + element_type = self.types.maybe_unwrap_inference(element_type); } (container_type, env.phase) @@ -2285,58 +2356,6 @@ impl<'a> TypeChecker<'a> { env.phase, ) } - ExprKind::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_annotation(type_, env) - } else { - let inner_type = self.types.make_inference(); - self.types.add_type(Type::Map(inner_type)) - }; - - let mut value_type = match *container_type { - Type::Map(t) => t, - Type::MutMap(t) => t, - _ => { - self.spanned_error(exp, format!("Expected \"Map\" type, found \"{}\"", container_type)); - self.types.error() - } - }; - - // Verify all types are the same as the inferred type - for (_, v) in fields.iter() { - let (t, _) = self.type_check_exp(v, env); - value_type = self.validate_type(t, value_type, v); - } - - (container_type, env.phase) - } - ExprKind::SetLiteral { type_, items } => { - // Infer type based on either the explicit type or the value in one of the items - let container_type = if let Some(type_) = type_ { - self.resolve_type_annotation(type_, env) - } else { - let inferred = self.types.make_inference(); - self.types.add_type(Type::Set(inferred)) - }; - - let mut element_type = match *container_type { - Type::Set(t) => t, - Type::MutSet(t) => t, - _ => { - self.spanned_error(exp, format!("Expected \"Set\" type, found \"{}\"", container_type)); - self.types.error() - } - }; - - // Verify all types are the same as the inferred type - for v in items.iter() { - let (t, _) = self.type_check_exp(v, env); - element_type = self.validate_type(t, element_type, v); - } - - (container_type, env.phase) - } ExprKind::FunctionClosure(func_def) => self.type_check_closure(func_def, env), ExprKind::CompilerDebugPanic => { // Handle the debug panic expression (during type-checking) @@ -2565,35 +2584,6 @@ impl<'a> TypeChecker<'a> { } } - fn check_json_serializable_or_validate_type( - &mut self, - actual_type: TypeRef, - expected_type: TypeRef, - exp: &Expr, - ) -> TypeRef { - // Skip validate if in Json - if self.in_json == 0 { - return self.validate_type(actual_type, expected_type, exp); - } - - if self.is_in_mut_json && !actual_type.is_json_legal_value() { - self.spanned_error( - exp, - format!( - "Expected a valid Json value (https://www.json.org/json-en.html), but got \"{}\"", - actual_type - ), - ); - return self.types.error(); - } - - if expected_type.is_json() { - expected_type - } else { - actual_type - } - } - /// Validate that the given type is a subtype (or same) as the expected type. If not, add an error /// to the diagnostics. /// diff --git a/tools/hangar/__snapshots__/invalid.ts.snap b/tools/hangar/__snapshots__/invalid.ts.snap index 00bd529c818..7676d6d9de0 100644 --- a/tools/hangar/__snapshots__/invalid.ts.snap +++ b/tools/hangar/__snapshots__/invalid.ts.snap @@ -488,11 +488,11 @@ error: Expected type to be \\"num\\", but got \\"str\\" instead | ^^^ Expected type to be \\"num\\", but got \\"str\\" instead -error: Expected \\"Set\\" type, found \\"Array\\" +error: Expected \\"Set\\" or \\"MutSet\\", found \\"Array\\" --> ../../../examples/tests/invalid/container_types.w:3:12 | 3 | let arr2 = Array {1, 2, 3}; - | ^^^^^^^^^^^^^^^^^^^^ Expected \\"Set\\" type, found \\"Array\\" + | ^^^^^^^^^^ Expected \\"Set\\" or \\"MutSet\\", found \\"Array\\" error: Expected type to be \\"Array\\", but got \\"Array\\" instead @@ -551,18 +551,11 @@ error: Expected type to be \\"num\\", but got \\"str\\" instead | ^^^ Expected type to be \\"num\\", but got \\"str\\" instead -error: Expected \\"Array\\" type, found \\"Set\\" +error: Expected \\"Array\\" or \\"MutArray\\", found \\"Set\\" --> ../../../examples/tests/invalid/container_types.w:21:10 | 21 | let s2 = Set [1, \\"2\\", 3]; - | ^^^^^^^^^^^^^^^^^^^^ Expected \\"Array\\" type, found \\"Set\\" - - -error: Expected type to be \\"num\\", but got \\"str\\" instead - --> ../../../examples/tests/invalid/container_types.w:21:23 - | -21 | let s2 = Set [1, \\"2\\", 3]; - | ^^^ Expected type to be \\"num\\", but got \\"str\\" instead + | ^^^^^^^^ Expected \\"Array\\" or \\"MutArray\\", found \\"Set\\" error: Expected type to be \\"num\\", but got \\"str\\" instead @@ -1537,11 +1530,11 @@ exports[`mut_container_types.w 1`] = ` | ^^^ Expected type to be \\"num\\", but got \\"str\\" instead -error: Expected \\"Set\\" type, found \\"MutArray\\" +error: Expected \\"Set\\" or \\"MutSet\\", found \\"MutArray\\" --> ../../../examples/tests/invalid/mut_container_types.w:3:12 | 3 | let arr2 = MutArray{1, 2, 3}; - | ^^^^^^^^^^^^^^^^^^^^^^ Expected \\"Set\\" type, found \\"MutArray\\" + | ^^^^^^^^^^^^^ Expected \\"Set\\" or \\"MutSet\\", found \\"MutArray\\" error: Expected type to be \\"MutArray\\", but got \\"Array\\" instead @@ -1572,18 +1565,11 @@ error: Expected type to be \\"num\\", but got \\"str\\" instead | ^^^ Expected type to be \\"num\\", but got \\"str\\" instead -error: Expected \\"Array\\" type, found \\"MutSet\\" +error: Expected \\"Array\\" or \\"MutArray\\", found \\"MutSet\\" --> ../../../examples/tests/invalid/mut_container_types.w:11:10 | 11 | let s2 = MutSet[1, \\"2\\", 3]; - | ^^^^^^^^^^^^^^^^^^^^^^ Expected \\"Array\\" type, found \\"MutSet\\" - - -error: Expected type to be \\"num\\", but got \\"str\\" instead - --> ../../../examples/tests/invalid/mut_container_types.w:11:25 - | -11 | let s2 = MutSet[1, \\"2\\", 3]; - | ^^^ Expected type to be \\"num\\", but got \\"str\\" instead + | ^^^^^^^^^^^ Expected \\"Array\\" or \\"MutArray\\", found \\"MutSet\\" error: Expected type to be \\"MutSet\\", but got \\"Set\\" instead @@ -1614,11 +1600,11 @@ error: Expected type to be \\"num\\", but got \\"str\\" instead | ^^^^^^^ Expected type to be \\"num\\", but got \\"str\\" instead -error: Expected \\"Array\\" type, found \\"MutMap\\" +error: Expected \\"Array\\" or \\"MutArray\\", found \\"MutMap\\" --> ../../../examples/tests/invalid/mut_container_types.w:20:10 | 20 | let m2 = MutMap[\\"hello\\", \\"world\\"]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Expected \\"Array\\" type, found \\"MutMap\\" + | ^^^^^^^^^^^ Expected \\"Array\\" or \\"MutArray\\", found \\"MutMap\\" error: Expected type to be \\"MutMap\\", but got \\"Map\\" instead diff --git a/tools/hangar/__snapshots__/test_corpus/valid/std_containers.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/std_containers.w_compile_tf-aws.md index eae296806f9..c8334c978c4 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/std_containers.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/std_containers.w_compile_tf-aws.md @@ -1,5 +1,43 @@ # [std_containers.w](../../../../../examples/tests/valid/std_containers.w) | compile | tf-aws +## inflight.Animal-1.js +```js +module.exports = function({ }) { + class Animal { + constructor({ }) { + } + } + return Animal; +} + +``` + +## inflight.Cat-1.js +```js +module.exports = function({ $Animal }) { + class Cat extends $Animal { + constructor({ }) { + super({ }); + } + } + return Cat; +} + +``` + +## inflight.Dog-1.js +```js +module.exports = function({ $Animal }) { + class Dog extends $Animal { + constructor({ }) { + super({ }); + } + } + return Dog; +} + +``` + ## main.tf.json ```json { @@ -41,6 +79,77 @@ const std = $stdlib.std; class $Root extends $stdlib.std.Resource { constructor(scope, id) { super(scope, id); + class Animal extends $stdlib.std.Resource { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("$inflight_init"); + } + static _toInflightType(context) { + return $stdlib.core.NodeJsCode.fromInline(` + require("./inflight.Animal-1.js")({ + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const AnimalClient = ${Animal._toInflightType(this).text}; + const client = new AnimalClient({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + } + class Cat extends Animal { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("$inflight_init"); + } + static _toInflightType(context) { + return $stdlib.core.NodeJsCode.fromInline(` + require("./inflight.Cat-1.js")({ + $Animal: ${context._lift(Animal)}, + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const CatClient = ${Cat._toInflightType(this).text}; + const client = new CatClient({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + } + class Dog extends Animal { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("$inflight_init"); + } + static _toInflightType(context) { + return $stdlib.core.NodeJsCode.fromInline(` + require("./inflight.Dog-1.js")({ + $Animal: ${context._lift(Animal)}, + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const DogClient = ${Dog._toInflightType(this).text}; + const client = new DogClient({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + } const sArray = ["one", "two"]; const mutArray = [...(sArray)]; (mutArray.push("three")); @@ -81,6 +190,10 @@ class $Root extends $stdlib.std.Resource { {((cond) => {if (!cond) throw new Error("assertion failed: sMap.size() == 2")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(Object.keys(sMap).length,2)))}; {((cond) => {if (!cond) throw new Error("assertion failed: immutMap.size() == 3")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(Object.keys(immutMap).length,3)))}; {((cond) => {if (!cond) throw new Error("assertion failed: nestedMap.get(\"a\").get(\"b\").get(\"c\") == \"hello\"")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })((((nestedMap)["a"])["b"])["c"],"hello")))}; + const heterogeneousArray = [new Cat(this,"C1"), new Dog(this,"D1")]; + const heterogeneousDoubleArray = [[new Cat(this,"C2")], [new Cat(this,"C3"), new Dog(this,"D2")], [new Animal(this,"A1")]]; + const heterogeneousSet = new Set([new Cat(this,"C4"), new Dog(this,"D3")]); + const heterogeneousMap = ({"cat": new Cat(this,"C5"),"dog": new Dog(this,"D4")}); } } const $App = $stdlib.core.App.for(process.env.WING_TARGET);