Skip to content

Commit

Permalink
rfc: preflight type reflection (#7148)
Browse files Browse the repository at this point in the history
Related to #4106

Proposal for an extension to Winglang that adds support for type reflection. Examples and an implementation sketch is included.

## Checklist

- [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted)
- [x] Description explains motivation and solution
- [ ] Tests added (always)
- [ ] Docs updated (only required for features)
- [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing

*By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*.
  • Loading branch information
Chriscbr authored Sep 19, 2024
1 parent 9d49fce commit 2a78abb
Showing 1 changed file with 149 additions and 0 deletions.
149 changes: 149 additions & 0 deletions docs/api/05-language-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1280,10 +1280,159 @@ let rawData: bytes = fs.readBytes("path/to/file");
fs.writeBytes("path/to/file", rawData);
```
#### Roadmap
The following features are not yet implemented, but we are planning to add them in the future:
* `mutbytes` - see https://github.com/winglang/wing/issues/7144
[`top`][top]
---
### 1.15 Type reflection
Wing supports reflection on types in preflight code. This allows you to inspect and analyze the structure of types at compile-time, which can be useful for generating code, validating type constraints, or building generic utilities.
Using reflection, you can obtain information about:
- The methods of a type
- The fields of a struct or class
- The variants of an enum
- The implemented interfaces of a class
#### 1.15.1 Basic usage
The `@type` intrinsic function is used to obtain type information:
```TS
let t: Type = @type(MyStruct);
let t: Type = @type(IMyInterface);
```
The `Type` value has a `kind` field which indicates the general category of the type.
```TS
let t = @type(MyStruct);
log(t.kind); // "struct", "class", "interface", "num", "str", "bool", etc.
```
#### 1.15.2 Specific type information
The `Type` value can be converted to more specific type representations:
```TS
let t = @type(SomeType);
if let st = t.asStruct() {
log(st.name);
for field in st.fields {
log(field.name);
log(field.type.kind);
}
}
if let cl = t.asClass() {
log(cl.name);
for method in cl.methods {
log(method.name);
log(method.returnType.kind);
}
}
if let en = t.asEnum() {
log(en.name);
for variant in en.variants {
log(variant);
}
}
```
#### 1.15.3 Advanced examples
Here are some more advanced examples of using preflight reflection:
```TS
// Generate a JSON schema for a struct
let generateJsonSchema = (structType: Type): str => {
if let st = structType.asStruct() {
let schema = MutJson {
type: "object",
properties: {},
required: []
};
for field in st.fields {
let fieldSchema = {};
if field.type.kind == "str" {
fieldSchema.type = "string";
} else if field.type.kind == "num" {
fieldSchema.type = "number";
} // ... handle other types
schema.properties[field.name] = fieldSchema;
if !field.optional {
schema.required.push(field.name);
}
}
return Json.stringify(schema);
}
throw "Input must be a struct type";
};
struct User {
name: str;
age: num;
email: str?;
}
log(generateJsonSchema(@type(User)));
// Check if a class implements an interface
let implementsInterface = (classType: Type, interfaceType: Type): bool => {
if let cl = classType.asClass() {
if let int = interfaceType.asInterface() {
for impl in cl.implements {
if impl.name == int.name {
return true;
}
}
}
}
return false;
};
interface ILogger {
log(message: str): void;
}
class ConsoleLogger impl ILogger {
log(message: str) {
// ...
}
}
assert(implementsInterface(@type(ConsoleLogger), @type(ILogger)));
```
#### 1.15.4 Implementation sketch
The preflight reflection system could be implemented as follows:
1. During compilation, the Wing compiler builds a type information database for all types encountered in the code.
2. The `@type` function is implemented as a compiler intrinsic that looks up the type information and emits a `Type` object into the generated JavaScript code.
3. The `Type` class and its variants (`StructType`, `ClassType`, etc.) are implemented as special compiler-known types that provide an API for accessing the type information.
5. The methods on `Type` (like `asStruct()`, `asClass()`, etc.) are implemented to return the appropriate specific type object if the conversion is valid, or `nil` if not.
6. All of this happens at compile-time, so there's no runtime overhead for using reflection. The compiler can optimize away any reflection code that isn't used to affect the runtime behavior of the program.
This implementation allows for powerful compile-time introspection while maintaining Wing's performance characteristics and type safety.
[`top`][top]
## 2. Statements
### 2.1 bring
Expand Down

0 comments on commit 2a78abb

Please sign in to comment.