Skip to content

Commit

Permalink
feat(compiler): interface declaration (#1938)
Browse files Browse the repository at this point in the history
This PR adds support for `interface` according to the [spec](https://docs.winglang.io/reference/spec#34-interfaces).

Also in this PR:
- [x] Removed interface fields from the spec and our codebase ([slack discussion](https://winglang.slack.com/archives/C048QCN2XLJ/p1680077416200329)).
- [x] Removed access modifiers from interface methods (to comply with the spec).
- [x] (Very) small refactoring in our grammar.js to rename method return type from `type` to `return_type`.
- [x] Removed unused parameter `is_class` from SymbolEnv::new

Fixes #123

*By submitting this pull request, I confirm that my contribution is made under the terms of the [Monada Contribution License](https://docs.winglang.io/terms-and-policies/contribution-license.html)*.
  • Loading branch information
staycoolcall911 committed Apr 2, 2023
1 parent 346dca1 commit 7e12b93
Show file tree
Hide file tree
Showing 19 changed files with 583 additions and 78 deletions.
10 changes: 6 additions & 4 deletions docs/04-reference/winglang-spec.md
Expand Up @@ -1647,22 +1647,24 @@ separated with commas.
All methods of an interface are implicitly public and cannot be of any other
type of visibility (private, protected, etc.).
Interface fields are not supported.
> ```TS
> // 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;
> field1: num;
> field2: str;
>
> inflight init(x: num) {
> // inflight client initialization
> this.field2 = "sample";
> this.field1 = x;
> this.field2 = "sample";
> }
> method1(x: num): str {
> return "sample: ${x}";
Expand Down
19 changes: 19 additions & 0 deletions examples/tests/invalid/impl_interface.w
Expand Up @@ -17,3 +17,22 @@ resource C impl cloud.Bucket {
// ^^^^^^^^^^^ Error: cloud.Bucket is a resource, not an interface
init() {}
}

interface I1 {
method_1(x: num): num;
}

interface I2 extends I1 {
inflight method_2(x: str): str;
}

interface I3 extends I2 {
method_3(x: Array<num>): Array<num>;
}

resource r impl I3 {
// ^ Resource "r" does not implement method "method_1" of interface "I3"
// ^ Resource "r" does not implement method "method_2" of interface "I3"
// ^ Resource "r" does not implement method "method_3" of interface "I3"
init() {}
}
29 changes: 29 additions & 0 deletions examples/tests/invalid/interface.w
@@ -0,0 +1,29 @@
// interface extend loop
interface IA extends IB {
// ^^ Unknown symbol "IB"
}

interface IB extends IA {
}

// interface extends interface which doesn't exist
interface IExist extends IDontExist {
// ^^^^^^^^^^ Unknown symbol "IDontExist"
}

// interface extends class
class ISomeClass {
init(){}
}
interface ISomeInterface extends ISomeClass {
// Interface "ISomeInterface (at ../../examples/tests/invalid/interface.w:21:11)" extends "ISomeClass", which is not an interface
}

// interface with multiple methods having the same name, different signature
interface IWithSameName {
foo();
foo();
// ^^^ Symbol "foo" already defined in this scope
foo(): num;
// ^^^ Symbol "foo" already defined in this scope
}
39 changes: 39 additions & 0 deletions examples/tests/valid/impl_interface.w
Expand Up @@ -12,3 +12,42 @@ let x: cloud.IQueueOnMessageHandler = new A();
let y = inflight () => {
x.handle("hello world!");
};

interface I1 {
method_1(x: num): num;
}

interface I2 extends I1 {
inflight method_2(x: str): str;
}

interface I3 extends I2 {
method_3(x: Array<num>): Array<num>;
}

resource r impl I3 {
init() {}
method_1(x: num): num {
return x;
}
inflight method_2(x: str): str {
return x;
}
method_3(x: Array<num>): Array<num> {
return x;
}
}

// a variable of some interface type can be assigned a class instance that implements it.
interface IAnimal {
inflight eat();
}

resource Dog impl IAnimal {
init(){}
inflight eat() {
return;
}
}

let z: IAnimal = new Dog();
16 changes: 16 additions & 0 deletions examples/tests/valid/interface.w
@@ -0,0 +1,16 @@
interface IShape {
// method with a return type
method_1(): str;
// method without a return type
method_2();
// method with a return type of the interface type
method_3(): IShape;
}

interface IPointy {
method_2();
}

interface ISquare extends IShape, IPointy {

}
20 changes: 9 additions & 11 deletions libs/tree-sitter-wing/grammar.js
Expand Up @@ -210,7 +210,7 @@ module.exports = grammar({
seq(
"interface",
field("name", $.identifier),
optional(seq("extends", field("implements", commaSep1($.custom_type)))),
optional(seq("extends", field("extends", commaSep1($.custom_type)))),
field("implementation", $.interface_implementation)
),
interface_implementation: ($) =>
Expand Down Expand Up @@ -415,14 +415,14 @@ module.exports = grammar({

extern_modifier : ($) => seq("extern", $.string),

_return_type: ($) => $._type_annotation,

method_signature: ($) =>
seq(
optional(field("access_modifier", $.access_modifier)),
optional(field("static", $.static)),
optional(field("async", $.async_modifier)),
field("name", $.identifier),
field("parameter_list", $.parameter_list),
optional(field("return_type", $._type_annotation)),
optional($._return_type),
";"
),

Expand All @@ -434,18 +434,16 @@ module.exports = grammar({
optional(field("async", $.async_modifier)),
field("name", $.identifier),
field("parameter_list", $.parameter_list),
optional(field("return_type", $._type_annotation)),
optional($._return_type),
choice(field("block", $.block), ";")
),

inflight_method_signature: ($) =>
seq(
optional(field("access_modifier", $.access_modifier)),
optional(field("static", $.static)),
field("phase_modifier", $._inflight_specifier),
field("name", $.identifier),
field("parameter_list", $.parameter_list),
optional(field("return_type", $._type_annotation)),
optional($._return_type),
";"
),

Expand All @@ -457,7 +455,7 @@ module.exports = grammar({
field("phase_modifier", $._inflight_specifier),
field("name", $.identifier),
field("parameter_list", $.parameter_list),
optional(field("return_type", $._type_annotation)),
optional($._return_type),
choice(field("block", $.block), ";")
),

Expand Down Expand Up @@ -557,7 +555,7 @@ module.exports = grammar({
preflight_closure: ($) =>
seq(
field("parameter_list", $.parameter_list),
optional(field("return_type", $._type_annotation)),
optional($._return_type),
"=>",
field("block", $.block)
),
Expand All @@ -566,7 +564,7 @@ module.exports = grammar({
seq(
"inflight",
field("parameter_list", $.parameter_list),
optional(field("return_type", $._type_annotation)),
optional($._return_type),
"=>",
field("block", $.block)
),
Expand Down
Expand Up @@ -82,7 +82,7 @@ resource A {
async preflight_func() {}
public preflight_func2() {}
inflight inflight_func() {}
public inflight inflight_func2() {}
public inflight inflight_func2(): num {}
pf_member: str;
inflight if_member: str;
public inflight var if_member2: str;
Expand Down Expand Up @@ -124,6 +124,7 @@ resource A {
access_modifier: (access_modifier)
name: (identifier)
parameter_list: (parameter_list)
type: (builtin_type)
block: (block)
)
(class_field
Expand Down Expand Up @@ -193,9 +194,9 @@ interface A extends B, C {
(source
(interface_definition
name: (identifier)
implements: (custom_type
extends: (custom_type
object: (identifier))
implements: (custom_type
extends: (custom_type
object: (identifier))
implementation: (interface_implementation
(method_signature
Expand Down
8 changes: 8 additions & 0 deletions libs/wingc/src/ast.rs
Expand Up @@ -265,6 +265,13 @@ pub struct Class {
pub is_resource: bool,
}

#[derive(Debug)]
pub struct Interface {
pub name: Symbol,
pub methods: Vec<(Symbol, FunctionSignature)>,
pub extends: Vec<UserDefinedType>,
}

#[derive(Debug)]
pub enum StmtKind {
Bring {
Expand Down Expand Up @@ -302,6 +309,7 @@ pub enum StmtKind {
Return(Option<Expr>),
Scope(Scope),
Class(Class),
Interface(Interface),
Struct {
name: Symbol,
extends: Vec<Symbol>,
Expand Down
6 changes: 5 additions & 1 deletion libs/wingc/src/capture.rs
Expand Up @@ -399,7 +399,11 @@ fn scan_captures_in_inflight_scope(scope: &Scope, diagnostics: &mut Diagnostics)
todo!()
}
// Type definitions with no expressions in them can't capture anything
StmtKind::Struct { .. } | StmtKind::Enum { .. } | StmtKind::Break | StmtKind::Continue => {}
StmtKind::Struct { .. }
| StmtKind::Interface { .. }
| StmtKind::Enum { .. }
| StmtKind::Break
| StmtKind::Continue => {}
StmtKind::TryCatch {
try_statements,
catch_block,
Expand Down
4 changes: 4 additions & 0 deletions libs/wingc/src/jsify.rs
Expand Up @@ -642,6 +642,10 @@ impl<'a> JSifier<'a> {
}
}
StmtKind::Class(class) => self.jsify_class(env, class, context),
StmtKind::Interface { .. } => {
// This is a no-op in JS
format!("")
}
StmtKind::Struct { .. } => {
// This is a no-op in JS
format!("")
Expand Down
2 changes: 1 addition & 1 deletion libs/wingc/src/lib.rs
Expand Up @@ -165,7 +165,7 @@ pub fn parse(source_path: &Path) -> (Scope, Diagnostics) {
}

pub fn type_check(scope: &mut Scope, types: &mut Types, source_path: &Path) -> Diagnostics {
let env = SymbolEnv::new(None, types.void(), false, false, Phase::Preflight, 0);
let env = SymbolEnv::new(None, types.void(), false, Phase::Preflight, 0);
scope.set_env(env);

// note: Globals are emitted here and wrapped in "{ ... }" blocks. Wrapping makes these emissions, actual
Expand Down

0 comments on commit 7e12b93

Please sign in to comment.