Skip to main content

Language Spec

Not fully implemented yet

This document is a specification of the programming language, and many features are still not implemented (see project board).

0. Preface

0.1 Motivation

The Wing Programming Language (aka winglangRFC) is a general purpose programming language designed for building applications for the cloud.

What makes Wing special? Traditional programming languages are designed around the premise of telling a single machine what to do. The output of the compiler is a program that can be executed on that machine. But cloud applications are distributed systems that consist of code running across multiple machines and which intimately use various cloud resources and services to achieve their business goals.

Wing’s goal is to allow developers to express all pieces of a cloud application using the same programming language. This way, we can leverage the power of the compiler to deeply understand the intent of the developer and implement it with the mechanics of the cloud.

▲ top

0.2 Design Tenets

  • Developer Experience (DX) is priority #1 for Wing.
  • Syntax design aims to be concise and minimal, while being "batteries included"
  • at the same time in terms of tooling and DX.
  • Developers coming from other mainstream cloud languages (C#, Java, and TS) should feel right at home.
  • Public facing APIs and syntax are designed to be compatible with JSII. Wing Libraries are JSII libraries themselves.
  • All clouds are treated equally.
  • Syntactic sugar comes last.

▲ top

0.3 Table of Contents

1. General

1.1 Types

1.1.1 Primitive Types

NameExtra information
voidrepresents the absence of a type
nilrepresents the absence of a value
anyrepresents everything and anything
numrepresents numbers (doubles)
strUTF-16 encoded strings
boolrepresents true or false

any is only available to JSII imported modules.

User defined explicit "any" is supported iff declared by the user.
Almost all types can be implicitly resolved by the compiler except for "any".
"any" must be explicitly declared and annotated.

let x = 1;                  // x is a num
let v = 23.6; // v is a num
let y = "Hello"; // y is a str
let z = true; // z is a bool
let w: any = 1; // w is an any
let q: num? = nil; // q is an optional num
Equivalent TypeScript Code
const x: number = 1;
const v: number = 23.6;
const y: string = "Hello";
const z: boolean = true;
const w: any = 1;
const q: number? = undefined;

▲ top

1.1.2 Container Types

NameExtra information
Set<T>set type (set of unique items)
Map<T>map type (key-value with string keys)
Array<T>variable size array of a certain type
MutSet<T>mutable set type
MutMap<T>mutable map type
MutArray<T>mutable array type
Promise<T>promise type (inflight code)

Promise<T> is only available to JSII imported modules.

let z = {1, 2, 3};               // immutable set, Set<Num> is inferred
let zm = MutSet<num>{}; // mutable set
let y = {"a": 1, "b": 2}; // immutable map, Map<num> is inferred
let ym = MutMap<num>{}; // mutable map
let x = [1, 2, 3]; // immutable array, Array<num> is inferred
let xm = MutArray<num>[]; // mutable array
let w = new SampleClass(); // class instance (mutability unknown)
Equivalent TypeScript Code
const z: Set<number> = Object.freeze(new Set([1, 2, 3]));
const zm: Set = new Set();
const y: Map<string, number> = Object.freeze(new Map([["a", 1], ["b", 2]]));
const ym: Map = new Map();
const x: number[] = Object.freeze([1, 2, 3]);
const xm: number[] = [];
const w: SampleClass = new SampleClass();

▲ top

1.1.3 Function Types

Function type annotations are written as if they were closure declarations, with the difference that body is replaced with return type annotation.

The inflight modifier indicates that a function is an inflight function.
inflight in Wing implies async in JavaScript.

(arg1: <type1>, arg2: <type2>, ...) => <type>
inflight (arg1: <type1>, arg2: <type2>, ...) => <type>
// type annotation in wing: (num) => num
let f1 = (x: num): num => { return x + 1; };
// type annotation in wing: inflight (num, str) => void
let f2 = inflight (x: num, s: str) => { /* no-op */ };

▲ top

1.1.4 Json type

Wing has a data type called Json (alias is json). This type represents an immutable untyped JSON value, including JSON primitives (string, number, boolean), arrays (both heterogenous and homogenous) and objects (key-value maps where keys are strings and values can be any other JSON value)).

Json objects are immutable and can be referenced across inflight context.

JSON is the "wire protocol of the cloud" and as such Wing offers built-in support for it. However, since Wing is statically-typed (type must be known during compilation) and JSON is dynamically typed (type is only known at runtime), bridging is required between these two models.

Let's look at a quick example:

