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 = ({});