From 6338ad7bed3136c6d5e73890a7c1235228d9fa4e Mon Sep 17 00:00:00 2001 From: Hasan <45375125+hasanaburayyan@users.noreply.github.com> Date: Tue, 8 Aug 2023 17:24:14 -0400 Subject: [PATCH] fix(compiler): could not hydrate optionals (#3741) There are several open issues in progress that need to be able to define generic optionals. Such as: - https://github.com/winglang/wing/issues/1868 - https://github.com/winglang/wing/issues/3575 - https://github.com/winglang/wing/issues/3653 I only added `tryAt` method for Array since the actual work is defined in another issue, but I needed an example to write some test cases with. Since there are several efforts that need this change I opted to not wait and include it in one of those PRs but just add the support here so other efforts can rebase. ## Checklist - [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted) - [x] Description explains motivation and solution - [x] Tests added (always) - [x] Docs updated (only required for features) - [x] Added `pr/e2e-full` label if this feature requires end-to-end testing *By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*. --- .../02-std/api-reference.md | 17 +++++ examples/tests/invalid/container_types.w | 4 ++ examples/tests/valid/container_types.w | 10 +++ libs/wingc/src/type_check.rs | 7 +- libs/wingsdk/src/std/array.ts | 13 ++++ tools/hangar/__snapshots__/invalid.ts.snap | 71 ++++++++++--------- .../valid/container_types.w_compile_tf-aws.md | 17 +++++ 7 files changed, 106 insertions(+), 33 deletions(-) diff --git a/docs/docs/04-standard-library/02-std/api-reference.md b/docs/docs/04-standard-library/02-std/api-reference.md index d935716ffcb..068d1bb1993 100644 --- a/docs/docs/04-standard-library/02-std/api-reference.md +++ b/docs/docs/04-standard-library/02-std/api-reference.md @@ -29,6 +29,7 @@ Immutable Array. | indexOf | Returns the index of the first occurrence of searchElement found. | | join | Returns a new string containing the concatenated values in this array, separated by commas or a specified separator string. | | lastIndexOf | Returns the index of the last occurrence of searchElement found. | +| tryAt | Get the value at the given index, returning nil if the index is out of bounds. | --- @@ -137,6 +138,22 @@ to search for. --- +##### `tryAt` + +```wing +tryAt(index: num): +``` + +Get the value at the given index, returning nil if the index is out of bounds. + +###### `index`Required + +- *Type:* num + +index of the value to get. + +--- + #### Properties diff --git a/examples/tests/invalid/container_types.w b/examples/tests/invalid/container_types.w index 0d023e40f46..fa1050c24b8 100644 --- a/examples/tests/invalid/container_types.w +++ b/examples/tests/invalid/container_types.w @@ -5,6 +5,10 @@ let arr3 = Array["hello"]; let arr4: Array = arr3; arr1.someRandomMethod(); +let arr5: Array = [1, 2, 3]; +let val: num = arr5.tryAt(0); +// ^^^^^^^^^^^^^ Expected type to be "num", but got "num?" instead + //Map tests let m1: Map = {"a" => 1, "b" => "2", "c" => 3}; let m2: Map = ["a" => 1, "b" => "2", "c" => 3]; diff --git a/examples/tests/valid/container_types.w b/examples/tests/valid/container_types.w index c862ed670e2..426f4e71f87 100644 --- a/examples/tests/valid/container_types.w +++ b/examples/tests/valid/container_types.w @@ -31,6 +31,16 @@ let arr7: Array = arr4; assert(arr7.length == 3); assert(arr7.at(1) == 2); +if let val = emptyArray.tryAt(0) { + assert(false); // Should not happen +} + +if let val = arr1.tryAt(0) { + assert(val == 1); +} else { + assert(false); // Should not happen +} + //Map tests let emptyMap = Map{}; assert(emptyMap.size() == 0); diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index 33b32c42756..8284553e287 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -3839,7 +3839,6 @@ impl<'a> TypeChecker<'a> { // Replace type params in function signatures if let Some(sig) = v.as_function_sig() { let new_return_type = self.get_concrete_type_for_generic(sig.return_type, &types_map); - let new_this_type = if let Some(this_type) = sig.this_type { Some(self.get_concrete_type_for_generic(this_type, &types_map)) } else { @@ -3932,6 +3931,11 @@ impl<'a> TypeChecker<'a> { } else { // Handle generic return types // TODO: If a generic class has a method that returns another generic, it must be a builtin + if let Type::Optional(t) = *type_to_maybe_replace { + let concrete_t = self.get_concrete_type_for_generic(t, types_map); + return self.types.add_type(Type::Optional(concrete_t)); + } + if let Some(c) = type_to_maybe_replace.as_class() { if let Some(type_parameters) = &c.type_parameters { // For now all our generics only have a single type parameter so use the first type parameter as our "T1" @@ -3942,6 +3946,7 @@ impl<'a> TypeChecker<'a> { .map(|(_, n)| n) .expect("generic must have a type parameter"); let fqn = format!("{}.{}", WINGSDK_STD_MODULE, c.name.name); + return match fqn.as_str() { WINGSDK_MUT_ARRAY => self.types.add_type(Type::MutArray(t1_replacement)), WINGSDK_ARRAY => self.types.add_type(Type::Array(t1_replacement)), diff --git a/libs/wingsdk/src/std/array.ts b/libs/wingsdk/src/std/array.ts index bfb1d7bd035..891ef6b08f3 100644 --- a/libs/wingsdk/src/std/array.ts +++ b/libs/wingsdk/src/std/array.ts @@ -38,6 +38,19 @@ export class Array { throw new Error("Abstract"); } + /** + * Get the value at the given index, returning nil if the index is out of bounds. + * + * @macro ($self$.at($args$)) + * + * @param index index of the value to get + * @returns the value at the given index, or undefined if the index is out of bounds + */ + public tryAt(index: number): T1 | undefined { + index; + throw new Error("Macro"); + } + /** * Merge arr to the end of this array * @param arr array to merge diff --git a/tools/hangar/__snapshots__/invalid.ts.snap b/tools/hangar/__snapshots__/invalid.ts.snap index 79a4c56d607..7134e563639 100644 --- a/tools/hangar/__snapshots__/invalid.ts.snap +++ b/tools/hangar/__snapshots__/invalid.ts.snap @@ -461,23 +461,23 @@ Duration " exports[`container_types.w 1`] = ` "error: Unknown parser error - --> ../../../examples/tests/invalid/container_types.w:10:25 + --> ../../../examples/tests/invalid/container_types.w:14:25 | -10 | let m2: Map = [\\"a\\" => 1, \\"b\\" => \\"2\\", \\"c\\" => 3]; +14 | let m2: Map = [\\"a\\" => 1, \\"b\\" => \\"2\\", \\"c\\" => 3]; | ^^^^ Unknown parser error error: Unexpected 'string' - --> ../../../examples/tests/invalid/container_types.w:10:31 + --> ../../../examples/tests/invalid/container_types.w:14:31 | -10 | let m2: Map = [\\"a\\" => 1, \\"b\\" => \\"2\\", \\"c\\" => 3]; +14 | let m2: Map = [\\"a\\" => 1, \\"b\\" => \\"2\\", \\"c\\" => 3]; | ^^^ Unexpected 'string' error: Unknown parser error - --> ../../../examples/tests/invalid/container_types.w:10:47 + --> ../../../examples/tests/invalid/container_types.w:14:47 | -10 | let m2: Map = [\\"a\\" => 1, \\"b\\" => \\"2\\", \\"c\\" => 3]; +14 | let m2: Map = [\\"a\\" => 1, \\"b\\" => \\"2\\", \\"c\\" => 3]; | ^^^^ Unknown parser error @@ -509,87 +509,94 @@ error: Unknown symbol \\"someRandomMethod\\" | ^^^^^^^^^^^^^^^^ Unknown symbol \\"someRandomMethod\\" -error: Expected type to be \\"num\\", but got \\"str\\" instead - --> ../../../examples/tests/invalid/container_types.w:9:38 +error: Expected type to be \\"num\\", but got \\"num?\\" instead + --> ../../../examples/tests/invalid/container_types.w:9:16 | -9 | let m1: Map = {\\"a\\" => 1, \\"b\\" => \\"2\\", \\"c\\" => 3}; - | ^^^ Expected type to be \\"num\\", but got \\"str\\" instead +9 | let val: num = arr5.tryAt(0); + | ^^^^^^^^^^^^^ Expected type to be \\"num\\", but got \\"num?\\" instead + + +error: Expected type to be \\"num\\", but got \\"str\\" instead + --> ../../../examples/tests/invalid/container_types.w:13:38 + | +13 | let m1: Map = {\\"a\\" => 1, \\"b\\" => \\"2\\", \\"c\\" => 3}; + | ^^^ Expected type to be \\"num\\", but got \\"str\\" instead error: Expected type to be \\"Map\\", but got \\"Array\\" instead - --> ../../../examples/tests/invalid/container_types.w:10:20 + --> ../../../examples/tests/invalid/container_types.w:14:20 | -10 | let m2: Map = [\\"a\\" => 1, \\"b\\" => \\"2\\", \\"c\\" => 3]; +14 | let m2: Map = [\\"a\\" => 1, \\"b\\" => \\"2\\", \\"c\\" => 3]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Expected type to be \\"Map\\", but got \\"Array\\" instead error: Expected type to be \\"Map\\", but got \\"Map\\" instead - --> ../../../examples/tests/invalid/container_types.w:12:20 + --> ../../../examples/tests/invalid/container_types.w:16:20 | -12 | let m4: Map = m3; +16 | let m4: Map = m3; | ^^ Expected type to be \\"Map\\", but got \\"Map\\" instead error: Unknown symbol \\"someRandomMethod\\" - --> ../../../examples/tests/invalid/container_types.w:13:4 + --> ../../../examples/tests/invalid/container_types.w:17:4 | -13 | m1.someRandomMethod(); +17 | m1.someRandomMethod(); | ^^^^^^^^^^^^^^^^ Unknown symbol \\"someRandomMethod\\" error: Expected type to be \\"num\\", but got \\"str\\" instead - --> ../../../examples/tests/invalid/container_types.w:16:24 + --> ../../../examples/tests/invalid/container_types.w:20:24 | -16 | let s1: Set = {1, \\"2\\", 3}; +20 | let s1: Set = {1, \\"2\\", 3}; | ^^^ Expected type to be \\"num\\", but got \\"str\\" instead error: Expected \\"Array\\" type, found \\"Set\\" - --> ../../../examples/tests/invalid/container_types.w:17:10 + --> ../../../examples/tests/invalid/container_types.w:21:10 | -17 | let s2 = Set [1, \\"2\\", 3]; +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:17:23 + --> ../../../examples/tests/invalid/container_types.w:21:23 | -17 | let s2 = Set [1, \\"2\\", 3]; +21 | let s2 = Set [1, \\"2\\", 3]; | ^^^ Expected type to be \\"num\\", but got \\"str\\" instead error: Expected type to be \\"num\\", but got \\"str\\" instead - --> ../../../examples/tests/invalid/container_types.w:18:24 + --> ../../../examples/tests/invalid/container_types.w:22:24 | -18 | let s3: Set = [1, \\"2\\", 3]; +22 | let s3: Set = [1, \\"2\\", 3]; | ^^^ Expected type to be \\"num\\", but got \\"str\\" instead error: Expected type to be \\"Set\\", but got \\"Array\\" instead - --> ../../../examples/tests/invalid/container_types.w:18:20 + --> ../../../examples/tests/invalid/container_types.w:22:20 | -18 | let s3: Set = [1, \\"2\\", 3]; +22 | let s3: Set = [1, \\"2\\", 3]; | ^^^^^^^^^^^ Expected type to be \\"Set\\", but got \\"Array\\" instead error: Expected type to be \\"Set\\", but got \\"Set\\" instead - --> ../../../examples/tests/invalid/container_types.w:20:20 + --> ../../../examples/tests/invalid/container_types.w:24:20 | -20 | let s5: Set = s4; +24 | let s5: Set = s4; | ^^ Expected type to be \\"Set\\", but got \\"Set\\" instead error: Unknown symbol \\"someRandomMethod\\" - --> ../../../examples/tests/invalid/container_types.w:21:4 + --> ../../../examples/tests/invalid/container_types.w:25:4 | -21 | s1.someRandomMethod(); +25 | s1.someRandomMethod(); | ^^^^^^^^^^^^^^^^ Unknown symbol \\"someRandomMethod\\" error: Expected type to be \\"Array\\", but got \\"MutArray\\" instead - --> ../../../examples/tests/invalid/container_types.w:23:21 + --> ../../../examples/tests/invalid/container_types.w:27:21 | -23 | let a: Array = MutArray[]; +27 | let a: Array = MutArray[]; | ^^^^^^^^^^^^^^^ Expected type to be \\"Array\\", but got \\"MutArray\\" instead diff --git a/tools/hangar/__snapshots__/test_corpus/valid/container_types.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/container_types.w_compile_tf-aws.md index 27a8b513e36..178ad6c5c08 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/container_types.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/container_types.w_compile_tf-aws.md @@ -195,6 +195,23 @@ class $Root extends $stdlib.std.Resource { const arr7 = arr4; {((cond) => {if (!cond) throw new Error("assertion failed: arr7.length == 3")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(arr7.length,3)))}; {((cond) => {if (!cond) throw new Error("assertion failed: arr7.at(1) == 2")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })((arr7.at(1)),2)))}; + { + const $IF_LET_VALUE = (emptyArray.at(0)); + if ($IF_LET_VALUE != undefined) { + const val = $IF_LET_VALUE; + {((cond) => {if (!cond) throw new Error("assertion failed: false")})(false)}; + } + } + { + const $IF_LET_VALUE = (arr1.at(0)); + if ($IF_LET_VALUE != undefined) { + const val = $IF_LET_VALUE; + {((cond) => {if (!cond) throw new Error("assertion failed: val == 1")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(val,1)))}; + } + else { + {((cond) => {if (!cond) throw new Error("assertion failed: false")})(false)}; + } + } const emptyMap = ({}); {((cond) => {if (!cond) throw new Error("assertion failed: emptyMap.size() == 0")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(Object.keys(emptyMap).length,0)))}; const emptyMap2 = ({});