struct Employee { 
id: str;
name: str;

let response = http_get("/employees");
// returns something like { "items": [ { "id": "12234", "name": "bob" }, ... ] }

let employees = Array<Employee>.from_json(response.items);

for e in employees {
print("hello, ${}, your employee id is ${}");

In the above example, the http_get function returns a Json object from the server that has a single field items, with a JSON array of JSON objects, each with an id and name fields.

The expression response.items returns a Json array, and we use Array<T>.from_json to convert this array from Json to an Array<Employee>. Note that by default from_json will perform schema validation on the array and on each item (based on the declaration of the Employee struct). Literals

Literals can be defined using the Json type initializers:

let json_string  = Json "hello";
let json_number = Json 123;
let json_bool = Json true;
let json_array = Json [ 1, 2, 3 ];
let json_obj = Json { boom: 123 };
let json_mut_obj = MutJson {
hello: 123,
world: [ 1, "cat", 3 ], // <-- heterogenous array
"boom boom": { hello: 1233 } // <-- non-symbolic key

Every value within a Json array or object also has a type of Json. JSON objects

To access a field within an object, use the . notation:

let boom: Json = json_obj.boom;

Trying to access a non-existent field will fail at runtime. For example:

// RUNTIME ERROR: Uncaught TypeError: Cannot read properties of undefined (reading 'world')

Like in JavaScript, it is also possible to access object fields using []:

let foo = j["my-field"].your_field["their-field"];

To obtain an array of all the keys within a JSON object use the Json.keys(o) method.

let j = Json { hello: 123, world: [ 1, 2, 3 ] };
assert(Json.keys(j) == ["hello", "world"]);

To obtain an array of all the values, use Json.values(o). To obtain an array of all key/value pairs use Json.entries(o) (P2):

assert(Json.values(j).equals([ Json 123, Json [ 1, 2, 3 ] ]));
[ Json "hello", Json 123 ],
[ Json "world", Json [ 1, 2, 3 ] ]

NOTE: values() and entries() return an array inside a Json object because at the moment we cannot represent heterogenous arrays in Wing. JSON arrays

To access an array element, use the [] notation:

let item2 = json_array[2]; // type: Json

Trying to index a value that is not an array will return JavaScript undefined. Assignment from native types

It is also possible to assign the native str, num, bool and Array<T> values and they will implicitly be casted to Json:

let my_str: str = "hello";
let my_num: num = 183;
let my_bool: bool = true;
let my_arr: Array<num> = [1,2,3];

let json_obj = Json {
a: my_string,
b: my_num,
c: my_bool,
d: my_arr
}; Assignment to native types

We only allow implicit assignment from safe to unsafe types because otherwise we cannot guarantee safety (e.g. from str to Json but not from Json to str), so this won't work:

let j = Json "hello";
let s: str = j;
// ^ cannot assign `Json` to `str`.

To assign a Json to a strong-type variable, use the from_json() static method on the target type:

let my_str = str.from_json(json_string);
let my_number = num.from_json(json_number);
let my_arr = Array<num>.from_json(json_array); Schema validation

All from_json() methods will validate that the runtime type is compatible with the target type in order to ensure type safety (at a runtime cost):

str.from_json(json_number);      // RUNTIME ERROR: unable to parse number `123` as a string.
num.from_json(Json "\"hello\""); // RUNTIME ERROR: unable to parse string "hello" as a number

let my_array = Json [1,2,3,"hello"];
Array<num>.from_json(my_array); // RUNTIME ERROR: unable to parse `[1,2,3,"hello"]` as an array of `num`.

Use unsafe: true to disable this check at your own risk (P2):

let trust_me = Json [1,2,3];
let x = Array<num>.from_json(trust_me, unsafe: true);
assert( == 2);

For each from_json(), there is a try_from_json() method which returns an optional T? which indicates if parsing was successful or not:

let s = str.try_from_json(my_json) ?? "invalid string"; Assignment to user-defined structs

All structs also have a from_json() method that can be used to parse Json into a struct:

struct Contact {
first: str;
last: str;
phone: str?;

let j = Json { first: "Wing", last: "Lyly" };
let my_contact = Contact.from_json(j);
assert(my_contact.first == "Wing");

When a Json is parsed into a struct, the schema will be validated to ensure the result is type-safe:

let p = Json { first: "Wing", phone: 1234 };
// RUNTIME ERROR: unable to parse Contact:
// - field "last" is required and missing
// - field "phone" is expected to be a string, got number.

Same as with primitives and containers, it is possible to opt-out of validation using unsafe: true:

let p = Json { first: "Wing", phone: 1234 };
let x = Contact.from_json(p, unsafe: true);
assert(x.last.len > 0);
// RUNTIME ERROR: Cannot read properties of undefined (reading 'length')

Struct parsing is partial by default. This means that parsing is successful even if the Json includes extraneous fields:

let p = Json { first: "hello", last: "world", another_field: "ignored" };
let c = Contact.from_json(p);
assert(c.first == "hello");
assert(c.last == "world");
// `c.another_field` is not a thing

This can be disabled using partial: false (P2):

Contact.from_json(Json { first: "hello", last: "world", another_field: "ignored" }, partial: false);
// RUNTIME ERROR: cannot parse Contact due to extraneous field "another_field" Schemas

Structs have a schema static method which returns a JsonSchema object (P2):

let schema = Contact.schema();
schema.validate(j); Mutability

To define a mutable JSON container, use the MutJson type:

let my_obj = MutJson { hello: "dear" };

Now you can mutate the contents by assigning values:

let foo_num = 123; = "world";
my_obj.dang = [1,2,3,4];
my_obj.sub_object = {};
my_obj.sub_object.arr = [1,"hello","world"]; = foo_num;

For the sake of completeness, it is possible to also define primitives using MutJson but that's not very interesting because there is no way to mutate them:

let foo = MutJson "hello";
// ok what now?

Use the Json.clone() and Json.clone_mut() methods to get a deep clone of the object:

let mut_json = MutJson { hello: 123 };
let immut = Json.clone(mut_json);
mut_json.hello = 999;
assert(immut.hello == 123);

To delete a key from an object, use the Json.delete() method:

let my_obj = MutJson { hello: 123, world: 555 };
Json.delete(my_obj, "world");

let immut_obj = Json { hello: 123 };
Json.delete(immut_obj, "hello");
// ^^^^^^^^^ expected `JsonMut`

To modify a Json array, you will need to parse it into a native MutArray and then modify it. This implies that at the moment, it is not possible to mutate heterogenous JSON arrays:

let j1 = MutJson { hello: [1,2,3,4] };
let a1 = MutArray<num>.from_json(j1);

j1.hello = a1;

We will need to revisit this as we progress if this is a major use case. Serialization

The Json.stringify(j: Json): str static method can be used to serialize a Json as a string (JSON.stringify):

assert(Json.stringify(json_string) == "\"hello\"");
assert(Json.stringify(json_obj) == "{\"boom\":123}");
assert(Json.stringify(json_mut_obj, indent: 2) == "{\n\"hello\": 123,\n" \"world\": [\n 1,\n 2,\n 3\n ],\n \"boom\": {\n \"hello\": 1233\n }\n}");

The Json.parse(s: str): Json static method can be used to parse a string into a Json:

let j_array = Json.parse("[1,2,3]");
let arr = Array<num>.from_json(j_array);

Json.try_parse returns an optional:

let o = Json.try_parse("xxx") ?? Json [1,2,3]; Equality, diff and patch

The Json.equals(lhs: Json, rhs: Json): bool static method can be used to determine if two values are equal (recursively comparing arrays and objects):

assert(Json.equals(json_string, Json "hello"));
assert(Json.equals(json_obj, { boom: [ 1, 2, 3 ] }));
assert(!Json.equals(Json { hello: [ 1, 2, 3 ] }, Json { hello: [ 1, 2 ] }));

The Json.diff(lhs: Json, rhs: Json): JsonPatch static method can be used to calculate the deep difference between two JSON values. It returns a list of differences in json-patch format (P2).

let j1 = Json {
baz: "qux",
foo: "bar"

let j2 = Json {
baz: "boo",
hello: ["world"]

assert(Json.diff(j1, j2) = [
{ op: JsonPatch.REPLACE, path: "/baz", value: "boo" },
{ op: JsonPatch.ADD, path: "/hello", value: ["world"] },
{ op: JsonPatch.REMOVE, path: "/foo" }

The Json.patch(j: Json, patch: JsonPatch): Json static method applies a JsonPatch to a Json object (P2). Printing

A Json value can be printed using print(), in which case it will be pretty-formatted:

print("my object is: ${json_obj}");
// is equivalent to
print("my object is: ${Json.stringify(json_obj, indent: 2)}");

This will output:

my object is: {
boom: 123

It is also legal to just print a json object:


▲ top

1.1.5 Duration

The Duration (alias duration) type represents a time duration.

Duration literals are numbers with m, s, h suffixes:

let one_minute = 1m;
let two_seconds = 2s;
let three_hours = 3h;
let half_minute: duration = 0.5m;


assert(one_minute.seconds == 60);
assert(half_minute.seconds == 30);
assert(three_hours.minutes == 180);

Duration objects are immutable and can be referenced across inflight context.

1.1.6 Datetime

The Datetime (alias datetime) type represents a single moment in time in a platform-independent format.

Datetime objects are immutable and can be referenced across inflight context.

Here is the initial API for the Datetime type:

struct DatetimeComponents {
year: num;
month: num;
day: num;
hour: num;
min: num;
sec: num;
ms: num;
tz: num; // timezone offset in minutes from UTC

class Datetime {
static utc_now(): Datetime; // returns the current time in UTC timezone
static system_now(): Datetime; // returns the current time in system timezone
static from_iso(iso: str): Datetime; // creates an instance from an ISO-8601 string
static from_components(c: DatetimeComponents): Datetime;

timestamp: num; // Date.valueOf()/1000 (non-leap seconds since epoch)
timestamp_ms: num; // Date.valueOf() (non-leap milliseconds since epoch)

hours: num; // Date.getHours()
min: num; // Date.getMinutes()
sec: num; // Date.getSeconds()
ms: num; // Date.getMilliseconds()
day: num; // Date.getDay()
month: num; // Date.getMonth()
year: num; // Date.getFullYear()

timezone: num; // Date.getTimezoneOffset() (offset in minutes from UTC)
utc: Datetime; // returns the same time in UTC timezone

to_iso(): str; // returns ISO-8601 string

A few examples:

let now = Datetime.utc_now();
print("It is now ${now.month}/${}/${now.year} at ${now.hours}:${now.min}:${now.sec})");
assert(now.timezone == 0); // UTC

let t1 = DateTime.from_iso("2023-02-09T06:20:17.573Z");
print("Timezone is GMT${d.timezone() / 60}"); // output: Timezone is GMT-2
print("UTC: ${t1.utc.to_iso())}"); // output: 2023-02-09T06:21:03.000Z

▲ top

1.2 Utility Functions

NameExtra information
printprints anything serializable.
throwcreates and throws an instance of an exception
panicexits with a serializable, dumps the trace + a core dump
assertchecks a condition and panics if evaluated to false

Wing is a statically typed language, so attempting to redefine any of the above functions, just like any other "symbol" will result in a compile-time error.

Above functions can accept variadic arguments of any type except throw which only accepts one argument and that is the message to be contained in the error.

"panic" is a fatal call by design. If intention is error handling, panic is the last resort. Exceptions are non fatal and should be used instead for effectively communicating errors to the user.

print(23, "Hello", true, { "a": 1, "b": 2 });
throw("a recoverable error occurred");
panic("a fatal error encountered", [1,2]);
assert(x > 0, x < 10);
Equivalent TypeScript Code
console.log(23, "Hello", true, Object.freeze(new Map([["a", 1], ["b", 2]])));
// throws
throw new Error("a recoverable error occurred");
// calling panic in wing is fatal
(() => {
console.error("Something went wrong", [1,2]);
// generate core dump
// show stack trace
// multiple assertions
(() => { assert.ok(x > 0); assert.ok(x < 10); })();

▲ top

1.3 Phase Modifiers

In Wing, we differentiate between code that executes during compilation and code that executes after the application has been deployed by referring to them as preflight and inflight code respectively.

The default (and implicit) execution context in Wing is preflight. This is because in cloud applications, the entrypoint is the definition of the app's cloud architecture, and not the code that runs within a specific machine within this cloud infrastructure.

The phase modifier inflight is allowed in the context of declaring interface and resource members (methods, fields and properties). Example code is shown in the resources section.

resource Bucket {
// preflight method
allow_public_access() {


// inflight method
inflight put(key: str, contents: str): void {


Inflight members can only be accessed from an inflight context (an inflight method or an inflight closure) and preflight members can only be accessed from a preflight context (a preflight method or a preflight closure).

The inflight modifier is allowed when defining a function closure:

let handler = inflight () => {
print("hello, world");

For example (continuing the Bucket example above):

let bucket = new Bucket();
// OK! We are calling a preflight method from a preflight context
// ERROR: cannot call inflight methods from preflight context
bucket.put("file.txt", "hello");

let handler = inflight () => {
// now we are in inflight context
// OK! We are calling an inflight methods from an inflight context
bucket.put("file.txt", "hello");

Resources can only be instantiated within preflight context:

resource Bar {}

new Bar(); // OK! Bar is a resource

let handler2 = inflight() => {
new Bar(); // ERROR: cannot instantiate a resource from an inflight context

Bridge between preflight and inflight is crossed with the help of immutable data structures, "structs" (user definable and Struct), and the capture mechanism.

Preflight resource methods and initializers can receive an inflight function as an argument. This enables resources to define code that will be executed on the deployed resource (lambda functions, docker, virtual machines etc).

▲ top

1.4 Storage Modifiers

A storage modifier is a keyword that specifies the placement of a function or variable in the program memory once compiled. Some declarations might have a temporary storage (such as a local closure definition), while others might have a permanent storage (such as a global variable).

Currently the only storage modifier is static. static indicates a definition is only available once per program and for the entire duration of that program. All statics must be defined inline and initialized right away.
Statics are not allowed on structs or interfaces.

Statics are both supported in inflight as well as preflight mode of execution.

A declaration for a static member is a member declaration whose declaration specifiers contain the keyword static. The keyword static must appear before other specifiers. More details in the classes section.

The name of any static data member and static member function must be different from the name of the containing class regardless of the casing.

Code samples for static are not shown here. They are shown in the relevant sections below.

To avoid confusion, it is invalid to have a static and a non-static with the same name. Overloading a static is allowed however.
Accessing static is done via the type name and the . operator.

▲ top

1.5 Access Modifiers

Visibility inference is done with the following rules:

  • Default visibility is private for all members. If modifiers are missing, the symbol is assumed private by the compiler and not exported.
  • public is to declare a symbol that is visible for and exported publicly.
  • protected is to declare a symbol that is visible for and exported publicly but only for the class and its subclasses.
  • internal is to declare a symbol that is visible for and exported publicly but only for the current compilation unit.

Accessing fields, members, or structured data is done with ..

Visibility modifiers can be applied to members of classes and resources.
Mixing protected and internal is not allowed.

▲ top

1.6 Reassignability

Re-assignment to variables that are defined with let is not allowed in Wing.

Variables can be reassigned to by adding the var modifier:

// wing
let var sum = 0;
for item in [1,2,3] {
sum = sum + item;

Re-assignment to class fields is allowed if field is marked with var. Examples in the class section below.

var is available in the body of class declarations. Assigning var to immutables of the same type is allowed. That is similar to assigning non readonlys to readonlys in TypeScript.

By default function closure arguments are non-reassignable. By prefixing var to an argument definition you can make a re-assignable function argument:

// wing
let f = (arg1: num, var arg2: num) => {
if (arg2 > 100) {
// We can reassign a value to arg2 since it's marked `var`
args2 = 100;

▲ top

1.7 Optionality

Symbol ? can mark a type as optional.
Optionality means the value behind type can be either present or nil.

Rules of optionality apply to the entire new container type of type? and not the value behind it (type).

Using the ?? operator allows one to safely access the value behind an optional variable by always providing a default, in case the variable is nil. This forces a value to be present for the l-value (left hand side of the assignment operator and guarantees what's returned is type-stripped from its ? keyword).

let x? = 44;
let y = x ?? 55;
Equivalent TypeScript Code
const x: number? = 44;
const y: number = x ?? 55;

▲ top

1.8 Type Inference

Type can optionally be put between name and the equal sign, using a colon.
Partial type inference is allowed while using the ? keyword immediately after the variable name.

When type annotation is missing, type will be inferred from r-value type.
r-value refers to the right hand side of an assignment here.

All defined symbols are immutable (constant) by default.
Type casting is generally not allowed unless otherwise specified.

Function arguments and their return type is always required. Function argument type is inferred iff a default value is provided.

let i = 5;
let m = i;
let arr_opt? = MutArray<num>[];
let arr = Array<num>[];
let copy = arr;
let i1? = nil;
let i2: num? = i;
Equivalent TypeScript Code
const i: number = 5;
const m: number = i;
const arr_opt: number[]? = [];
const arr: number[] = Object.freeze([]);
const copy: number[] = Object.freeze([...arr]);
const i1: any = undefined;
const i2: number? = i;

▲ top

1.9 Error Handling

Exceptions and try/catch/finally is the error mechanism. Mechanics directly translate to JavaScript. You can create a new exception with a throw call.

In the presence of try, both catch and finally are optional but at least one of them must be present. In the presence of catch the variable holding the exception (e in the example below) is optional.

panic is meant to be fatal error handling.
throw is meant to be recoverable error handling.

An uncaught exception is considered user error but a panic call is not. Compiler must guarantee exception safety by throwing a compile error if an exception is expected from a call and it is not being caught.

try {
let x? = 1;
throw("hello exception");
} catch e {
} finally {
Equivalent TypeScript Code
try {
let x: number? = 1;
throw new Error("hello exception");
} catch (e) {
} finally {

▲ top

Wing recommends the following formatting and naming conventions:

  • Interface names should start with capital letter "I"
  • Class, struct, interface, and resource names should be TitleCased
  • Members of classes, interfaces, and resources cannot share the same TitleCased representation as the declaring expression itself.
  • Parentheses are optional in expressions. Any Wing expression can be surrounded by parentheses to enforce precedence, which implies that the expression inside an if/for/while statement may be surrounded by parentheses.

▲ top

1.11 Memory Management

There is no implicit memory de-allocation function, dynamic memory is managed by Wing and is garbage collected (relying on JSII target GC for the meantime).

▲ top

1.12 Execution Model

Execution model currently is delegated to the JSII target. This means if you are targeting JSII with Node, Wing will use the event based loop that Node offers.

In Wing, writing and executing at root block scope level is forbidden except for the entrypoint of the program. Root block scope is considered special and compiler generates special instructions to properly assign all resources to their respective scopes recursively down the constructs tree based on entry.

Entrypoint is always a Wing source with an extension of .w. Within this entry point, a root resource is made available for all subsequent resources that are initialized and instantiated. Type of the root resource is determined by the target being used by the compiler. The root resource might be of type App in AWS CDK or TerraformApp in case of CDK for Terraform target.

Following "shimming" is only done for the entrypoint file and nowhere else. Type of the "shim" changes from cdk.Stack to TerraformStack for cdk-tf.

// Wing Entrypoint Code:
let a = MyResource();
let b = MyResource() be "my-resource";
Equivalent TypeScript Code
(new class extends cdk.Stack {
constructor(scope: constructs.Construct, id: string) {
const a = new MyResource(this, "MyResource");
const b = new MyResource(this, "my-resource");
})(new cdk.App(), "WingEntry");

▲ top

1.13 Asynchronous Model

Wing builds upon the asynchronous model of JavaScript currently and expands upon it with new keywords and concepts. The async keyword of JavaScript is replaced with inflight in Wing deliberately to indicate extended functionality.

Main concepts to understand:

  • "preflight" implies synchronous execution.
  • inflight implies asynchronous execution

Contrary to JavaScript, any call to an async function is implicitly awaited. As a result, await in Wing is a rarely used keyword, since its use is implied by the inflight keyword. await is only used when you want a defered Promise to be fulfilled before execution flow continues.

The Wing compiler emits awaits when encountering Promise<T> types as rvalues in expressions. Use the defer keyword to defer the resolution of a promise and obtain a Promise<T> type instead (a.k.a unawait what the compiler does).

The Promise<T> type is not allowed to hold nested promises in T.

2. Statements

2.1 bring

"bring" statement can be used to import and reuse code from other Wing files or other JSII supported languages. The statement is detailed in its own section in this document: Module System.

▲ top

2.2 break

break statement allows to end execution of a cycle. This includes for and while loops currently.

for let i in 1..10 {
if i > 5 {
Equivalent TypeScript Code
for (let i = 1; i < 10; i++) {
if (i > 5) {

▲ top

2.3 continue

continue statement allows to skip to the next iteration of a cycle. This includes for and while loops currently.

for let i in 1..10 {
if i > 5 {
Equivalent TypeScript Code
for (let i = 1; i < 10; i++) {
if (i > 5) {

▲ top

2.4 return

return statement allows to return a value or exit from a called context.

class MyClass {
public myPublicMethod() {}
private _myPrivateMethod(): void {}
protected myProtectedMethod(): nil { return nil; }
internal _myInternalMethod(): str { return "hi!"; }
Equivalent TypeScript Code
class MyClass {
public myPublicMethod(): void {}
private myPrivateMethod(): undefined {}
protected myProtectedMethod(): undefined { return undefined; }
// specific compiled instruction is up to implementation of the compiler
public __wing__internal_myInternalMethod(): string { return "hi!"; }

▲ top

2.5 defer/await

Read Asynchronous Model section as a prerequisite.

You mostly do not need to use defer and await keywords in Wing.
"defer" prevents the compiler from awaiting a promise and grabs a reference.
"await" and "Promise" are semantically similar to JavaScript's promises.
"await" statement is only valid in inflight function declarations.
awaiting non promises in Wing is a no-op just like in JavaScript.

// Wing program:
class MyClass {
inflight foo(): num {
let w = defer some_promise();
let x = await w;
return x;
inflight boo(): num {
let x = some_promise();
return x;
Equivalent TypeScript Code
class MyClass {
async foo(): Promise<number> {
const w = some_promise();
const x = Object.freeze(await w);
return x;
async boo(): Promise<number> {
const x = Object.freeze(await some_promise());
return x;

▲ top

2.6 if

Flow control can be done with if/elif/else statements.
The if statement is optionally followed by elif and else.

// Wing program:
let x = 1;
let y = "sample";
if x == 2 {
print("x is 2");
} elif y != "sample" {
print("y is not sample");
} else {
print("x is 1 and y is sample");
Equivalent TypeScript Code
const x: number = 1;
const y: string = "sample";
if (x === 2) {
console.log("x is 2");
} else if (y !== "sample") {
console.log("y is not sample");
} else {
console.log("x is 1 and y is sample");

▲ top

2.7 for statement is used to iterate over a array or set.
Type annotation after an iteratee (left hand side of in) is optional.
The loop invariant in for loops is implicitly re-assignable (var).

// Wing program:
let arr = [1, 2, 3];
let set = {1, 2, 3};
for item in arr {
for item: num in set {
for item in 0..100 {
Equivalent TypeScript Code
const arr: number[] = Object.freeze([1, 2, 3]);
const set: Set<number> = Object.freeze(new Set([1, 2, 3]));
for (const item of arr) {
for (const item of set) {
// calling 0..100 does not allocate, just returns an iterator
function* iterator(start, end) {
let i = start;
while (i < end) yield i++;
while (i > end) yield i--;
const iter = iterator(0, 100);
for (const val of iter) {

▲ top

2.8 while

while statement is used to execute a block of code while a condition is true.

// Wing program:
while call_some_function() {
Equivalent TypeScript Code
while (call_some_function()) {

▲ top

3. Declarations

3.1 Structs

Structs are loosely modeled after typed JSON literals in JavaScript.
Structs are defined with the struct keyword.
Structs are "bags" of immutable data.

Structs can only have fields of primitive types, resources, and other structs.
Array, set, and map of above types is also allowed in struct field definition.
Visibility, storage and phase modifiers are not allowed in struct fields.

Structs can inherit from multiple other structs.

// Wing program:
struct MyDataModel1 {
field1: num;
field2: str;
struct MyDataModel2 {
field3: num;
field4: bool?;
struct MyDataModel3 extends MyDataModel1, MyDataModel2 {
field5: str;
let s1 = MyDataModel1 { field1: 1, field2: "sample" };
let s2 = MyDataModel2 { field3: 1, field4: true };
let s3 = MyDataModel2 { field3: 1 };
let s4 = MyDataModel3 {
field1: 12,
field2: "sample",
field3: 11,
field4: false,
field5: "sample"
Equivalent TypeScript Code
interface MyDataModel1 {
public readonly field1: number;
public readonly field2: string;
interface MyDataModel2 {
public readonly field3: number;
public readonly field4?: boolean;
interface MyDataModel3 extends MyDataModel1, MyDataModel2 {
public readonly field5: string;
public readonly field6: number;
const s1: MyDataModel1 = { field1: 1, field2: "sample" };
const s2: MyDataModel2 = { field3: 1, field4: true };
const s3: MyDataModel2 = { field3: 1, field4: undefined };
const s4: MyDataModel3 = {
field1: 12,
field2: "sample",
field3: 11,
field4: false,
field5: "sample",
field6: 11

▲ top

3.2 Classes

Classes consist of fields and methods in any order.
The class system is single-dispatch class based object orientated system.
Classes are instantiated with the new keyword.

A class member function that has the name init is considered to be a class constructor (or initializer, or allocator).

class Name extends Base impl IMyInterface1, IMyInterface2 {
init() {
// constructor implementation
// order is up to user
this._field1 = 1;
this._field2 = "sample";

// class fields (private by due to having leading underscore)
_field1: num;
_field2: str;

// static method (access with Name.static_method(...))
public static static_method(arg: type, arg: type, ...) { /* impl */ }
// private method
private _private_method(arg: type, arg: type, ...): type { /* impl */ }
// visible to outside the instance
public public_method(arg:type, arg:type, ...) { /* impl */ }
// visible to children only
protected protected_method(type:arg, type:arg, ...) { /* impl */ }
// public in current compilation unit only
internal _internal_method3(type:arg, type:arg, ...): type { /* impl */ }

Default initialization does not exist in Wing. All member fields must be initialized in the constructor. Absent initialization is a compile error. All field types, including the optional types must be initialized. Optionals are initialized to nil if omitted, unless the type is nil?, which in that case, absent initialization is a compile error.

Member function and field access in constructor with the "this" keyword before all fields are initialized is invalid and should throw a compile error.

In other words, the this keyword is immutable to its field access operator . before all the member fields are properly initialized. The behavior is similar to JavaScript and TypeScript in their "strict" mode.

class Foo {
x: num;
init() { this.x = 1; }
class Bar {
y: num;
z: Foo;
init() {
// this.print() // is compile error here
this.y = 1;
// this.print() // is also compile error here
this.z = new Foo();
this.print(); // OK to call here
public print() {
let a = new Bar();
a.print(); // prints 20.

Overloading methods is allowed. This means functions can be overloaded with many signatures only varying in the number of arguments and their unique type order. Overloading the constructor is also allowed.
Inheritance is allowed with the extends keyword. super can be used to access the base class, immediately up the inheritance chain (parent class).

Calling using the member access operator . before calling super in inherited classes is forbidden. The behavior is similar to JavaScript and TypeScript in their "strict" mode.

class Foo {
x: num;
init() { this.x = 0; }
public method() { }
class Boo extends Foo {
init() {
// this.x = 10; // compile error
this.x = 10; // OK
public override method() {
// override implementation

Classes can inherit and extend other classes using the extends keyword.
Classes can implement interfaces iff the interfaces does not contain inflight. You can use the keyword final to stop the inheritance chain.

class Foo {
x: num;
init() { this.x = 0; }
public method() { }
final class Boo extends Foo {
init() { super(); this.x = 10; }
public override method() {
// override implementation
// compile error
// class FinalBoo extends Boo {}

By default all methods are virtual. But if you are about to override a method, you need to explicitly provide the keyword override.
Static, private, and internal methods cannot be and are not virtual.

Statics are not inherited.
As a result, statics can be overridden mid hierarchy chain. Access to statics is through the class name that originally defined it <class name>.Foo.

Child class must not introduce additional signatures (overloads) for overridden (virtual) methods.

Multiple inheritance is invalid and forbidden.
Multiple implementations of various interfaces is allowed.
Multiple implementations of the same interface is invalid and forbidden.

In methods if return type is missing, : void is assumed.

▲ top

3.3 Resources

Resources cannot be defined and instantiated in inflight contexts.

Resources provide first class composite pattern support in Wing. They are modeled after and leverage the constructs programming model and as such are fully interoperable with CDK constructs.
Resources can be defined like so:

// Wing Code:
resource Foo {
init() { /* initialize preflight fields */ } // preflight constructor
inflight init() {} // optional client initializer

// inflight members
inflight foo(arg: num): num { return arg; }
inflight boo(): num { return 32; }

// inflight fields
inflight field1: num;
inflight field2: str;
inflight field3: bool;

// preflight members
foo(arg: num): num { return arg; }
boo(): num { return 32; }

// preflight fields
field4: num;
field5: str;
field6: bool;

// re-assignable class fields, read about them in the mutability section
var field4: num;
var field5: str;

Resources all have a scope and a unique ID. Compiler provides an implicit scope and ID for each resource, both overrideable by user-defined ones in constructor.

The default for scope is this, which means the scope in which the resource was defined. The implicit ID is the type name of the resource iff the resource type is the only resource type being used in the current scope. In other words, if there are multiple resources of the same type defined in the same scope, they must all have an explicit id.

Resources instantiated at block scope root level of entrypoint are assigned the root app construct as their default implicit scope.

Resource instantiation syntax uses the let keyword the same way variables are declared in Wing. The difference is be/in keywords afterwards.

let <name>[: <type>] = <resource> [be <id>] [in <scope>];
// Wing Code:
let a = Foo(); // with default scope and id
let a = Foo() in scope; // with user-defined scope
let a = Foo() be "custom-id" in scope; // with user-defined scope and id
let a = Foo(...) be "custom-id2" in scope; // with constructor arguments

"id" must be of type string. It can also be a string literal with substitution support (normal strings as well as shell strings).
"scope" must be an expression of resource type.

Resources can be captured into inflight functions and once that happens, inside the capture block only the inflight members are available.

Resources can extend other resources (but not structs) and implement interfaces.

Declaration of fields with different phases is not allowed due to requirement of having inflight fields of same name being implicitly initialized by the compiler
but declaration of methods with different phases is allowed.

▲ top

3.4 Interfaces

Interfaces represent a contract that a class or resource must fulfill.
Interfaces are defined with the interface keyword.
Both preflight and inflight signatures are allowed.
impl keyword is used to implement an interface or multiple interfaces that are separated with commas.

All methods of an interface are implicitly public and cannot be of any other type of visibility (private, protected, etc.).

// Wing program:
interface IMyInterface1 {
field1: num;
method1(x: num): str;
interface IMyInterface2 {
inflight field2: str;
inflight method2(): str;
resource MyResource impl IMyInterface1, IMyInterface2 {
inflight field2: str;
inflight init(x: num) {
// inflight client initialization
this.field2 = "sample";
this.field1 = x;
method1(x: num): str {
return "sample: ${x}";
inflight method2(): str {
return this.field2;
Equivalent TypeScript Code
interface IMyInterface1 {
public readonly field1: number;
public method1(x: number): string;
interface IMyInterface2 {
public readonly __inflight__field2: string;
public __inflight__method2(): string;
// this is only shown as a hypothetical sample
class MyResource extends constructs.Construct
implements IMyInterface1, IMyInterface2 {
public readonly field1: number;
public readonly __inflight__field2: string;
public __inflight__constructor() {
// inflight client initialization
this.field1 = x;
this.__inflight__field2 = "sample";
public __inflight__method2(): string {
return this.__inflight__field2;
public method1(x: number): string {
return `sample: ${x}`;

▲ top

3.5 Variables

Let let be let. (Elad B. 2022)

let <name>[: <type>] = <value>;

Assignment operator is =.
Assignment declaration keyword is let.
Type annotation is optional if a default value is given.

let n = 10;
let s: str = "hello";
Equivalent TypeScript Code
const n: number = 10;
const s: string = "hello";

▲ top

3.6 Functions

3.6.1 Closures

It is possible to create closures.
It is not possible to create named closures.
However, it is possible to create anonymous closures and assign to variables (function literals). Inflight closures are also supported.

// preflight closure:
let f1 = (a: num, b: num) => { print(a + b); }
// inflight closure:
let f2 = inflight (a: num, b: num) => { print(a + b); }
// OR:
// preflight closure:
let f4 = (a: num, b: num): void => { print(a + b); }
// inflight closure:
let f5 = inflight (a: num, b: num): void => { print(a + b); }

▲ top

3.6.2 Promises

Promises in Wing are defined with Promise<T> syntax.
All inflight functions implicitly wrap their return type in Promise<T>.

let schema = inflight (): Struct => {
return some_call_for_schema();
Equivalent TypeScript Code
const schema = async (): Promise<Struct> => {
return await some_call_for_schema();

▲ top

3.6.3 Struct Expansion

If the last argument of a function call is a struct, then the struct in the call is "expandable" with a special : syntax.
In this calling signature, order of struct members do not matter.
Partial struct expansion in terms of supplying less number of arguments than the number of fields on type of the struct expected is not allowed. Omitting nils is allowed with the same rules as explicit initialization in class constructors.

This style of expansion can be thought of as having positional arguments passed in before the final positional argument, which if happens to be a struct, it can be passed as named arguments. As a result of named arguments being passed in, it is safe to omit optional struct fields, or have order of arguments mixed.

struct MyStruct {
field1: num;
field2: num;
let f = (x: num, y: num, z: MyStruct) => {
print(x + y + z.field1 + z.field2);
// last arguments are expanded into their struct
f(1, 2, field1: 3, field2: 4);
// f(1, 2, field1: 3); // can't do this, partial expansion is not allowed

▲ top

3.6.4 Variadic Arguments

If the last argument of a function type is the ...args keyword followed by a Array type, then the function accepts typed variadic arguments. Expansion of variadic arguments is not supported currently and the container of variadic arguments is accessible with the args key like a normal array instance.

let f = (x: num, ...args: Array<num>) => {
print(x + y + args.len);
// last arguments are expanded into their struct
f(1, 2, 3, 4, 5, 6, 34..100);

▲ top

3.7 Arrays

Arrays are dynamically sized in Wing and are defined with the [] syntax.
Individual array items are also accessed with the [] syntax.
Arrays are similar to dynamically sized arrays or vectors in other languages.

let arr1 = [1, 2, 3];
let arr2 = ["a", "b", "c"];
let arr3 = MutArray<str>["a1", "b2", "c3"];
let l = arr1.len + arr2.len + arr3.len + arr1[0];
Equivalent TypeScript Code
const arr1: number[] = Object.freeze([1, 2, 3]);
const arr2: string[] = Object.freeze(["a", "b", "c"]);
const arr3: string[] = ["a1", "b2", "c3"];
const l = arr1.length + arr2.length + arr3.length + arr1[0];

▲ top

3.8 Enumeration

Enumeration type (enum) is a type that groups a list of named constant members. Enumeration is defined by writing enum, followed by enumeration name and a list of comma-separated constants in a {}. Last comma is optional in single line definitions but required in multi line definitions.
Naming convention for enums is to use "TitleCase" for name ALL_CAPS for members.

enum SomeEnum { ONE, TWO, THREE };
enum MyFoo {
let x = MyFoo.B;
let y = x; // type is MyFoo
Equivalent TypeScript Code
enum SomeEnum { ONE, TWO, THREE };
enum MyFoo {
const x: MyFoo = MyFoo.B;
const y: MyFoo = x;

nameof operator is used to get the name of a constant member at compile time. For example nameof(MyEnum.MEMBER) resolves to "MEMBER" at compile time.

This allows painless conditionals when enums are serialized and deserialized over the wire without littering the source with strings everywhere. Compare:

// Wing Code:
enum SomeEnum { ONE, TWO, THREE };
let some_val: str = "ONE";
if some_val == nameof(SomeEnum.ONE) {
// whatever1
} elif some_val == nameof(SomeEnum.TWO) {
// whatever2

Which is functionally equivalent to:

// Wing Code:
enum SomeEnum { ONE, TWO, THREE };
let some_val: str = get_enum_serialized_from_network();
if some_val == "ONE" {
// whatever1
} elif some_val == "TWO" {
// whatever2

▲ top

3.9 Computed Properties

You may use the following syntax to define computed properties.
Computed properties are syntactic sugar for getters and setters which themselves are syntactic sugar for methods, therefore omitting either one is acceptable.

"Read block" must always return a value of the same type as the property.

Keyword new can be used inside the write block to access the incoming r-value of the assignment (write) operation. new is always the same type as the type of the property itself.

Both preflight and inflight computed properties are allowed.
Keyword var behind computed properties is not allowed. inflight computed properties are also allowed.

// Wing Code:
struct Vec2 {
x: num;
y: num;

class Rect {
var size: Vec2;
var origin: Vec2;

center: Vec2 {
read {
let center_x = origin.x + (size.width / 2);
let center_y = origin.y + (size.height / 2);
return Vec2(x: center_x, y: center_y);
write {
origin.x = new.x - (size.width / 2);
origin.y = new.y - (size.height / 2);

inflight prop: num { /* ... */ }
Equivalent TypeScript Code
interface Vec2 { x: number; y: number; }
class Rect {
size: Vec2;
origin: Vec2;
// computed property with a getter and setter block
get center(): Vec2 {
let center_x = origin.x + (size.width / 2);
let center_y = origin.y + (size.height / 2);
return Vec2(x: center_x, y: center_y);
set center(_new: Vec2) {
origin.x = _new.x - (size.width / 2);
origin.y = _new.y - (size.height / 2);

▲ top

4. Module System

The module system in Wing uses the "bring" expression to reuse code.
bring expression allows code to "import" functions, classes and variables from other files, to allow reusability.
bring expression is only allowed at the top of the file before any other code. Comments before the first bring expression are valid.

4.1 Imports

To import a JSII / Wing package under a named import, you may use the following syntax:

bring std; // from std bring * as std;
bring cloud; // from cloud bring * as cloud;
bring "path/to/what.js" as what; // from "path/to/what.js" bring * as what;

Currently, "bringing" other Wing files is treated as a preprocessor step and it acts like C #includes. Symbols collision is fatal in this style of imports.

▲ top

4.2 Exports

In preflight, anything with public at block scope level is importable.
This includes functions, classes, structs, interfaces and resources.
In inflight, the above excluding resources are importable.
Variables are not exportable.

Resources are not usable in inflight functions. There is no synthesizer inside and no deployment system in the inflight body to synthesize inflight resources.

"Bringing" other Wing files currently ignores exports, but bringing JSII modules respect the visibility of JSII module exports.

▲ top

5. Interoperability

5.1 JSII Interoperability

5.1.1 External Libraries

You may import JSII modules in Wing and they are considered resources if their JSII type manifest shows that the JSII module is a construct. Wing is a consumer of JSII modules currently.

bring "aws-cdk-lib" as cdk;
let bucket = cdk.aws_s3.Bucket(
public_access: true,

5.1.2 Internal Libraries

Wing libraries themselves are JSII modules. They can be used in all other JSII supported languages.

5.2 JavaScript

The extern "file.js" modifier can be used on method declarations (in classes and resources) to indicate that a method is backed by an implementation imported from a JavaScript file.

In the following example, the static inflight method make_id is implemented in helper.js:

// task-list.w
resource TaskList {
// ...

inflight add_task(title: str) {
let id = TaskList.uuid();
this.bucket.put(id, title);

extern "./helpers.js" static inflight make_id(): str;

// helpers.js
const uuid = require('uuid');

exports.make_id = function() {
return uuid.v6();

Given a method of name X, the compiler will map the method to the JavaScript export with the matching name (without any case conversion).

Initially, we only support specifying extern for static methods (either inflight or preflight), but we will consider adding support for instance methods in the future. In those cases the first argument to the method will implicitly be this.

5.2.1 TypeScript

It is possible to use TypeScript to write helpers, but at the moment this will not be directly supported by Wing. This means that you will need to setup the TypeScript toolchain to compile your code to JavaScript and then use extern against the JavaScript file.

In the future we will consider adding direct support for extern "./helpers.ts".

5.2.2 Type model

The table below shows the mapping between Wing types and JavaScript types, represented with TypeScript syntax. When calling extern function, the arguments are checked against these declared types and the return type is assumed to be satisfied by the called function.

If frozen, the value is expected to be immutable and will throw if any attempt is made to modify it.

Built-In Wing TypeJavaScript TypeFrozen?
Map<T>{ [key: string]: T }Yes
MutMap<T>{ [key: string]: T }
Jsonstring ⏐ number ⏐ boolean ⏐ null ⏐ json[] ⏐ { [key: string]: json }Yes
MutJsonstring ⏐ number ⏐ boolean ⏐ null ⏐ json[] ⏐ { [key: string]: json }
User-Defined Wing TypesJavaScript TypeFrozen?
classclass, only with members whose phase is compatible with the function signature
resourceclass, only with members whose phase is compatible with the function signature
interfaceinterface, only with members whose phase is compatible with the function signature
enumstring-based enum-like ObjectYes

▲ top

6. Miscellaneous

6.1 Strings

String reference doc is available here. Type of string is UTF-16 internally.
All string declaration variants are multi-line.

▲ top

6.1.1 Normal strings "..."

The string inside the double quotes is processed, and all notations of form ${<expression>} are substituted from their respective scopes. The behavior is similar to `text ${sub.prop}` notation in JavaScript.
Processing unicode escape sequences happens in these strings.
" and $ can be escaped with backslash \ inside string substitutions.

let name = "World";
let s = "Hello, ${name}!";
let l = s.len;
Equivalent TypeScript Code
const name = "World";
const s = `Hello, ${name}!`; // with substitution
const l = s.length; // length of string
[`▲ top`][top]

6.1.2 Shell strings `...`

If string is enclosed with backticks, the contents of that string will be interpreted as a shell command and its output will be used as a string. `echo "Hello"` is equal to "Hello" for example.

Shell strings are invalid in the bring expression.

let name = `echo "World"`;
let s = "Hello, ${name}!";

Shell strings are executed in an instance of the BusyBox shell, compiled to run in WebAssembly to guarantee portability, in runtime. The stdout is returned as a string, interleaved with stderr. If BusyBox exits with a non-zero exit code, the stderr is thrown as an exception.

▲ top


Single line comments start with a // and continue to the end of the line.
Multi-line comments are supported with the /* ... */ syntax.
Commenting in Wing has a style that's described earlier in this document.

// comment
/* comment */
multi line comment

▲ top

6.3 Operators

Unary operators are not supported except outline below.
Arithmetic assignment operators are not supported.
Ternary or conditional operators are not supported.

6.3.1 Relational Operators

==Checks for equalitya == b
!=Checks for inequalitya != b
>Checks if left is greater than righta > b
<Checks if left less than righta < b
>=Checks if left is greater than or equal to righta >= b
<=Checks if left is less than or equal to righta <= b

▲ top

6.3.2 Logical Operators

&&Logical AND operatora && b
\|\|Logical OR operatora \|\| b
!Logical NOT operator!a

▲ top

6.3.3 Mathematics Operators

*Multiplicationa * b
/Divisiona / b
\Floor Divisiona \ b
%Modulusa % b
+Additiona + b
-Subtractiona - b
**Powera ** b

▲ top

6.3.4 Operator Precedence

-xUnary minus
*, /, \, %Multiplication, Division, Floor division, Modulus
+, -Addition, Subtraction
==, !=, >, >=, <, <=Comparisons, Identity, operators
!Logical NOT
&&Logical AND
||Logical OR

Table above is in descending order of precedence.
= operator in Wing does not return a value so you cannot do let x = y = 1.
Operators of the same row in the table above have precedence from left to right in the expression they appear in (e.g. 4 * 2 \ 3). In other words, order is determined by associativity.

▲ top

6.3.5 Short Circuiting

For the built-in logical NOT operators, the result is true if the operand is false. Otherwise, the result is false.

For the built-in logical AND operators, the result is true if both operands are true. Otherwise, the result is false. This operator is short-circuiting if the first operand is false, the second operand is not evaluated.

For the built-in logical OR operators, the result is true if either the first or the second operand (or both) is true. This operator is short-circuiting if the first operand is true, the second operand is not evaluated.

Note that bitwise logic operators do not perform short-circuiting.

In conditionals, if an optional type is used as the only r-value expression of the condition statement, it's equivalent to checking it against nil. Note that using a bool? type in this short-circuit is a compile error due to ambiguity. Using a nil? type is also ambiguous and results in a compile error.

let x? = 1;
if x {
// ...

Which is equivalent to:

let x? = 1;
if x != nil {
// ...

▲ top

6.3.6 Equality

Of the operators supported, the following can be used with non-numeric operands:

  • ==: can be used to check for equality of types and values in operands.
  • !=: can be used to check for inequality of types and values in operands.

When these operators are used on immutable data, values are checked for equality as well as types. When these operators are used on mutable data, types and refs are checked for equality. Concept of "refs" is loosely defined as "any object" that's instantiated through a class currently and then gets reassigned to other names until a mutable method is called on it, which then turns into a new "ref".

This behavior is the same as JavaScript with the addition of structural equality for immutable data (on top of nominal typing).

▲ top

6.4 Kitchen Sink

This is an example with almost every feature of the Wing, showing you a whole picture of what the syntax feels like.

bring cloud;
bring fs;

struct DenyListRule {
package_name: str;
version: str?;
reason: str;

struct DenyListProps {
rules: MutArray<DenyListRule>[];

resource DenyList {
_bucket: cloud.Bucket;
_object_key: str;

init(props: DenyListProps) {
this._bucket = cloud.Bucket();
this._object_key = "deny-list.json";

let rules_dir = this._write_to_file(props.rules, this._object_key);
this._bucket.upload("${rules_dir}/*/**", prune: true, retain_on_delete: true);

_write_to_file(list: MutArray<DenyListRule>[], filename: str): str {
let tmpdir = fs.mkdtemp();
let filepath = "${tmpdir}/${filename}";
let map = MutMap<DenyListRule>{};
for rule in list {
let suffix = DenyList._maybe_suffix(rule.version);
let path = "${rule.package_name}${suffix}";
map[path] = rule;
fs.write_json(filepath, map);
return tmpdir;

inflight rules: MutMap<DenyListRule>{}?;

inflight init() {
// this._bucket is already initialized by the capture mechanic!
this.rules = this._bucket.get(this._object_key) ?? MutMap<DenyListRule>{};

public inflight lookup(name: str, version: str): DenyListRule? {
return this.rules[name] ?? this.rules["${name}/v${version}"];

static _maybe_suffix(version: str?): str {
if version {
return "/v${version}";
} else {
return "";

let deny_list = DenyList();
let filter_fn = inflight (event: cloud.QueueEvent) => {
let package_name =["package_name"];
let version =["version"];
let reason =["reason"];
if deny_list.lookup(package_name, version) {
print("Package rejected: ${package_name}");
} else {
print("Package accepted: ${package_name}");

queue = cloud.Queue();
filter = cloud.Function(filter_fn);

▲ top

6.5 Roadmap

  • Asynchronous Execution Safety Model.
  • Make the language async by default.
  • Make inflight functions async by default.
  • First class support for regx, glob, and cron types.
  • Support of math operations over date and duration types.
  • Add time, date, and durations as first class types with syntax.
  • More useful enums: Support for Enum Classes and Swift style enums.
  • Reflection: add an extended typeof operator to get type information.
  • Advanced OOP: Support for abstract and private implementations.
  • Enforce naming conventions on public APIs (required by JSII).
  • Develop a conformance test suite for ISO certification.
  • Launch a formal spec site with ECMA standards.
  • Built-in automatic formatter and linter.
  • Distributed concurrency primitives.
  • Distributed data structures.

▲ top

6.6 Credits