From 6b016b1e915a4571d84767d9ba99195d6b9ecee7 Mon Sep 17 00:00:00 2001 From: Chris Rybicki Date: Mon, 16 Sep 2024 12:51:54 -0400 Subject: [PATCH] feat: `bytes` type (#7122) Co-authored-by: wingbot <109207340+monadabot@users.noreply.github.com> Co-authored-by: monada-bot[bot] --- .../04-standard-library/fs/api-reference.md | 78 +++++ docs/api/04-standard-library/std/bytes.md | 282 ++++++++++++++++++ docs/api/05-language-reference.md | 6 +- packages/@winglang/sdk/src/fs/fs.ts | 40 +++ packages/@winglang/sdk/src/helpers.ts | 22 +- packages/@winglang/sdk/src/std/bytes.ts | 197 ++++++++++++ packages/@winglang/sdk/src/std/index.ts | 1 + .../@winglang/tree-sitter-wing/grammar.js | 3 +- .../tree-sitter-wing/src/grammar.json | 4 + packages/@winglang/wingc/src/ast.rs | 2 + packages/@winglang/wingc/src/docs.rs | 2 + .../wingc/src/dtsify/extern_dtsify.rs | 14 +- packages/@winglang/wingc/src/dtsify/mod.rs | 1 + packages/@winglang/wingc/src/fold.rs | 1 + packages/@winglang/wingc/src/lib.rs | 1 + .../@winglang/wingc/src/lsp/completions.rs | 5 +- .../completions/call_struct_expansion.snap | 3 + .../call_struct_expansion_partial.snap | 3 + .../src/lsp/snapshots/completions/empty.snap | 3 + .../hide_parent_symbols_defined_later.snap | 5 +- .../only_show_symbols_in_scope.snap | 3 + .../struct_arg_expansion_partial.snap | 3 + .../completions/struct_definition_types.snap | 5 +- .../completions/struct_literal_value.snap | 3 + .../completions/struct_show_values.snap | 3 + .../type_annotation_shows_struct.snap | 5 +- .../snapshots/completions/type_parameter.snap | 5 +- .../@winglang/wingc/src/lsp/symbol_locator.rs | 1 + packages/@winglang/wingc/src/parser.rs | 5 + packages/@winglang/wingc/src/type_check.rs | 35 ++- .../wingc/src/type_check/inference_visitor.rs | 1 + .../wingc/src/type_check/jsii_importer.rs | 6 +- packages/@winglang/wingc/src/visit.rs | 1 + packages/@winglang/wingc/src/visit_types.rs | 1 + .../vscode-wing/syntaxes/wing.tmLanguage.json | 2 +- tests/sdk_tests/std/bytes.test.w | 175 +++++++++++ .../std/bytes.test.w_compile_tf-aws.md | 20 ++ .../sdk_tests/std/bytes.test.w_test_sim.md | 12 + 38 files changed, 942 insertions(+), 17 deletions(-) create mode 100644 docs/api/04-standard-library/std/bytes.md create mode 100644 packages/@winglang/sdk/src/std/bytes.ts create mode 100644 tests/sdk_tests/std/bytes.test.w create mode 100644 tools/hangar/__snapshots__/test_corpus/sdk_tests/std/bytes.test.w_compile_tf-aws.md create mode 100644 tools/hangar/__snapshots__/test_corpus/sdk_tests/std/bytes.test.w_test_sim.md diff --git a/docs/api/04-standard-library/fs/api-reference.md b/docs/api/04-standard-library/fs/api-reference.md index 04ae46446af..08cc8b22b77 100644 --- a/docs/api/04-standard-library/fs/api-reference.md +++ b/docs/api/04-standard-library/fs/api-reference.md @@ -51,6 +51,7 @@ new fs.Util(); | metadata | Gets the stats of the given path. | | mkdir | Create a directory. | | mkdtemp | Create a temporary directory. | +| readBytes | Read the contents of the file as bytes. | | readdir | Read the contents of the directory. | | readFile | Read the entire contents of a file. | | readJson | Read the contents of the file and convert it to JSON. | @@ -60,10 +61,12 @@ new fs.Util(); | setPermissions | Set the permissions of the file, directory, etc. | | symlink | Creates a symbolic link. | | symlinkMetadata | Gets the stats of the given path without following symbolic links. | +| tryReadBytes | If the file exists, read the contents of the file as bytes; | | tryReaddir | If the path exists, read the contents of the directory; | | tryReadFile | If the file exists and can be read successfully, read the entire contents; | | tryReadJson | Retrieve the contents of the file and convert it to JSON if the file exists and can be parsed successfully, otherwise, return `undefined`. | | tryReadYaml | Convert all YAML objects from a single file into JSON objects if the file exists and can be parsed successfully, `undefined` otherwise. | +| writeBytes | Write bytes to a file, replacing the file if it already exists. | | writeFile | Writes data to a file, replacing the file if it already exists. | | writeJson | Writes JSON to a file, replacing the file if it already exists. | | writeYaml | Writes multiple YAML objects to a file, replacing the file if it already exists. | @@ -356,6 +359,24 @@ The prefix for the directory to be created, default `wingtemp`. --- +##### `readBytes` + +```wing +bring fs; + +fs.readBytes(filepath: str); +``` + +Read the contents of the file as bytes. + +###### `filepath`Required + +- *Type:* str + +The file path. + +--- + ##### `readdir` ```wing @@ -572,6 +593,26 @@ The path to get stats for. --- +##### `tryReadBytes` + +```wing +bring fs; + +fs.tryReadBytes(filepath: str); +``` + +If the file exists, read the contents of the file as bytes; + +otherwise, return `undefined`. + +###### `filepath`Required + +- *Type:* str + +The file path. + +--- + ##### `tryReaddir` ```wing @@ -656,6 +697,43 @@ The file path of the YAML file. --- +##### `writeBytes` + +```wing +bring fs; + +fs.writeBytes(filepath: str, data: Bytes, options?: WriteFileOptions); +``` + +Write bytes to a file, replacing the file if it already exists. + +###### `filepath`Required + +- *Type:* str + +The file path that needs to be written. + +--- + +###### `data`Required + +- *Type:* Bytes + +The bytes to write. + +--- + +###### `options`Optional + +- *Type:* WriteFileOptions + +The `encoding` can be set to specify the character encoding. + +And the `flag` can be set to specify the attributes. +If a flag is not provided, it defaults to `"w"`. + +--- + ##### `writeFile` ```wing diff --git a/docs/api/04-standard-library/std/bytes.md b/docs/api/04-standard-library/std/bytes.md new file mode 100644 index 00000000000..f583428b2f2 --- /dev/null +++ b/docs/api/04-standard-library/std/bytes.md @@ -0,0 +1,282 @@ +--- +title: bytes +id: bytes +--- + +# API Reference + + +## Classes + +### Bytes + +Immutable sequence of binary data. + +#### Methods + +| **Name** | **Description** | +| --- | --- | +| at | Get the byte at the given index. | +| copy | Create a copy of the `bytes` value. | +| slice | Get the slice of the `bytes` value from the given start index to the given end index. | +| toBase64 | Convert the `bytes` value to a base64 encoded string. | +| toHex | Convert the `bytes` value to a hex encoded string. | +| toRaw | Convert the `bytes` value to an array of byte values. | +| tryAt | Get the byte at the given index, returning nil if the index is out of bounds. | + +--- + +##### `at` + +```wing +at(index: num): num +``` + +Get the byte at the given index. + +###### `index`Required + +- *Type:* num + +index of the value to get. + +--- + +##### `copy` + +```wing +copy(): Bytes +``` + +Create a copy of the `bytes` value. + +##### `slice` + +```wing +slice(startIndex: num, endIndex?: num): Bytes +``` + +Get the slice of the `bytes` value from the given start index to the given end index. + +###### `startIndex`Required + +- *Type:* num + +index to start the slice. + +--- + +###### `endIndex`Optional + +- *Type:* num + +index to end the slice. + +--- + +##### `toBase64` + +```wing +toBase64(): str +``` + +Convert the `bytes` value to a base64 encoded string. + +##### `toHex` + +```wing +toHex(): str +``` + +Convert the `bytes` value to a hex encoded string. + +##### `toRaw` + +```wing +toRaw(): MutArray +``` + +Convert the `bytes` value to an array of byte values. + +##### `tryAt` + +```wing +tryAt(index: num): num? +``` + +Get the byte at the given index, returning nil if the index is out of bounds. + +###### `index`Required + +- *Type:* num + +index of the value to get. + +--- + +#### Static Functions + +| **Name** | **Description** | +| --- | --- | +| concat | Concatenate multiple `bytes` values. | +| fromBase64 | Create a new `bytes` value from a base64 encoded string. | +| fromHex | Create a new `bytes` value from a hex encoded string. | +| fromRaw | Create a new `bytes` value from an array of byte values. | +| fromString | Create a new `bytes` value from a string. | +| zeros | Create a new `bytes` value with the given length, filled with zeros. | + +--- + +##### `concat` + +```wing +Bytes.concat(...values: Array); +``` + +Concatenate multiple `bytes` values. + +###### `values`Required + +- *Type:* Bytes + +the `bytes` values to concatenate. + +--- + +##### `fromBase64` + +```wing +Bytes.fromBase64(base64: str); +``` + +Create a new `bytes` value from a base64 encoded string. + +###### `base64`Required + +- *Type:* str + +The base64 encoded string to create the `bytes` from. + +--- + +##### `fromHex` + +```wing +Bytes.fromHex(hex: str); +``` + +Create a new `bytes` value from a hex encoded string. + +###### `hex`Required + +- *Type:* str + +The hex encoded string to create the `bytes` from. + +--- + +##### `fromRaw` + +```wing +Bytes.fromRaw(values: MutArray); +``` + +Create a new `bytes` value from an array of byte values. + +###### `values`Required + +- *Type:* MutArray<num> + +The byte values to create the `bytes` from. + +--- + +##### `fromString` + +```wing +Bytes.fromString(value: str); +``` + +Create a new `bytes` value from a string. + +###### `value`Required + +- *Type:* str + +The string to create the `bytes` from. + +--- + +##### `zeros` + +```wing +Bytes.zeros(length: num); +``` + +Create a new `bytes` value with the given length, filled with zeros. + +###### `length`Required + +- *Type:* num + +The length of the new `bytes` value. + +--- + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| length | num | The length of the bytes. | + +--- + +##### `length`Required + +```wing +length: num; +``` + +- *Type:* num + +The length of the bytes. + +--- + + +## Structs + +### BytesToStringOptions + +Options for converting a `bytes` value to a string. + +#### Initializer + +```wing +let BytesToStringOptions = BytesToStringOptions{ ... }; +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| encoding | str | The encoding to use when converting the `bytes` value to a string. | + +--- + +##### `encoding`Required + +```wing +encoding: str; +``` + +- *Type:* str +- *Default:* "utf-8" + +The encoding to use when converting the `bytes` value to a string. + +> [https://developer.mozilla.org/en-US/docs/Web/API/Encoding_API/Encodings](https://developer.mozilla.org/en-US/docs/Web/API/Encoding_API/Encodings) + +--- + + diff --git a/docs/api/05-language-reference.md b/docs/api/05-language-reference.md index 236e9c050f2..6433997d547 100644 --- a/docs/api/05-language-reference.md +++ b/docs/api/05-language-reference.md @@ -1233,14 +1233,14 @@ let rawData: bytes = bytes.fromRaw([104, 101, 108, 108, 111]); let rawString: bytes = bytes.fromString("hello"); let base64: bytes = bytes.fromBase64("aGVsbG8="); let hex: bytes = bytes.fromHex("68656c6c6f"); -let zeroes: bytes = bytes.alloc(20); // allocates 20 zeroed bytes +let zeros: bytes = bytes.zeros(20); // allocates 20 zeroed bytes // mutable initializers let rawDataMut: mutbytes = mutbytes.fromRaw([104, 101, 108, 108, 111]); let rawStringMut: mutbytes = mutbytes.fromString("hello"); let base64Mut: mutbytes = mutbytes.fromBase64("aGVsbG8="); let hexMut: mutbytes = mutbytes.fromHex("68656c6c6f"); -let zeroesMut: mutbytes = mutbytes.alloc(20); // allocates 20 zeroed bytes +let zerosMut: mutbytes = mutbytes.zeros(20); // allocates 20 zeroed bytes ``` #### Converting bytes to other types @@ -1262,7 +1262,7 @@ let asBytes: bytes = asMutBytes.copy(); #### Working with bytes ```TS -let concatenated: bytes = rawData.concat(rawData); +let concatenated: bytes = bytes.concat(rawData, rawData, rawData); let sliced: bytes = rawData.slice(1, 3); let length: num = rawData.length; diff --git a/packages/@winglang/sdk/src/fs/fs.ts b/packages/@winglang/sdk/src/fs/fs.ts index be0f46c4edc..bc252144926 100644 --- a/packages/@winglang/sdk/src/fs/fs.ts +++ b/packages/@winglang/sdk/src/fs/fs.ts @@ -7,6 +7,7 @@ import * as yaml from "yaml"; import { InflightClient } from "../core"; import { normalPath } from "../shared/misc"; import { Datetime, Json } from "../std"; +import { Bytes } from "../std/bytes"; /** * Custom settings for reading from a file @@ -386,6 +387,30 @@ export class Util { return undefined; } } + + /** + * Read the contents of the file as bytes. + * @param filepath The file path. + * @returns The bytes contained in the file. + */ + public static readBytes(filepath: string): Bytes { + const buf = fs.readFileSync(filepath); + return Bytes._fromUtf8Array(buf); + } + + /** + * If the file exists, read the contents of the file as bytes; otherwise, return `undefined`. + * @param filepath The file path. + * @returns The bytes contained in the file, `undefined` otherwise. + */ + public static tryReadBytes(filepath: string): Bytes | undefined { + try { + return Util.readBytes(filepath); + } catch { + return undefined; + } + } + /** * Writes data to a file, replacing the file if it already exists. * @param filepath The file path that needs to be written. @@ -423,6 +448,21 @@ export class Util { fs.writeFileSync(filepath, contents.join("---\n")); } + /** + * Write bytes to a file, replacing the file if it already exists. + * @param filepath The file path that needs to be written. + * @param data The bytes to write. + * @param options The `encoding` can be set to specify the character encoding. And the `flag` can be set to specify the attributes. + * If a flag is not provided, it defaults to `"w"`. + */ + public static writeBytes( + filepath: string, + data: Bytes, + options?: WriteFileOptions + ): void { + fs.writeFileSync(filepath, data._data, options); + } + /** * Appends new data to the end of an existing file * @param filepath The file path that needs to be appended. diff --git a/packages/@winglang/sdk/src/helpers.ts b/packages/@winglang/sdk/src/helpers.ts index 12332d23f0d..1e7e46492f0 100644 --- a/packages/@winglang/sdk/src/helpers.ts +++ b/packages/@winglang/sdk/src/helpers.ts @@ -7,6 +7,7 @@ import type { Construct } from "constructs"; import { parse } from "dotenv"; import { expand } from "dotenv-expand"; import type { Resource } from "./std"; +import type { Bytes } from "./std/bytes"; import type { Node } from "./std/node"; // since we moved from node:18 to node:20 the deepStrictEqual doesn't work as expected. // https://github.com/winglang/wing/issues/4444 @@ -64,6 +65,10 @@ export function unwrap(value: T): T | never { } export function lookup(obj: any, index: string | number): any { + if (isBytes(obj)) { + obj = obj._data; + } + checkIndex(index); if (typeof index === "number") { @@ -90,6 +95,10 @@ export function assign( kind: "=" | "+=" | "-=", value: any ) { + if (isBytes(obj)) { + obj = obj._data; + } + checkIndex(index); if (typeof index === "number") { @@ -126,7 +135,12 @@ function checkIndex(index: string | number) { } function checkArrayAccess(obj: any, index: number): number { - if (!Array.isArray(obj) && !Buffer.isBuffer(obj) && typeof obj !== "string") { + if ( + !Array.isArray(obj) && + !Buffer.isBuffer(obj) && + !(obj instanceof Uint8Array) && + typeof obj !== "string" + ) { throw new TypeError( "Index is a number but collection is not an array or string" ); @@ -142,6 +156,12 @@ function checkArrayAccess(obj: any, index: number): number { return index; } +function isBytes(obj: any): obj is Bytes { + return ( + typeof obj === "object" && obj !== null && obj._data instanceof Uint8Array + ); +} + export function createExternRequire(dirname: string) { return (externPath: string) => { // using eval to always avoid bundling diff --git a/packages/@winglang/sdk/src/std/bytes.ts b/packages/@winglang/sdk/src/std/bytes.ts new file mode 100644 index 00000000000..deeeae31afe --- /dev/null +++ b/packages/@winglang/sdk/src/std/bytes.ts @@ -0,0 +1,197 @@ +// These classes are used by Wing to create the built-in `bytes` type. +// They should not be consumed directly by users. +import { InflightClient } from "../core"; + +/** + * Options for converting a `bytes` value to a string. + */ +export interface BytesToStringOptions { + /** + * The encoding to use when converting the `bytes` value to a string. + * @default "utf-8" + * @see https://developer.mozilla.org/en-US/docs/Web/API/Encoding_API/Encodings + */ + readonly encoding: string; +} + +// TODO: is there a way so we can avoid wrapping the Uint8Array data in a class? +// e.g. so compiled code for bytes.fromString("hello") can just return a Uint8Array + +/** + * Immutable sequence of binary data. + */ +export class Bytes { + /** + * @internal + */ + public static _toInflightType(): string { + return InflightClient.forType(__filename, this.name); + } + + /** @internal */ + public static _fromUtf8Array(data: Uint8Array): Bytes { + return new Bytes(data); + } + + /** + * Create a new `bytes` value from an array of byte values + * @param values - The byte values to create the `bytes` from + * @returns a new `bytes` value containing the byte values + */ + public static fromRaw(values: Array): Bytes { + return new Bytes(new Uint8Array(values)); + } + + /** + * Create a new `bytes` value from a string + * @param value - The string to create the `bytes` from + * @returns a new `bytes` value containing the string + */ + public static fromString(value: string): Bytes { + return new Bytes(new TextEncoder().encode(value)); + } + + /** + * Create a new `bytes` value from a base64 encoded string + * @param base64 - The base64 encoded string to create the `bytes` from + * @returns a new `bytes` value containing the decoded bytes + */ + public static fromBase64(base64: string): Bytes { + const binaryString = atob(base64); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return new Bytes(bytes); + } + + /** + * Create a new `bytes` value from a hex encoded string + * @param hex - The hex encoded string to create the `bytes` from + * @returns a new `bytes` value containing the decoded bytes + */ + public static fromHex(hex: string): Bytes { + const bytes = new Uint8Array(hex.length / 2); + for (let i = 0; i < hex.length; i += 2) { + bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16); + } + return new Bytes(bytes); + } + + /** + * Create a new `bytes` value with the given length, filled with zeros + * @param length - The length of the new `bytes` value + * @returns a new `bytes` value with the given length, filled with zeros + */ + public static zeros(length: number): Bytes { + return new Bytes(new Uint8Array(length)); + } + + /** + * Concatenate multiple `bytes` values + * @param values the `bytes` values to concatenate + * @returns a new `bytes` value with the bytes from the input values concatenated together + */ + public static concat(...values: Array): Bytes { + const totalLength = values.reduce((acc, bytes) => acc + bytes.length, 0); + const newData = new Uint8Array(totalLength); + let offset = 0; + for (const bytes of values) { + newData.set(bytes._data, offset); + offset += bytes.length; + } + return new Bytes(newData); + } + + /** @internal */ + public readonly _data: Uint8Array; + + private constructor(data: Uint8Array) { + this._data = data; + } + + /** + * Convert the `bytes` value to an array of byte values + * @returns an array of byte values + */ + public toRaw(): Array { + return Array.from(this._data); + } + + /** + * Convert the `bytes` value to a string + * @param options - The options to use when converting the `bytes` value to a string + * @returns a decoded string + */ + public toString(options?: BytesToStringOptions): string { + return new TextDecoder(options?.encoding).decode(this._data); + } + + /** + * Convert the `bytes` value to a base64 encoded string + * @returns a base64 encoded string + */ + public toBase64(): string { + return btoa(String.fromCharCode(...this._data)); + } + + /** + * Convert the `bytes` value to a hex encoded string + * @returns a hex encoded string + */ + public toHex(): string { + return Array.from(this._data) + .map((byte) => byte.toString(16).padStart(2, "0")) + .join(""); + } + + /** + * Create a copy of the `bytes` value + * @returns a new `bytes` value with the same byte values as the original + */ + public copy(): Bytes { + return new Bytes(this._data.slice()); + } + + /** + * The length of the bytes + * @returns the length of the bytes + */ + public get length(): number { + return this._data.length; + } + + /** + * Get the byte at the given index + * @param index index of the value to get + * @returns the byte value (0-255) at the given index + */ + public at(index: number): number { + if (index < 0 || index >= this.length) { + throw new Error("Index out of bounds"); + } + return this._data[index]; + } + + /** + * Get the byte at the given index, returning nil if the index is out of bounds. + * @param index index of the value to get + * @returns the byte value (0-255) at the given index, or nil if the index is out of bounds + */ + public tryAt(index: number): number | undefined { + if (index < 0 || index >= this.length) { + return undefined; + } + return this._data[index]; + } + + /** + * Get the slice of the `bytes` value from the given start index to the given end index + * @param startIndex index to start the slice + * @param endIndex index to end the slice + * @returns a new `bytes` value with the bytes from the start index to the end index + */ + public slice(startIndex: number, endIndex?: number): Bytes { + return new Bytes(this._data.slice(startIndex, endIndex)); + } +} diff --git a/packages/@winglang/sdk/src/std/index.ts b/packages/@winglang/sdk/src/std/index.ts index d5f77f4e326..52451e6243a 100644 --- a/packages/@winglang/sdk/src/std/index.ts +++ b/packages/@winglang/sdk/src/std/index.ts @@ -1,5 +1,6 @@ export * from "./array"; export * from "./bool"; +export * from "./bytes"; export * from "./datetime"; export * from "./duration"; export * from "./generics"; diff --git a/packages/@winglang/tree-sitter-wing/grammar.js b/packages/@winglang/tree-sitter-wing/grammar.js index eb4c91ea436..7a8bdda1494 100644 --- a/packages/@winglang/tree-sitter-wing/grammar.js +++ b/packages/@winglang/tree-sitter-wing/grammar.js @@ -539,7 +539,8 @@ module.exports = grammar({ "void", "duration", "datetime", - "regex" + "regex", + "bytes" ), initializer: ($) => diff --git a/packages/@winglang/tree-sitter-wing/src/grammar.json b/packages/@winglang/tree-sitter-wing/src/grammar.json index a531bc822fe..fe22450e4ab 100644 --- a/packages/@winglang/tree-sitter-wing/src/grammar.json +++ b/packages/@winglang/tree-sitter-wing/src/grammar.json @@ -2929,6 +2929,10 @@ { "type": "STRING", "value": "regex" + }, + { + "type": "STRING", + "value": "bytes" } ] }, diff --git a/packages/@winglang/wingc/src/ast.rs b/packages/@winglang/wingc/src/ast.rs index 305edb71a31..1c151d3b3bd 100644 --- a/packages/@winglang/wingc/src/ast.rs +++ b/packages/@winglang/wingc/src/ast.rs @@ -131,6 +131,7 @@ pub enum TypeAnnotationKind { Duration, Datetime, Regex, + Bytes, Void, Json, MutJson, @@ -207,6 +208,7 @@ impl Display for TypeAnnotationKind { TypeAnnotationKind::Duration => write!(f, "duration"), TypeAnnotationKind::Datetime => write!(f, "datetime"), TypeAnnotationKind::Regex => write!(f, "regex"), + TypeAnnotationKind::Bytes => write!(f, "bytes"), TypeAnnotationKind::Void => write!(f, "void"), TypeAnnotationKind::Json => write!(f, "Json"), TypeAnnotationKind::MutJson => write!(f, "MutJson"), diff --git a/packages/@winglang/wingc/src/docs.rs b/packages/@winglang/wingc/src/docs.rs index 434fdd7b82e..1900389cb25 100644 --- a/packages/@winglang/wingc/src/docs.rs +++ b/packages/@winglang/wingc/src/docs.rs @@ -157,6 +157,7 @@ impl Documented for TypeRef { | Type::Duration | Type::Datetime | Type::Regex + | Type::Bytes | Type::Boolean | Type::Void | Type::Json(_) @@ -194,6 +195,7 @@ impl Documented for TypeRef { | Type::Duration | Type::Datetime | Type::Regex + | Type::Bytes | Type::Boolean | Type::Void | Type::Json(_) diff --git a/packages/@winglang/wingc/src/dtsify/extern_dtsify.rs b/packages/@winglang/wingc/src/dtsify/extern_dtsify.rs index 7fbcd359eba..776b327d9b8 100644 --- a/packages/@winglang/wingc/src/dtsify/extern_dtsify.rs +++ b/packages/@winglang/wingc/src/dtsify/extern_dtsify.rs @@ -10,12 +10,13 @@ use crate::{ files::{remove_file, update_file, FilesError}, jsify::codemaker::CodeMaker, type_check::*, - WINGSDK_ASSEMBLY_NAME, WINGSDK_DATETIME, WINGSDK_DURATION, WINGSDK_REGEX, + WINGSDK_ASSEMBLY_NAME, WINGSDK_BYTES, WINGSDK_DATETIME, WINGSDK_DURATION, WINGSDK_REGEX, }; const DURATION_FQN: &str = formatcp!("{WINGSDK_ASSEMBLY_NAME}.{WINGSDK_DURATION}"); const DATETIME_FQN: &str = formatcp!("{WINGSDK_ASSEMBLY_NAME}.{WINGSDK_DATETIME}"); const REGEX_FQN: &str = formatcp!("{WINGSDK_ASSEMBLY_NAME}.{WINGSDK_REGEX}"); +const BYTES_FQN: &str = formatcp!("{WINGSDK_ASSEMBLY_NAME}.{WINGSDK_BYTES}"); /// Generates a self-contained .d.ts file for a given extern file. pub struct ExternDTSifier<'a> { @@ -158,6 +159,17 @@ impl<'a> ExternDTSifier<'a> { .unwrap(); self.dtsify_type(regex_type, false) } + Type::Bytes => { + let bytes_type = self + .types + .libraries + .lookup_nested_str(BYTES_FQN, None) + .unwrap() + .0 + .as_type() + .unwrap(); + self.dtsify_type(bytes_type, false) + } Type::Optional(t) => format!("({}) | undefined", self.dtsify_type(*t, is_inflight)), Type::Array(t) => format!("(readonly ({})[])", self.dtsify_type(*t, is_inflight)), Type::MutArray(t) => format!("({})[]", self.dtsify_type(*t, is_inflight)), diff --git a/packages/@winglang/wingc/src/dtsify/mod.rs b/packages/@winglang/wingc/src/dtsify/mod.rs index 34c8d8319e5..431dbed8e8d 100644 --- a/packages/@winglang/wingc/src/dtsify/mod.rs +++ b/packages/@winglang/wingc/src/dtsify/mod.rs @@ -378,6 +378,7 @@ impl<'a> DTSifier<'a> { TypeAnnotationKind::Duration => format!("{TYPE_STD}.Duration"), TypeAnnotationKind::Datetime => format!("{TYPE_STD}.Datetime"), TypeAnnotationKind::Regex => format!("{TYPE_STD}.Regex"), + TypeAnnotationKind::Bytes => format!("{TYPE_STD}.Bytes"), TypeAnnotationKind::Optional(t) => { format!("({}) | undefined", self.dtsify_type_annotation(&t, ignore_phase)) } diff --git a/packages/@winglang/wingc/src/fold.rs b/packages/@winglang/wingc/src/fold.rs index 0a35536f173..261d2550a2b 100644 --- a/packages/@winglang/wingc/src/fold.rs +++ b/packages/@winglang/wingc/src/fold.rs @@ -536,6 +536,7 @@ where TypeAnnotationKind::Duration => TypeAnnotationKind::Duration, TypeAnnotationKind::Datetime => TypeAnnotationKind::Datetime, TypeAnnotationKind::Regex => TypeAnnotationKind::Regex, + TypeAnnotationKind::Bytes => TypeAnnotationKind::Bytes, TypeAnnotationKind::Void => TypeAnnotationKind::Void, TypeAnnotationKind::Json => TypeAnnotationKind::Json, TypeAnnotationKind::MutJson => TypeAnnotationKind::MutJson, diff --git a/packages/@winglang/wingc/src/lib.rs b/packages/@winglang/wingc/src/lib.rs index cebfc6a1fa3..75a2fec4932 100644 --- a/packages/@winglang/wingc/src/lib.rs +++ b/packages/@winglang/wingc/src/lib.rs @@ -106,6 +106,7 @@ const WINGSDK_GENERIC: &'static str = "std.T1"; const WINGSDK_DURATION: &'static str = "std.Duration"; const WINGSDK_DATETIME: &'static str = "std.Datetime"; const WINGSDK_REGEX: &'static str = "std.Regex"; +const WINGSDK_BYTES: &'static str = "std.Bytes"; const WINGSDK_MAP: &'static str = "std.Map"; const WINGSDK_MUT_MAP: &'static str = "std.MutMap"; const WINGSDK_ARRAY: &'static str = "std.Array"; diff --git a/packages/@winglang/wingc/src/lsp/completions.rs b/packages/@winglang/wingc/src/lsp/completions.rs index 314de292f08..4c84a4fb977 100644 --- a/packages/@winglang/wingc/src/lsp/completions.rs +++ b/packages/@winglang/wingc/src/lsp/completions.rs @@ -25,7 +25,9 @@ use crate::{UTIL_CLASS_NAME, WINGSDK_BRINGABLE_MODULES, WINGSDK_STD_MODULE}; use super::sync::check_utf8; -const BUILTIN_TYPES: [&str; 8] = ["bool", "duration", "Json", "MutJson", "num", "str", "datetime", "regex"]; +const BUILTIN_TYPES: [&str; 9] = [ + "bool", "duration", "Json", "MutJson", "num", "str", "datetime", "regex", "bytes", +]; const BUILTIN_GENERICS: [&str; 6] = ["Array", "Map", "MutArray", "MutMap", "MutSet", "Set"]; #[no_mangle] @@ -1131,6 +1133,7 @@ fn format_symbol_kind_as_completion(name: &str, symbol_kind: &SymbolKind) -> Opt | Type::Duration | Type::Datetime | Type::Regex + | Type::Bytes | Type::Boolean | Type::Void | Type::Json(_) diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/call_struct_expansion.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/call_struct_expansion.snap index 4eeb4d6499e..88c03620462 100644 --- a/packages/@winglang/wingc/src/lsp/snapshots/completions/call_struct_expansion.snap +++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/call_struct_expansion.snap @@ -56,6 +56,9 @@ source: packages/@winglang/wingc/src/lsp/completions.rs - label: bool kind: 14 sortText: kl|zybool +- label: bytes + kind: 14 + sortText: kl|zybytes - label: datetime kind: 14 sortText: kl|zydatetime diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/call_struct_expansion_partial.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/call_struct_expansion_partial.snap index 8b052f0e78e..21e3e16a959 100644 --- a/packages/@winglang/wingc/src/lsp/snapshots/completions/call_struct_expansion_partial.snap +++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/call_struct_expansion_partial.snap @@ -56,6 +56,9 @@ source: packages/@winglang/wingc/src/lsp/completions.rs - label: bool kind: 14 sortText: kl|zybool +- label: bytes + kind: 14 + sortText: kl|zybytes - label: datetime kind: 14 sortText: kl|zydatetime diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/empty.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/empty.snap index 579eebdf85d..2e2bc7fa22d 100644 --- a/packages/@winglang/wingc/src/lsp/snapshots/completions/empty.snap +++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/empty.snap @@ -65,6 +65,9 @@ source: packages/@winglang/wingc/src/lsp/completions.rs - label: bool kind: 14 sortText: kl|zybool +- label: bytes + kind: 14 + sortText: kl|zybytes - label: datetime kind: 14 sortText: kl|zydatetime diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/hide_parent_symbols_defined_later.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/hide_parent_symbols_defined_later.snap index 4ab06337a4a..50adb593bdd 100644 --- a/packages/@winglang/wingc/src/lsp/snapshots/completions/hide_parent_symbols_defined_later.snap +++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/hide_parent_symbols_defined_later.snap @@ -1,5 +1,5 @@ --- -source: libs/wingc/src/lsp/completions.rs +source: packages/@winglang/wingc/src/lsp/completions.rs --- - label: this kind: 6 @@ -66,6 +66,9 @@ source: libs/wingc/src/lsp/completions.rs - label: bool kind: 14 sortText: kl|zybool +- label: bytes + kind: 14 + sortText: kl|zybytes - label: datetime kind: 14 sortText: kl|zydatetime diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/only_show_symbols_in_scope.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/only_show_symbols_in_scope.snap index 8664a76341c..eaf30451b3e 100644 --- a/packages/@winglang/wingc/src/lsp/snapshots/completions/only_show_symbols_in_scope.snap +++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/only_show_symbols_in_scope.snap @@ -45,6 +45,9 @@ source: packages/@winglang/wingc/src/lsp/completions.rs - label: bool kind: 14 sortText: kl|zybool +- label: bytes + kind: 14 + sortText: kl|zybytes - label: datetime kind: 14 sortText: kl|zydatetime diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_arg_expansion_partial.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_arg_expansion_partial.snap index 271e4af50c5..edccd1167fb 100644 --- a/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_arg_expansion_partial.snap +++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_arg_expansion_partial.snap @@ -65,6 +65,9 @@ source: packages/@winglang/wingc/src/lsp/completions.rs - label: bool kind: 14 sortText: kl|zybool +- label: bytes + kind: 14 + sortText: kl|zybytes - label: datetime kind: 14 sortText: kl|zydatetime diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_definition_types.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_definition_types.snap index 26c043077fe..4f053723130 100644 --- a/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_definition_types.snap +++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_definition_types.snap @@ -1,5 +1,5 @@ --- -source: libs/wingc/src/lsp/completions.rs +source: packages/@winglang/wingc/src/lsp/completions.rs --- - label: Foo kind: 22 @@ -22,6 +22,9 @@ source: libs/wingc/src/lsp/completions.rs - label: bool kind: 14 sortText: kl|zybool +- label: bytes + kind: 14 + sortText: kl|zybytes - label: datetime kind: 14 sortText: kl|zydatetime diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_literal_value.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_literal_value.snap index 106ee1c1519..280c0e22884 100644 --- a/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_literal_value.snap +++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_literal_value.snap @@ -47,6 +47,9 @@ source: packages/@winglang/wingc/src/lsp/completions.rs - label: bool kind: 14 sortText: kl|zybool +- label: bytes + kind: 14 + sortText: kl|zybytes - label: datetime kind: 14 sortText: kl|zydatetime diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_show_values.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_show_values.snap index 2870bfba730..7e2f2e11bfc 100644 --- a/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_show_values.snap +++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/struct_show_values.snap @@ -51,6 +51,9 @@ source: packages/@winglang/wingc/src/lsp/completions.rs - label: bool kind: 14 sortText: kl|zybool +- label: bytes + kind: 14 + sortText: kl|zybytes - label: datetime kind: 14 sortText: kl|zydatetime diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/type_annotation_shows_struct.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/type_annotation_shows_struct.snap index 067f932d154..cd12b645328 100644 --- a/packages/@winglang/wingc/src/lsp/snapshots/completions/type_annotation_shows_struct.snap +++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/type_annotation_shows_struct.snap @@ -1,5 +1,5 @@ --- -source: libs/wingc/src/lsp/completions.rs +source: packages/@winglang/wingc/src/lsp/completions.rs --- - label: Foo kind: 22 @@ -16,6 +16,9 @@ source: libs/wingc/src/lsp/completions.rs - label: bool kind: 14 sortText: kl|zybool +- label: bytes + kind: 14 + sortText: kl|zybytes - label: datetime kind: 14 sortText: kl|zydatetime diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/type_parameter.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/type_parameter.snap index 4d715c940df..1a8974047ea 100644 --- a/packages/@winglang/wingc/src/lsp/snapshots/completions/type_parameter.snap +++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/type_parameter.snap @@ -1,5 +1,5 @@ --- -source: libs/wingc/src/lsp/completions.rs +source: packages/@winglang/wingc/src/lsp/completions.rs --- - label: Json kind: 14 @@ -10,6 +10,9 @@ source: libs/wingc/src/lsp/completions.rs - label: bool kind: 14 sortText: kl|zybool +- label: bytes + kind: 14 + sortText: kl|zybytes - label: datetime kind: 14 sortText: kl|zydatetime diff --git a/packages/@winglang/wingc/src/lsp/symbol_locator.rs b/packages/@winglang/wingc/src/lsp/symbol_locator.rs index 083aac1fbdc..6b02b4d28b0 100644 --- a/packages/@winglang/wingc/src/lsp/symbol_locator.rs +++ b/packages/@winglang/wingc/src/lsp/symbol_locator.rs @@ -142,6 +142,7 @@ impl<'a> SymbolLocator<'a> { | Type::Duration | Type::Datetime | Type::Regex + | Type::Bytes | Type::Boolean => { if let Some((std_type, ..)) = self.types.get_std_class(&type_) { if let Some(t) = std_type.as_type_ref() { diff --git a/packages/@winglang/wingc/src/parser.rs b/packages/@winglang/wingc/src/parser.rs index 91c0c6f9f51..67da995f7c9 100644 --- a/packages/@winglang/wingc/src/parser.rs +++ b/packages/@winglang/wingc/src/parser.rs @@ -121,6 +121,7 @@ static RESERVED_WORDS: phf::Set<&'static str> = phf_set! { "duration", "datetime", "regex", + "bytes", "bool", "Json", "MutJson", @@ -2032,6 +2033,10 @@ impl<'s> Parser<'s> { kind: TypeAnnotationKind::Regex, span, }), + "bytes" => Ok(TypeAnnotation { + kind: TypeAnnotationKind::Bytes, + span, + }), "void" => Ok(TypeAnnotation { kind: TypeAnnotationKind::Void, span, diff --git a/packages/@winglang/wingc/src/type_check.rs b/packages/@winglang/wingc/src/type_check.rs index 62fe866f65f..fca22eb373c 100644 --- a/packages/@winglang/wingc/src/type_check.rs +++ b/packages/@winglang/wingc/src/type_check.rs @@ -29,9 +29,9 @@ use crate::visit_stmt_before_super::{CheckSuperCtorLocationVisitor, CheckValidBe use crate::visit_types::{VisitType, VisitTypeMut}; use crate::{ debug, CONSTRUCT_BASE_CLASS, CONSTRUCT_BASE_INTERFACE, CONSTRUCT_NODE_PROPERTY, DEFAULT_PACKAGE_NAME, - UTIL_CLASS_NAME, WINGSDK_APP, WINGSDK_ARRAY, WINGSDK_ASSEMBLY_NAME, WINGSDK_BRINGABLE_MODULES, WINGSDK_DATETIME, - WINGSDK_DURATION, WINGSDK_GENERIC, WINGSDK_IRESOURCE, WINGSDK_JSON, WINGSDK_MAP, WINGSDK_MUT_ARRAY, WINGSDK_MUT_JSON, - WINGSDK_MUT_MAP, WINGSDK_MUT_SET, WINGSDK_NODE, WINGSDK_REGEX, WINGSDK_RESOURCE, WINGSDK_SET, + UTIL_CLASS_NAME, WINGSDK_APP, WINGSDK_ARRAY, WINGSDK_ASSEMBLY_NAME, WINGSDK_BRINGABLE_MODULES, WINGSDK_BYTES, + WINGSDK_DATETIME, WINGSDK_DURATION, WINGSDK_GENERIC, WINGSDK_IRESOURCE, WINGSDK_JSON, WINGSDK_MAP, WINGSDK_MUT_ARRAY, + WINGSDK_MUT_JSON, WINGSDK_MUT_MAP, WINGSDK_MUT_SET, WINGSDK_NODE, WINGSDK_REGEX, WINGSDK_RESOURCE, WINGSDK_SET, WINGSDK_SIM_IRESOURCE_FQN, WINGSDK_STD_MODULE, WINGSDK_STRING, WINGSDK_STRUCT, }; use camino::{Utf8Path, Utf8PathBuf}; @@ -231,6 +231,7 @@ pub enum Type { Duration, Datetime, Regex, + Bytes, Boolean, Void, /// Immutable Json literals may store extra information about their known data @@ -955,6 +956,7 @@ impl Display for Type { Type::Duration => write!(f, "duration"), Type::Datetime => write!(f, "datetime"), Type::Regex => write!(f, "regex"), + Type::Bytes => write!(f, "bytes"), Type::Boolean => write!(f, "bool"), Type::Void => write!(f, "void"), Type::Json(_) => write!(f, "Json"), @@ -1330,6 +1332,7 @@ impl TypeRef { Type::Duration => false, Type::Datetime => false, Type::Regex => false, + Type::Bytes => false, Type::Inferred(_) => false, Type::Set(_) => false, Type::MutSet(_) => false, @@ -1480,6 +1483,7 @@ pub struct Types { duration_idx: usize, datetime_idx: usize, regex_idx: usize, + bytes_idx: usize, anything_idx: usize, void_idx: usize, json_idx: usize, @@ -1519,6 +1523,8 @@ impl Types { let datetime_idx = types.len() - 1; types.push(Box::new(Type::Regex)); let regex_idx = types.len() - 1; + types.push(Box::new(Type::Bytes)); + let bytes_idx = types.len() - 1; types.push(Box::new(Type::Anything)); let anything_idx = types.len() - 1; types.push(Box::new(Type::Void)); @@ -1545,6 +1551,7 @@ impl Types { duration_idx, datetime_idx, regex_idx, + bytes_idx, anything_idx, void_idx, json_idx, @@ -1610,6 +1617,10 @@ impl Types { self.get_typeref(self.regex_idx) } + pub fn bytes(&self) -> TypeRef { + self.get_typeref(self.bytes_idx) + } + pub fn anything(&self) -> TypeRef { self.get_typeref(self.anything_idx) } @@ -1879,6 +1890,7 @@ impl Types { Type::Duration => "Duration", Type::Datetime => "Datetime", Type::Regex => "Regex", + Type::Bytes => "Bytes", Type::Json(_) => "Json", Type::MutJson => "MutJson", Type::Array(_) => "Array", @@ -4190,6 +4202,7 @@ See https://www.winglang.io/docs/concepts/application-tree for more information. TypeAnnotationKind::Duration => self.types.duration(), TypeAnnotationKind::Datetime => self.types.datetime(), TypeAnnotationKind::Regex => self.types.regex(), + TypeAnnotationKind::Bytes => self.types.bytes(), TypeAnnotationKind::Void => self.types.void(), TypeAnnotationKind::Json => self.types.json(), TypeAnnotationKind::MutJson => self.types.mut_json(), @@ -5170,6 +5183,7 @@ See https://www.winglang.io/docs/concepts/application-tree for more information. | Type::Duration | Type::Datetime | Type::Regex + | Type::Bytes | Type::Boolean | Type::Void | Type::Nil @@ -6223,6 +6237,10 @@ See https://www.winglang.io/docs/concepts/application-tree for more information. name: "Regex".to_string(), span: symbol.span.clone(), }), + "bytes" => Some(Symbol { + name: "Bytes".to_string(), + span: symbol.span.clone(), + }), "str" => Some(Symbol { name: "String".to_string(), span: symbol.span.clone(), @@ -6626,6 +6644,10 @@ See https://www.winglang.io/docs/concepts/application-tree for more information. ResolveReferenceResult::Location(instance_type, self.types.string()) } + Type::Bytes => { + self.validate_type(index_type, self.types.number(), index); + ResolveReferenceResult::Location(instance_type, self.types.bytes()) + } Type::Number | Type::Duration | Type::Datetime @@ -6745,6 +6767,12 @@ See https://www.winglang.io/docs/concepts/application-tree for more information. false, env, ), + Type::Bytes => self.get_property_from_class_like( + lookup_known_type(WINGSDK_BYTES, env).as_class().unwrap(), + property, + false, + env, + ), Type::Struct(ref s) => self.get_property_from_class_like(s, property, true, env), _ => self.spanned_error_with_var(property, "Property not found").0, } @@ -7408,6 +7436,7 @@ pub fn fully_qualify_std_type(type_: &str) -> String { "duration" => "Duration", "datetime" => "Datetime", "regex" => "Regex", + "bytes" => "Bytes", "str" => "String", "num" => "Number", "bool" => "Boolean", diff --git a/packages/@winglang/wingc/src/type_check/inference_visitor.rs b/packages/@winglang/wingc/src/type_check/inference_visitor.rs index 375803a5c14..0544f8e93d1 100644 --- a/packages/@winglang/wingc/src/type_check/inference_visitor.rs +++ b/packages/@winglang/wingc/src/type_check/inference_visitor.rs @@ -111,6 +111,7 @@ impl<'a> crate::visit_types::VisitType<'_> for InferenceVisitor<'a> { | Type::Duration | Type::Datetime | Type::Regex + | Type::Bytes | Type::Boolean | Type::Void | Type::Json(_) diff --git a/packages/@winglang/wingc/src/type_check/jsii_importer.rs b/packages/@winglang/wingc/src/type_check/jsii_importer.rs index 86ddd0fa3a9..1247039bda6 100644 --- a/packages/@winglang/wingc/src/type_check/jsii_importer.rs +++ b/packages/@winglang/wingc/src/type_check/jsii_importer.rs @@ -10,8 +10,8 @@ use crate::{ Class, FunctionParameter, FunctionSignature, Interface, ResolveSource, Struct, SymbolKind, Type, TypeRef, Types, CLASS_INIT_NAME, }, - CONSTRUCT_BASE_CLASS, CONSTRUCT_BASE_INTERFACE, WINGSDK_ASSEMBLY_NAME, WINGSDK_DATETIME, WINGSDK_DURATION, - WINGSDK_JSON, WINGSDK_MUT_JSON, WINGSDK_REGEX, WINGSDK_RESOURCE, + CONSTRUCT_BASE_CLASS, CONSTRUCT_BASE_INTERFACE, WINGSDK_ASSEMBLY_NAME, WINGSDK_BYTES, WINGSDK_DATETIME, + WINGSDK_DURATION, WINGSDK_JSON, WINGSDK_MUT_JSON, WINGSDK_REGEX, WINGSDK_RESOURCE, }; use colored::Colorize; use indexmap::IndexMap; @@ -108,6 +108,8 @@ impl<'a> JsiiImporter<'a> { self.wing_types.datetime() } else if type_fqn == &format!("{}.{}", WINGSDK_ASSEMBLY_NAME, WINGSDK_REGEX) { self.wing_types.regex() + } else if type_fqn == &format!("{}.{}", WINGSDK_ASSEMBLY_NAME, WINGSDK_BYTES) { + self.wing_types.bytes() } else if type_fqn == &format!("{}.{}", WINGSDK_ASSEMBLY_NAME, WINGSDK_JSON) { self.wing_types.json() } else if type_fqn == &format!("{}.{}", WINGSDK_ASSEMBLY_NAME, WINGSDK_MUT_JSON) { diff --git a/packages/@winglang/wingc/src/visit.rs b/packages/@winglang/wingc/src/visit.rs index e92bba6f237..97b20a2384e 100644 --- a/packages/@winglang/wingc/src/visit.rs +++ b/packages/@winglang/wingc/src/visit.rs @@ -500,6 +500,7 @@ where TypeAnnotationKind::Duration => {} TypeAnnotationKind::Datetime => {} TypeAnnotationKind::Regex => {} + TypeAnnotationKind::Bytes => {} TypeAnnotationKind::Void => {} TypeAnnotationKind::Json => {} TypeAnnotationKind::MutJson => {} diff --git a/packages/@winglang/wingc/src/visit_types.rs b/packages/@winglang/wingc/src/visit_types.rs index a6447329d8d..6a0c4405b8a 100644 --- a/packages/@winglang/wingc/src/visit_types.rs +++ b/packages/@winglang/wingc/src/visit_types.rs @@ -49,6 +49,7 @@ where | Type::Duration | Type::Datetime | Type::Regex + | Type::Bytes | Type::Boolean | Type::Void | Type::Json(None) diff --git a/packages/vscode-wing/syntaxes/wing.tmLanguage.json b/packages/vscode-wing/syntaxes/wing.tmLanguage.json index 610de4f1a9a..47ecdd6fa23 100644 --- a/packages/vscode-wing/syntaxes/wing.tmLanguage.json +++ b/packages/vscode-wing/syntaxes/wing.tmLanguage.json @@ -160,7 +160,7 @@ }, { "name": "support.class.wing", - "match": "\\b(void|str|num|bool|any|duration|regex|datetime|stringable|Map|Set|MutMap|MutSet|Array|MutArray|Promise|Json|MutJson)\\b" + "match": "\\b(void|str|num|bool|any|duration|regex|bytes|datetime|stringable|Map|Set|MutMap|MutSet|Array|MutArray|Promise|Json|MutJson)\\b" } ] }, diff --git a/tests/sdk_tests/std/bytes.test.w b/tests/sdk_tests/std/bytes.test.w new file mode 100644 index 00000000000..311256e162c --- /dev/null +++ b/tests/sdk_tests/std/bytes.test.w @@ -0,0 +1,175 @@ +bring expect; +bring fs; +bring util; + +// constructing bytes +let rawData = bytes.fromRaw([104, 101, 108, 108, 111]); +let rawString = bytes.fromString("hello"); +let base64 = bytes.fromBase64("aGVsbG8="); +let hex = bytes.fromHex("68656c6c6f"); +let zeros = bytes.zeros(5); + +expect.equal(rawData, rawString); +expect.equal(rawData, base64); +expect.equal(rawData, hex); +expect.equal(rawData.length, zeros.length); + +// converting bytes +let asString: str = rawData.toString(); +let asRaw: Array = rawData.toRaw(); +let asBase64: str = rawData.toBase64(); +let asHex: str = rawData.toHex(); + +expect.equal(asString, "hello"); +expect.equal(asRaw, [104, 101, 108, 108, 111]); +expect.equal(asBase64, "aGVsbG8="); +expect.equal(asHex, "68656c6c6f"); + +// string decoding +let someBytes = bytes.fromRaw([195, 169]); +let someString = someBytes.toString(); // default is "utf-8" +let someString2 = someBytes.toString(encoding: "windows-1252"); + +expect.equal(someString, "é"); +expect.equal(someString2, "é"); + +// indexing bytes +expect.equal(rawData[0], 104); +expect.equal(rawData[1], 101); +expect.equal(rawData[2], 108); +expect.equal(rawData[3], 108); +expect.equal(rawData[4], 111); + +try { + rawData[5]; + expect.fail("Expected error"); +} catch err { + expect.ok(err.contains("out of bounds")); +} + +// concatenating bytes +let concated = bytes.concat(rawData, rawData, rawData); +expect.equal(concated.length, rawData.length * 3); +expect.equal(concated.toString(), "hellohellohello"); + +// slicing bytes +let sliced = rawData.slice(1, 3); +expect.equal(sliced.length, 2); +expect.equal(sliced.toString(), "el"); + +// writing and reading bytes to disk +let tmpdir = fs.mkdtemp("bytes-test-"); +let path = fs.join(tmpdir, "hello.txt"); +fs.writeBytes(path, rawData); +let readData = fs.readBytes(path); +expect.equal(readData.toString(), "hello"); +fs.remove(tmpdir); + +// test that we can work with larger files +if util.os() != "win32" { + // Creating and reading a 10MB file + let tenMB = 10 * 1024 * 1024; // 10MB in bytes + let largeTmpdir = fs.mkdtemp("large-bytes-test-"); + let largePath = fs.join(largeTmpdir, "large-file.bin"); + + // Create a 10MB file using dd + util.exec("sh", ["-c", "dd if=/dev/urandom of={largePath} bs=1M count=10 2>/dev/null"]); + + // Read the file into memory as bytes + let largeBytes = fs.readBytes(largePath); + + // Verify the size + expect.equal(largeBytes.length, tenMB); + + // Clean up + fs.remove(largeTmpdir); +} + +// === inflight tests === +// all of the code above is repeated here to ensure that the code +// behaves the same on inflight hosts (like AWS Lambda, or the simulator) + +test "bytes inflight" { + // constructing bytes + let rawData = bytes.fromRaw([104, 101, 108, 108, 111]); + let rawString = bytes.fromString("hello"); + let base64 = bytes.fromBase64("aGVsbG8="); + let hex = bytes.fromHex("68656c6c6f"); + let zeros = bytes.zeros(5); + + expect.equal(rawData, rawString); + expect.equal(rawData, base64); + expect.equal(rawData, hex); + expect.equal(rawData.length, zeros.length); + + // converting bytes + let asString: str = rawData.toString(); + let asRaw: Array = rawData.toRaw(); + let asBase64: str = rawData.toBase64(); + let asHex: str = rawData.toHex(); + + expect.equal(asString, "hello"); + expect.equal(asRaw, [104, 101, 108, 108, 111]); + expect.equal(asBase64, "aGVsbG8="); + expect.equal(asHex, "68656c6c6f"); + + // string decoding + let someBytes = bytes.fromRaw([195, 169]); + let someString = someBytes.toString(); // default is "utf-8" + let someString2 = someBytes.toString(encoding: "windows-1252"); + + expect.equal(someString, "é"); + expect.equal(someString2, "é"); + + // indexing bytes + expect.equal(rawData[0], 104); + expect.equal(rawData[1], 101); + expect.equal(rawData[2], 108); + expect.equal(rawData[3], 108); + expect.equal(rawData[4], 111); + + try { + rawData[5]; + expect.fail("Expected error"); + } catch err { + expect.ok(err.contains("out of bounds")); + } + + // concatenating bytes + let concated = bytes.concat(rawData, rawData, rawData); + expect.equal(concated.length, rawData.length * 3); + expect.equal(concated.toString(), "hellohellohello"); + + // slicing bytes + let sliced = rawData.slice(1, 3); + expect.equal(sliced.length, 2); + expect.equal(sliced.toString(), "el"); + + // writing and reading bytes to disk + let tmpdir = fs.mkdtemp("bytes-test-"); + let path = fs.join(tmpdir, "hello.txt"); + fs.writeBytes(path, rawData); + let readData = fs.readBytes(path); + expect.equal(readData.toString(), "hello"); + fs.remove(tmpdir); + + // test that we can work with larger files + if util.os() != "win32" { + // Creating and reading a 10MB file + let tenMB = 10 * 1024 * 1024; // 10MB in bytes + let largeTmpdir = fs.mkdtemp("large-bytes-test-"); + let largePath = fs.join(largeTmpdir, "large-file.bin"); + + // Create a 10MB file using dd + util.exec("sh", ["-c", "dd if=/dev/urandom of={largePath} bs=1M count=10 2>/dev/null"]); + + // Read the file into memory as bytes + let largeBytes = fs.readBytes(largePath); + + // Verify the size + expect.equal(largeBytes.length, tenMB); + + // Clean up + fs.remove(largeTmpdir); + } +} diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/std/bytes.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/std/bytes.test.w_compile_tf-aws.md new file mode 100644 index 00000000000..ad223c5d4b2 --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/std/bytes.test.w_compile_tf-aws.md @@ -0,0 +1,20 @@ +# [bytes.test.w](../../../../../../tests/sdk_tests/std/bytes.test.w) | compile | tf-aws + +## main.tf.json +```json +{ + "//": { + "metadata": { + "backend": "local", + "stackName": "root" + }, + "outputs": {} + }, + "provider": { + "aws": [ + {} + ] + } +} +``` + diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/std/bytes.test.w_test_sim.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/std/bytes.test.w_test_sim.md new file mode 100644 index 00000000000..e6ca0d29e42 --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/std/bytes.test.w_test_sim.md @@ -0,0 +1,12 @@ +# [bytes.test.w](../../../../../../tests/sdk_tests/std/bytes.test.w) | test | sim + +## stdout.log +```log +pass ─ bytes.test.wsim » root/Default/test:bytes inflight + +Tests 1 passed (1) +Snapshots 1 skipped +Test Files 1 passed (1) +Duration +``` +