Skip to content

Commit

Permalink
fix(compiler)!: no way to detect if an interface implements a preflig…
Browse files Browse the repository at this point in the history
…ht class (#6197)

Fixes #5872

Add a phase modifier `inflight` support to `interface` definitions. Now all preflight interfaces implicitly implement `std.IResouce` the same way that prefligth classes implicitly implement `std.Resource`. We can now explicitly qualify these interfaces with the `lift()` builtin and we can get qualification errors if an inflight variable is being used to access such an object (same as classes).

Note that inflight interfaces can be defined preflight with the keywork and all their methods will be implicitly `inflight`. Interfaces defined inflight are always inflight interfaces. Preflight interfaces may extend inflight interfaces, but not the other way around.

JSII imported interfaces are, at least for now, always preflight interfaces.

**Breaking change**:
Since inflight interfaces are new, code using interfaces in inflight that were defined in preflight scope will need to add the `inflight` modifier to those interfaces or the compiler will complain that you're trying to access an unknown preflight object:
```wing
inflight IMyIface { // Explicitly mark this interface as inflight so it can be used in inflight code without worrying about lifting
  do(): void; // No need to specify `do` is an inflight method, it's implicit from the interface type.
}
inflight () => {
  x: IMyIface = ....;
  x.do(); // This would be a qualification error without the above fix.
}
```

**Other fixes**
* Fixed span in diagnostic of wrongly typed methods implementing an interface, previously the span pointed to the implementing class instead of the specific method inside that class that mismatches the interface definition.
* Cleaned up some dead code.

## Checklist

- [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted)
- [x] Description explains motivation and solution
- [x] Tests added (always)
- [x] Docs updated (only required for features)
- [ ] 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
yoav-steinberg authored Apr 12, 2024
1 parent 1b71ae3 commit 4e5335a
Show file tree
Hide file tree
Showing 29 changed files with 737 additions and 143 deletions.
19 changes: 14 additions & 5 deletions docs/docs/03-language-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1441,10 +1441,14 @@ class Boo extends Foo {
```
Classes can inherit and extend other classes using the `extends` keyword.
Classes can implement interfaces iff the interfaces do not contain `inflight`.
Classes can implement multiple interfaces using the `impl` keyword.
Inflight classes may only implement inflight interfaces.
```TS
class Foo {
interface IFoo {
method(): void;
}
class Foo impl IFoo {
x: num;
new() { this.x = 0; }
pub method() { }
Expand Down Expand Up @@ -1577,7 +1581,12 @@ of methods with different phases is not allowed as well.
Interfaces represent a contract that a class must fulfill.
Interfaces are defined with the `interface` keyword.
Currently, preflight interfaces are allowed, while inflight interfaces are not supported yet (see https://github.com/winglang/wing/issues/1961).
Interfaces may be either preflight interfaces or inflight interfaces.
Preflight interfaces are defined in preflight scope and can contain both preflight and inflight methods.
Only preflight classes may implement preflight interfaces.
Inflight interfaces are either defined with the `inflight` modifier in preflight scope or simply defined in inflight scope.
All methods of inflight interfaces are implicitly inflight (no need to use the `inflight` keyword).
Since both preflight and inflight classes can have inflight methods defined inside them, they are both capable of implementing inflight interfaces.
`impl` keyword is used to implement an interface or multiple interfaces that are
separated with commas.
Expand All @@ -1594,7 +1603,7 @@ Interface fields are not supported.
> inflight method3(): void;
> }
>
> interface IMyInterface2 {
> inflight interface IMyInterface2 {
> method2(): str;
> }
>
Expand All @@ -1610,7 +1619,7 @@ Interface fields are not supported.
> return "sample: {x}";
> }
> inflight method3(): void { }
> method2(): str {
> inflight method2(): str {
> return this.field2;
> }
> }
Expand Down
6 changes: 5 additions & 1 deletion examples/jsii-fixture/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class JsiiClass {
public static staticMethod(arg: string) {
return `Got ${arg}`;
}

public methodWithStructParam(s: SomeStruct): string {
return s.field;
}
Expand All @@ -43,4 +43,8 @@ export interface IFakeClosure {

export interface SomeStruct {
readonly field: string;
}

export interface ISomeInterface {
method(): void;
}
12 changes: 12 additions & 0 deletions examples/tests/invalid/explicit_lift_qualification.test.w
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
bring cloud;

interface IPreflightInterface {
inflight method(): void;
}
class PreflightClass impl IPreflightInterface {
pub inflight method(): void {}
}

let bucket = new cloud.Bucket();

let prelight_string = "hi";
let preflight_class = new PreflightClass();

class Foo {
pub inflight mehtod1() {
Expand Down Expand Up @@ -36,5 +44,9 @@ class Foo {
let b = bucket;
b.put("k", "v"); // With no explicit qualification this should be an error
//^ Expression of type "Bucket" references an unknown preflight object

let i: IPreflightInterface = preflight_class;
i.method(); // With no explicit qualification this should be an error
//^ Expression of type "IPreflightInterface" references an unknown preflight object
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
bring cloud;

interface IGoo {
inflight notHandle(): void;
inflight interface IGoo {
notHandle(): void;
}

inflight class NotGoo {
Expand Down
25 changes: 25 additions & 0 deletions examples/tests/invalid/interface.test.w
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
bring "jsii-fixture" as jsii_fixture;

// interface extend loop
interface IA extends IB {
// ^^ Unknown symbol "IB"
Expand Down Expand Up @@ -42,3 +44,26 @@ interface IMissingParamTypes {
method1(a, b): void;
//^ Expected type annotation
}

// Can't implement preflight interface on inflight class
interface IPreflight {
method1(): void;
}
inflight class CImplPreflightIface impl IPreflight {
pub method1(): void {}
}

// Can't extend preflight interface on inflight interface
inflight interface IInflightExtendsPreflight extends IPreflight {
method2(): void;
}

// Inflight interfaces can't extend JSII interfaces
inflight interface IInflightExtendsJsii extends jsii_fixture.ISomeInterface {
method(): void;
}

// Inflight classes can't implement JSII interfaces
inflight class CInflightImplJsii impl jsii_fixture.ISomeInterface {
pub method(): void {}
}
3 changes: 2 additions & 1 deletion examples/tests/invalid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"dependencies": {
"cdktf": "0.20.3",
"constructs": "^10.3",
"jsii-code-samples": "1.7.0"
"jsii-code-samples": "1.7.0",
"jsii-fixture": "workspace:^"
},
"volta": {
"extends": "../../../package.json"
Expand Down
2 changes: 1 addition & 1 deletion examples/tests/invalid/resource_captures.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ class Foo {

inflight test() {
let b = this.bucket;
// ^^^^^^^^^^^ Unable to qualify which operations are performed on 'this.bucket' of type 'Bucket'. This is not supported yet.
b.put("hello", "world");
// ^ Expression of type "Bucket" references an unknown preflight object

this.collectionOfResources.at(0).put("hello", "world");
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Capturing collection of resources is not supported yet (type is 'Array<Bucket>')
Expand Down
6 changes: 3 additions & 3 deletions examples/tests/sdk_tests/service/http-server.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ struct Address {
port: num;
}

interface IHttpServer {
inflight address(): Address;
inflight close(): void;
inflight interface IHttpServer {
address(): Address;
close(): void;
}


Expand Down
19 changes: 19 additions & 0 deletions examples/tests/valid/explicit_lift_qualification.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,22 @@ let inflight_closure = inflight () => {
test "explicit closure lift qualification" {
inflight_closure();
}

// Explicit qualification of preflight interface type
interface PreflightInterface {
inflight method(): str;
}

class PreflightClass impl PreflightInterface {
pub inflight method(): str {
return "ahoy there";
}
}

let bar = new PreflightClass();

test "explicit interface lift qualification" {
lift(bar, ["method"]);
let x: PreflightInterface = bar;
assert(x.method() == "ahoy there");
}
59 changes: 59 additions & 0 deletions examples/tests/valid/impl_interface.test.w
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
bring cloud;
bring "jsii-fixture" as jsii_fixture;

class A impl cloud.IQueueSetConsumerHandler {
pub inflight handle(msg: str) {
Expand Down Expand Up @@ -82,3 +83,61 @@ let f = inflight () => {
let dog = new MyDog();
dog.bark();
};

// This should work: extend a JSII interface in a preflight interface
// Commented out because of: https://github.com/winglang/wing/issues/6209
//
// interface ExtendJsiiIface extends jsii_fixture.ISomeInterface {
// inflight inflight_method(): void;
// }
//
// class ImplementJsiiIface impl ExtendJsiiIface {
// pub method() {
// return;
// }
// pub inflight inflight_method() {
// return;
// }
// }

// Implement an inflight interface in a preflight class
inflight interface IInflight {
inflight_method(): void;
}
class ImplementInflightIfaceInPreflightClass impl IInflight {
pub inflight inflight_method() {
return;
}
}

// Extend inflight interface in an inflight interface defined inflight
inflight () => {
interface InflightIfaceDefinedInflight extends IInflight {}
};

// Extend inflight interface in an inflight interface defined preflight
interface InflightIfaceDefinedPreflight extends IInflight {}

// Implement an inflight interface in an inflight class
inflight class ImplInflightIfaceInInflightClass impl IInflight {
pub inflight_method() {
return;
}
}

// Implement an inflight interface in a preflight class
class ImplInflightIfaceInPreflightClass impl IInflight {
pub inflight inflight_method() {
return;
}
}

// Implement preflight interface in an preflight class
interface IPreflight {
method(): void;
}
class ImplPreflightIfaceInPreflightClass impl IPreflight {
pub method() {
return;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
bring cloud;

interface IGoo {
inflight interface IGoo {
inflight handle(): num;
}

Expand Down
10 changes: 8 additions & 2 deletions libs/tree-sitter-wing/grammar.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ module.exports = grammar({

// These modifier conflicts should be solved through GLR parsing
[$.field_modifiers, $.method_modifiers],
[$.class_modifiers, $.closure_modifiers],
[$.class_modifiers, $.closure_modifiers, $.interface_modifiers],
[$.inflight_method_signature, $.field_modifiers],
],

Expand Down Expand Up @@ -225,14 +225,20 @@ module.exports = grammar({
$._semicolon
),

/// Interfaces

interface_modifiers: ($) =>
repeat1(choice($.access_modifier, $.inflight_specifier)),

interface_definition: ($) =>
seq(
optional(field("access_modifier", $.access_modifier)),
optional(field("modifiers", $.interface_modifiers)),
"interface",
field("name", $.identifier),
optional(seq("extends", field("extends", commaSep1($.custom_type)))),
field("implementation", $.interface_implementation)
),

interface_implementation: ($) =>
braced(
repeat(
Expand Down
23 changes: 20 additions & 3 deletions libs/tree-sitter-wing/src/grammar.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ class A extends B impl C, D {}
Interface definition
================================================================================

interface A extends B, C {
pub inflight interface A extends B, C {
do_something();
inflight do_something_else(x: str): num;
some_field: num;
Expand All @@ -212,6 +212,9 @@ interface A extends B, C {

(source
(interface_definition
modifiers: (interface_modifiers
(access_modifier)
(inflight_specifier))
name: (identifier)
extends: (custom_type
object: (type_identifier))
Expand Down
1 change: 1 addition & 0 deletions libs/wingc/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ pub struct Interface {
pub methods: Vec<(Symbol, FunctionSignature)>,
pub extends: Vec<UserDefinedType>,
pub access: AccessModifier,
pub phase: Phase,
}

#[derive(Debug)]
Expand Down
1 change: 1 addition & 0 deletions libs/wingc/src/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ where
.map(|interface| f.fold_user_defined_type(interface))
.collect(),
access: node.access,
phase: node.phase,
}
}

Expand Down
1 change: 1 addition & 0 deletions libs/wingc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ const WINGSDK_STRING: &'static str = "std.String";
const WINGSDK_JSON: &'static str = "std.Json";
const WINGSDK_MUT_JSON: &'static str = "std.MutJson";
const WINGSDK_RESOURCE: &'static str = "std.Resource";
const WINGSDK_IRESOURCE: &'static str = "std.IResource";
const WINGSDK_AUTOID_RESOURCE: &'static str = "std.AutoIdResource";
const WINGSDK_STRUCT: &'static str = "std.Struct";
const WINGSDK_TEST_CLASS_NAME: &'static str = "Test";
Expand Down
Loading

0 comments on commit 4e5335a

Please sign in to comment.