From 69cbe6d99fe4feca48199db12f3db9a75a97b423 Mon Sep 17 00:00:00 2001 From: Chris Rybicki Date: Thu, 11 Jul 2024 13:43:04 -0400 Subject: [PATCH] chore: grammar support for phase independent specifier (#6879) First step towards implementing the `unphased` functions RFC (https://github.com/winglang/wing/pull/1711) ## Checklist - [ ] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted) - [ ] 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)*. --- examples/tests/invalid/unphased.test.w | 33 +++ libs/tree-sitter-wing/grammar.js | 101 ++++----- libs/tree-sitter-wing/queries/highlights.scm | 2 +- libs/tree-sitter-wing/src/grammar.json | 195 +++++------------- .../test/corpus/expressions.txt | 16 +- .../corpus/statements/class_and_resource.txt | 33 +-- .../test/corpus/statements/statements.txt | 8 +- libs/wingc/src/lsp/completions.rs | 2 +- libs/wingc/src/parser.rs | 185 +++++++++++++---- libs/wingc/src/type_check.rs | 11 +- tools/hangar/__snapshots__/invalid.ts.snap | 91 +++++++- 11 files changed, 387 insertions(+), 290 deletions(-) create mode 100644 examples/tests/invalid/unphased.test.w diff --git a/examples/tests/invalid/unphased.test.w b/examples/tests/invalid/unphased.test.w new file mode 100644 index 00000000000..65d6bf2e6f8 --- /dev/null +++ b/examples/tests/invalid/unphased.test.w @@ -0,0 +1,33 @@ +unphased class UnphasedClass {} +// ^ Error: Unphased classes are not yet supported + +unphased interface UnphasedInterface {} +// ^ Error: Unphased interfaces are not yet supported + +let unphasedFn = unphased () => { + // ^ Error: Unphased functions are not yet supported + log("Hello, world!"); +}; + +let fnWithUnphasedParamType = (f: unphased (str): num) => { + // ^ Error: Unphased functions are not yet supported +}; + +let fnWithUnphasedReturnType = (): (unphased (str): num)? => { + // ^ Error: Unphased functions are not yet supported + return nil; +}; + +class Foo { + unphased myField: str; + // ^ Error: Class fields cannot be unphased + unphased new() {} + // ^ Error: Class constructors cannot be unphased + unphased myMethod() {} + // ^ Error: Unphased functions are not yet supported +} + +interface IFoo { + unphased bar(): void; + // ^ Error: Unphased methods on interfaces are not yet supported +} diff --git a/libs/tree-sitter-wing/grammar.js b/libs/tree-sitter-wing/grammar.js index edf15ee090e..38b8f1dc59f 100644 --- a/libs/tree-sitter-wing/grammar.js +++ b/libs/tree-sitter-wing/grammar.js @@ -42,7 +42,6 @@ module.exports = grammar({ // These modifier conflicts should be solved through GLR parsing [$.field_modifiers, $.method_modifiers], [$.class_modifiers, $.closure_modifiers, $.interface_modifiers], - [$.inflight_method_signature, $.field_modifiers], ], supertypes: ($) => [$.expression, $._literal], @@ -108,7 +107,7 @@ module.exports = grammar({ accessor: ($) => choice(".", "?."), - inflight_specifier: ($) => "inflight", + phase_specifier: ($) => choice("unphased", "inflight"), _statement: ($) => choice( @@ -220,7 +219,7 @@ module.exports = grammar({ // Classes class_modifiers: ($) => - repeat1(choice($.access_modifier, $.inflight_specifier)), + repeat1(choice($.access_modifier, $.phase_specifier)), class_definition: ($) => seq( @@ -240,7 +239,7 @@ module.exports = grammar({ choice( $.access_modifier, $.static, - $.inflight_specifier, + $.phase_specifier, $.reassignable ) ), @@ -257,7 +256,7 @@ module.exports = grammar({ /// Interfaces interface_modifiers: ($) => - repeat1(choice($.access_modifier, $.inflight_specifier)), + repeat1(choice($.access_modifier, $.phase_specifier)), interface_definition: ($) => seq( @@ -269,11 +268,7 @@ module.exports = grammar({ ), interface_implementation: ($) => - braced( - repeat( - choice($.method_signature, $.inflight_method_signature, $.class_field) - ) - ), + braced(repeat(choice($.method_definition, $.class_field))), inclusive_range: ($) => "=", @@ -369,7 +364,6 @@ module.exports = grammar({ expression: ($) => choice( - $.unwrap_or, $.binary_expression, $.unary_expression, $.new_expression, @@ -532,7 +526,7 @@ module.exports = grammar({ function_type: ($) => prec.right( seq( - optional(field("inflight", $.inflight_specifier)), + optional(field("phase_specifier", $.phase_specifier)), field("parameter_types", $.parameter_type_list), seq(":", field("return_type", $._type)) ) @@ -545,7 +539,7 @@ module.exports = grammar({ initializer: ($) => seq( - optional(field("inflight", $.inflight_specifier)), + optional(field("phase_specifier", $.phase_specifier)), field("ctor_name", "new"), field("parameter_list", $.parameter_list), field("block", $.block) @@ -555,21 +549,13 @@ module.exports = grammar({ _return_type: ($) => $._type_annotation, - method_signature: ($) => - seq( - field("name", $.identifier), - field("parameter_list", $.parameter_list), - optional($._return_type), - $._semicolon - ), - method_modifiers: ($) => repeat1( choice( $.extern_modifier, $.access_modifier, $.static, - $.inflight_specifier + $.phase_specifier ) ), @@ -582,15 +568,6 @@ module.exports = grammar({ choice(field("block", $.block), $._semicolon) ), - inflight_method_signature: ($) => - seq( - field("phase_modifier", $.inflight_specifier), - field("name", $.identifier), - field("parameter_list", $.parameter_list), - optional($._return_type), - $._semicolon - ), - access_modifier: ($) => choice("pub", "protected", "internal"), variadic: ($) => "...", @@ -625,16 +602,6 @@ module.exports = grammar({ _container_value_type: ($) => seq("<", field("type_parameter", $._type), ">"), - unwrap_or: ($) => - prec.right( - PREC.UNWRAP_OR, - seq( - field("left", $.expression), - field("op", "??"), - field("right", $.expression) - ) - ), - optional_unwrap: ($) => prec.right(PREC.OPTIONAL_UNWRAP, seq($.expression, "!")), @@ -658,34 +625,36 @@ module.exports = grammar({ }, binary_expression: ($) => { - /** @type {Array<[RuleOrLiteral, number]>} */ + /** @type {Array<[RuleOrLiteral, number, "left" | "right"]>} */ const table = [ - ["+", PREC.ADD], - ["-", PREC.ADD], - ["*", PREC.MULTIPLY], - ["/", PREC.MULTIPLY], - ["\\", PREC.MULTIPLY], - ["%", PREC.MULTIPLY], - ["**", PREC.POWER], - ["||", PREC.LOGICAL_OR], - ["&&", PREC.LOGICAL_AND], - //['|', PREC.INCLUSIVE_OR], - //['^', PREC.EXCLUSIVE_OR], - //['&', PREC.BITWISE_AND], - ["==", PREC.EQUAL], - ["!=", PREC.EQUAL], - [">", PREC.RELATIONAL], - [">=", PREC.RELATIONAL], - ["<=", PREC.RELATIONAL], - ["<", PREC.RELATIONAL], - //['<<', PREC.SHIFT], - //['>>', PREC.SHIFT], - //['>>>', PREC.SHIFT], + ["+", PREC.ADD, "left"], + ["-", PREC.ADD, "left"], + ["*", PREC.MULTIPLY, "left"], + ["/", PREC.MULTIPLY, "left"], + ["\\", PREC.MULTIPLY, "left"], + ["%", PREC.MULTIPLY, "left"], + ["**", PREC.POWER, "left"], + ["||", PREC.LOGICAL_OR, "left"], + ["&&", PREC.LOGICAL_AND, "left"], + //['|', PREC.INCLUSIVE_OR, "left"], + //['^', PREC.EXCLUSIVE_OR, "left"], + //['&', PREC.BITWISE_AND, "left"], + ["==", PREC.EQUAL, "left"], + ["!=", PREC.EQUAL, "left"], + [">", PREC.RELATIONAL, "left"], + [">=", PREC.RELATIONAL, "left"], + ["<=", PREC.RELATIONAL, "left"], + ["<", PREC.RELATIONAL, "left"], + //['<<', PREC.SHIFT, "left"], + //['>>', PREC.SHIFT, "left"], + //['>>>', PREC.SHIFT, "left"], + ["??", PREC.UNWRAP_OR, "right"], ]; return choice( - ...table.map(([operator, precedence]) => { - return prec.left( + ...table.map(([operator, precedence, associativity]) => { + const precFn = associativity === "left" ? prec.left : prec.right; + return precFn( precedence, seq( field("left", $.expression), @@ -697,7 +666,7 @@ module.exports = grammar({ ); }, - closure_modifiers: ($) => repeat1(choice($.inflight_specifier)), + closure_modifiers: ($) => repeat1(choice($.phase_specifier)), closure: ($) => seq( diff --git a/libs/tree-sitter-wing/queries/highlights.scm b/libs/tree-sitter-wing/queries/highlights.scm index 0171e09dec2..5d50b51c783 100644 --- a/libs/tree-sitter-wing/queries/highlights.scm +++ b/libs/tree-sitter-wing/queries/highlights.scm @@ -82,7 +82,7 @@ "class" "let" "new" - (inflight_specifier) + (phase_specifier) ] @keyword [ diff --git a/libs/tree-sitter-wing/src/grammar.json b/libs/tree-sitter-wing/src/grammar.json index d5c95a40acb..ac763282f68 100644 --- a/libs/tree-sitter-wing/src/grammar.json +++ b/libs/tree-sitter-wing/src/grammar.json @@ -291,9 +291,18 @@ } ] }, - "inflight_specifier": { - "type": "STRING", - "value": "inflight" + "phase_specifier": { + "type": "CHOICE", + "members": [ + { + "type": "STRING", + "value": "unphased" + }, + { + "type": "STRING", + "value": "inflight" + } + ] }, "_statement": { "type": "CHOICE", @@ -1082,7 +1091,7 @@ }, { "type": "SYMBOL", - "name": "inflight_specifier" + "name": "phase_specifier" } ] } @@ -1259,7 +1268,7 @@ }, { "type": "SYMBOL", - "name": "inflight_specifier" + "name": "phase_specifier" }, { "type": "SYMBOL", @@ -1341,7 +1350,7 @@ }, { "type": "SYMBOL", - "name": "inflight_specifier" + "name": "phase_specifier" } ] } @@ -1459,11 +1468,7 @@ "members": [ { "type": "SYMBOL", - "name": "method_signature" - }, - { - "type": "SYMBOL", - "name": "inflight_method_signature" + "name": "method_definition" }, { "type": "SYMBOL", @@ -1993,10 +1998,6 @@ "expression": { "type": "CHOICE", "members": [ - { - "type": "SYMBOL", - "name": "unwrap_or" - }, { "type": "SYMBOL", "name": "binary_expression" @@ -2822,10 +2823,10 @@ "members": [ { "type": "FIELD", - "name": "inflight", + "name": "phase_specifier", "content": { "type": "SYMBOL", - "name": "inflight_specifier" + "name": "phase_specifier" } }, { @@ -2956,10 +2957,10 @@ "members": [ { "type": "FIELD", - "name": "inflight", + "name": "phase_specifier", "content": { "type": "SYMBOL", - "name": "inflight_specifier" + "name": "phase_specifier" } }, { @@ -3010,43 +3011,6 @@ "type": "SYMBOL", "name": "_type_annotation" }, - "method_signature": { - "type": "SEQ", - "members": [ - { - "type": "FIELD", - "name": "name", - "content": { - "type": "SYMBOL", - "name": "identifier" - } - }, - { - "type": "FIELD", - "name": "parameter_list", - "content": { - "type": "SYMBOL", - "name": "parameter_list" - } - }, - { - "type": "CHOICE", - "members": [ - { - "type": "SYMBOL", - "name": "_return_type" - }, - { - "type": "BLANK" - } - ] - }, - { - "type": "SYMBOL", - "name": "_semicolon" - } - ] - }, "method_modifiers": { "type": "REPEAT1", "content": { @@ -3066,7 +3030,7 @@ }, { "type": "SYMBOL", - "name": "inflight_specifier" + "name": "phase_specifier" } ] } @@ -3137,51 +3101,6 @@ } ] }, - "inflight_method_signature": { - "type": "SEQ", - "members": [ - { - "type": "FIELD", - "name": "phase_modifier", - "content": { - "type": "SYMBOL", - "name": "inflight_specifier" - } - }, - { - "type": "FIELD", - "name": "name", - "content": { - "type": "SYMBOL", - "name": "identifier" - } - }, - { - "type": "FIELD", - "name": "parameter_list", - "content": { - "type": "SYMBOL", - "name": "parameter_list" - } - }, - { - "type": "CHOICE", - "members": [ - { - "type": "SYMBOL", - "name": "_return_type" - }, - { - "type": "BLANK" - } - ] - }, - { - "type": "SYMBOL", - "name": "_semicolon" - } - ] - }, "access_modifier": { "type": "CHOICE", "members": [ @@ -3421,39 +3340,6 @@ } ] }, - "unwrap_or": { - "type": "PREC_RIGHT", - "value": 80, - "content": { - "type": "SEQ", - "members": [ - { - "type": "FIELD", - "name": "left", - "content": { - "type": "SYMBOL", - "name": "expression" - } - }, - { - "type": "FIELD", - "name": "op", - "content": { - "type": "STRING", - "value": "??" - } - }, - { - "type": "FIELD", - "name": "right", - "content": { - "type": "SYMBOL", - "name": "expression" - } - } - ] - } - }, "optional_unwrap": { "type": "PREC_RIGHT", "value": 180, @@ -4048,6 +3934,39 @@ } ] } + }, + { + "type": "PREC_RIGHT", + "value": 80, + "content": { + "type": "SEQ", + "members": [ + { + "type": "FIELD", + "name": "left", + "content": { + "type": "SYMBOL", + "name": "expression" + } + }, + { + "type": "FIELD", + "name": "op", + "content": { + "type": "STRING", + "value": "??" + } + }, + { + "type": "FIELD", + "name": "right", + "content": { + "type": "SYMBOL", + "name": "expression" + } + } + ] + } } ] }, @@ -4058,7 +3977,7 @@ "members": [ { "type": "SYMBOL", - "name": "inflight_specifier" + "name": "phase_specifier" } ] } @@ -4710,10 +4629,6 @@ "class_modifiers", "closure_modifiers", "interface_modifiers" - ], - [ - "inflight_method_signature", - "field_modifiers" ] ], "precedences": [ diff --git a/libs/tree-sitter-wing/test/corpus/expressions.txt b/libs/tree-sitter-wing/test/corpus/expressions.txt index 53f58b780cf..df0d0c522b8 100644 --- a/libs/tree-sitter-wing/test/corpus/expressions.txt +++ b/libs/tree-sitter-wing/test/corpus/expressions.txt @@ -225,7 +225,7 @@ inflight (): num => { return 1; }; (expression_statement (closure modifiers: (closure_modifiers - (inflight_specifier)) + (phase_specifier)) parameter_list: (parameter_list) type: (builtin_type) block: (block @@ -244,7 +244,7 @@ inflight () => { return 1; }; (expression_statement (closure modifiers: (closure_modifiers - (inflight_specifier)) + (phase_specifier)) parameter_list: (parameter_list) block: (block (return_statement @@ -262,7 +262,7 @@ inflight (arg1) => { log(arg1); }; (expression_statement (closure modifiers: (closure_modifiers - (inflight_specifier)) + (phase_specifier)) parameter_list: (parameter_list (parameter_definition name: (identifier))) @@ -304,12 +304,12 @@ maybeVal ?? "hi" ?? 2; (source (expression_statement - (unwrap_or + (binary_expression (reference (reference_identifier)) (number))) (expression_statement - (unwrap_or + (binary_expression (reference (reference_identifier)) (binary_expression @@ -317,16 +317,16 @@ maybeVal ?? "hi" ?? 2; (number)))) (expression_statement (binary_expression - (unwrap_or + (binary_expression (reference (reference_identifier)) (number)) (number))) (expression_statement - (unwrap_or + (binary_expression (reference (reference_identifier)) - (unwrap_or + (binary_expression (string) (number))))) diff --git a/libs/tree-sitter-wing/test/corpus/statements/class_and_resource.txt b/libs/tree-sitter-wing/test/corpus/statements/class_and_resource.txt index 36a7110ffb0..1dc7bf8dd73 100644 --- a/libs/tree-sitter-wing/test/corpus/statements/class_and_resource.txt +++ b/libs/tree-sitter-wing/test/corpus/statements/class_and_resource.txt @@ -17,7 +17,7 @@ inflight class A { (source (class_definition modifiers: (class_modifiers - (inflight_specifier)) + (phase_specifier)) name: (identifier) implementation: (class_implementation (initializer @@ -25,7 +25,7 @@ inflight class A { block: (block)) (method_definition modifiers: (method_modifiers - (inflight_specifier)) + (phase_specifier)) name: (identifier) parameter_list: (parameter_list) block: (block)) @@ -66,7 +66,7 @@ inflight class A extends B {} (source (class_definition modifiers: (class_modifiers - (inflight_specifier)) + (phase_specifier)) name: (identifier) parent: (custom_type object: (type_identifier)) @@ -103,7 +103,7 @@ class A { parameter_list: (parameter_list) block: (block)) (initializer - inflight: (inflight_specifier) + phase_specifier: (phase_specifier) parameter_list: (parameter_list) block: (block)) (method_definition @@ -118,14 +118,14 @@ class A { block: (block)) (method_definition modifiers: (method_modifiers - (inflight_specifier)) + (phase_specifier)) name: (identifier) parameter_list: (parameter_list) block: (block)) (method_definition modifiers: (method_modifiers (access_modifier) - (inflight_specifier)) + (phase_specifier)) name: (identifier) parameter_list: (parameter_list) type: (builtin_type) @@ -133,7 +133,7 @@ class A { (method_definition modifiers: (method_modifiers (access_modifier) - (inflight_specifier)) + (phase_specifier)) name: (identifier) parameter_list: (parameter_list) type: (builtin_type) @@ -148,33 +148,33 @@ class A { type: (builtin_type)) (class_field modifiers: (field_modifiers - (inflight_specifier)) + (phase_specifier)) name: (identifier) type: (builtin_type)) (class_field modifiers: (field_modifiers (access_modifier) - (inflight_specifier) + (phase_specifier) (reassignable)) name: (identifier) type: (builtin_type)) (class_field modifiers: (field_modifiers (access_modifier) - (inflight_specifier) + (phase_specifier) (reassignable)) name: (identifier) type: (builtin_type)) (class_field modifiers: (field_modifiers (static) - (inflight_specifier)) + (phase_specifier)) name: (identifier) type: (builtin_type)) (method_definition modifiers: (method_modifiers (static) - (inflight_specifier)) + (phase_specifier)) name: (identifier) parameter_list: (parameter_list) block: (block))))) @@ -214,18 +214,19 @@ pub inflight interface A extends B, C { (interface_definition modifiers: (interface_modifiers (access_modifier) - (inflight_specifier)) + (phase_specifier)) name: (identifier) extends: (custom_type object: (type_identifier)) extends: (custom_type object: (type_identifier)) implementation: (interface_implementation - (method_signature + (method_definition name: (identifier) parameter_list: (parameter_list)) - (inflight_method_signature - phase_modifier: (inflight_specifier) + (method_definition + modifiers: (method_modifiers + (phase_specifier)) name: (identifier) parameter_list: (parameter_list (parameter_definition diff --git a/libs/tree-sitter-wing/test/corpus/statements/statements.txt b/libs/tree-sitter-wing/test/corpus/statements/statements.txt index 42437b99bd2..b585c9623ad 100644 --- a/libs/tree-sitter-wing/test/corpus/statements/statements.txt +++ b/libs/tree-sitter-wing/test/corpus/statements/statements.txt @@ -219,7 +219,7 @@ inflight (a: num, b: str?, var c: bool) => {}; (expression_statement (closure modifiers: (closure_modifiers - (inflight_specifier)) + (phase_specifier)) parameter_list: (parameter_list (parameter_definition name: (identifier) @@ -246,7 +246,7 @@ inflight (callback: (num,num):bool) => {}; (expression_statement (closure modifiers: (closure_modifiers - (inflight_specifier)) + (phase_specifier)) parameter_list: (parameter_list (parameter_definition name: (identifier) @@ -269,7 +269,7 @@ inflight (var ...z: MutArray):bool => {}; (expression_statement (closure modifiers: (closure_modifiers - (inflight_specifier)) + (phase_specifier)) parameter_list: (parameter_list (parameter_definition reassignable: (reassignable) @@ -292,7 +292,7 @@ inflight (var x: num, y: Array, ...z: Array):bool => {}; (expression_statement (closure modifiers: (closure_modifiers - (inflight_specifier)) + (phase_specifier)) parameter_list: (parameter_list (parameter_definition reassignable: (reassignable) diff --git a/libs/wingc/src/lsp/completions.rs b/libs/wingc/src/lsp/completions.rs index bfc6a55c06f..f5dcf703ca6 100644 --- a/libs/wingc/src/lsp/completions.rs +++ b/libs/wingc/src/lsp/completions.rs @@ -62,7 +62,7 @@ fn check_ts_to_completions(interesting_node: &Node) -> bool { "class" | "struct" | "interface" | "test" => true, // Starting an inflight closure - "inflight_specifier" => true, + "phase_specifier" => true, // No completions are valid immediately following a closing brace ")" | "}" | "]" => true, diff --git a/libs/wingc/src/parser.rs b/libs/wingc/src/parser.rs index 8f57b9068e3..7f7f0437aff 100644 --- a/libs/wingc/src/parser.rs +++ b/libs/wingc/src/parser.rs @@ -671,7 +671,7 @@ impl<'s> Parser<'s> { fn build_lift_statement(&self, statement_node: &Node, phase: Phase) -> DiagnosticResult { // Lift statements are only legal in inflight if phase != Phase::Inflight { - return self.with_error("Lift blocks are only allowed in inflight phase", statement_node); + return self.with_error("Lift blocks are only allowed in inflight code blocks", statement_node); } let lift_qualifications_node = statement_node.child_by_field_name("lift_qualifications").unwrap(); @@ -1271,13 +1271,26 @@ impl<'s> Parser<'s> { } } - fn build_class_statement(&self, statement_node: &Node, class_phase: Phase) -> DiagnosticResult { + /// Builds a class field from a class_field node + /// + /// If a class's phase isn't specified, it falls back to the phase of the scope. + fn build_class_statement(&self, statement_node: &Node, scope_phase: Phase) -> DiagnosticResult { let class_modifiers = statement_node.child_by_field_name("modifiers"); - let class_phase = if self.get_modifier("inflight_specifier", &class_modifiers)?.is_some() { - Phase::Inflight + + let class_phase = if let Some(phase) = self.get_phase_specifier(&class_modifiers)? { + phase } else { - class_phase + scope_phase }; + if class_phase == Phase::Independent { + self + .with_error::( + "Unphased classes are not yet supported - see https://github.com/winglang/wing/issues/435", + &statement_node, + ) + .err(); + } + let mut cursor = statement_node.walk(); let mut fields = vec![]; let mut methods = vec![]; @@ -1328,7 +1341,21 @@ impl<'s> Parser<'s> { // the initializer is considered an inflight initializer if either the class is inflight // (and then everything inside it is inflight by definition) or if there's an "inflight" // modifier. - let is_inflight = class_phase == Phase::Inflight || class_element.child_by_field_name("inflight").is_some(); + let initializer_phase_node = class_element.child_by_field_name("phase_specifier"); + let initializer_phase = if let Some(phase) = self.build_phase(&initializer_phase_node) { + phase + } else { + class_phase + }; + if initializer_phase == Phase::Independent { + self + .with_error::( + "Class constructors cannot be unphased", + &initializer_phase_node.unwrap_or(class_element), + ) + .err(); + } + let is_inflight = initializer_phase == Phase::Inflight; if initializer.is_some() && !is_inflight { self .with_error::( @@ -1543,10 +1570,17 @@ impl<'s> Parser<'s> { hints: vec![], }); } - let phase = match self.get_modifier("inflight_specifier", &modifiers)? { + + let phase = match self.get_phase_specifier(&modifiers)? { + Some(phase) => phase, None => class_phase, - Some(_) => Phase::Inflight, }; + if phase == Phase::Independent { + self + .with_error::("Class fields cannot be unphased", &class_element) + .err(); + } + Ok(ClassField { name: self.node_symbol(&class_element.child_by_field_name("name").unwrap())?, member_type: self.build_type_annotation(get_actual_child_by_field_name(class_element, "type"), phase)?, @@ -1558,17 +1592,25 @@ impl<'s> Parser<'s> { }) } - fn build_interface_statement(&self, statement_node: &Node, phase: Phase) -> DiagnosticResult { + fn build_interface_statement(&self, statement_node: &Node, scope_phase: Phase) -> DiagnosticResult { let mut cursor = statement_node.walk(); let mut extends = vec![]; let mut methods = vec![]; let interface_modifiers = statement_node.child_by_field_name("modifiers"); - let phase = if self.get_modifier("inflight_specifier", &interface_modifiers)?.is_some() { - Phase::Inflight - } else { - phase + + let interface_phase = match self.get_phase_specifier(&interface_modifiers)? { + Some(phase) => phase, + None => scope_phase, }; + if interface_phase == Phase::Independent { + self + .with_error::( + "Unphased interfaces are not yet supported - see https://github.com/winglang/wing/issues/435", + &statement_node, + ) + .err(); + } let name = self.check_reserved_symbol(&statement_node.child_by_field_name("name").unwrap())?; let mut doc_builder = DocBuilder::new(self); @@ -1582,13 +1624,24 @@ impl<'s> Parser<'s> { continue; }; match interface_element.kind() { - "method_signature" => { - if let Ok((method_name, func_sig)) = self.build_interface_method(interface_element, phase) { - methods.push((method_name, func_sig, doc)) + "method_definition" => { + let modifiers = interface_element.child_by_field_name("modifiers"); + let access_modifier = self.get_modifier("access_modifier", &modifiers)?; + if access_modifier.is_some() { + self + .with_error::("Access modifiers are not allowed in interfaces", &modifiers.unwrap()) + .err(); } - } - "inflight_method_signature" => { - if let Ok((method_name, func_sig)) = self.build_interface_method(interface_element, Phase::Inflight) { + let method_phase = match self.get_phase_specifier(&modifiers)? { + Some(phase) => phase, + None => interface_phase, + }; + if method_phase == Phase::Independent { + self + .with_error::("Unphased methods on interfaces are not yet supported - see https://github.com/winglang/wing/issues/435", &statement_node) + .err(); + } + if let Ok((method_name, func_sig)) = self.build_interface_method(interface_element, method_phase) { methods.push((method_name, func_sig, doc)) } } @@ -1653,7 +1706,7 @@ impl<'s> Parser<'s> { methods, extends, access, - phase, + phase: interface_phase, })) } @@ -1662,6 +1715,14 @@ impl<'s> Parser<'s> { interface_element: Node, phase: Phase, ) -> DiagnosticResult<(Symbol, FunctionSignature)> { + // Make sure there's no statements block for interface methods + if let Some(body) = &interface_element.child_by_field_name("block") { + self + .build_error("Interface methods cannot have a body", &interface_element) + .with_annotation("Body defined here", self.node_span(body)) + .report(); + } + let name = interface_element.child_by_field_name("name").unwrap(); let method_name = self.node_symbol(&name)?; let func_sig = self.build_function_signature(&interface_element, phase, true)?; @@ -1706,24 +1767,36 @@ impl<'s> Parser<'s> { }) } - fn build_anonymous_closure(&self, anon_closure_node: &Node, phase: Phase) -> DiagnosticResult { - self.build_function_definition(None, anon_closure_node, phase, false, None) + fn build_anonymous_closure( + &self, + anon_closure_node: &Node, + scope_phase: Phase, + ) -> DiagnosticResult { + self.build_function_definition(None, anon_closure_node, scope_phase, false, None) } fn build_function_definition( &self, name: Option, func_def_node: &Node, - phase: Phase, + scope_phase: Phase, require_annotations: bool, doc: Option, ) -> DiagnosticResult { let modifiers = func_def_node.child_by_field_name("modifiers"); - let phase = match self.get_modifier("inflight_specifier", &modifiers)? { - Some(_) => Phase::Inflight, - None => phase, + let phase = match self.get_phase_specifier(&modifiers)? { + Some(phase) => phase, + None => scope_phase, }; + if phase == Phase::Independent { + self + .with_error::( + "Unphased functions are not yet supported - see https://github.com/winglang/wing/issues/435", + &func_def_node, + ) + .err(); + } let is_static = self.get_modifier("static", &modifiers)?.is_some(); @@ -1794,6 +1867,7 @@ impl<'s> Parser<'s> { Ok(res) } + fn build_udt(&self, type_node: &Node) -> DiagnosticResult { match type_node.kind() { "custom_type" => { @@ -1840,6 +1914,23 @@ impl<'s> Parser<'s> { } } + /// Get the phase specifier from an elements (closure/class/field/method) modifiers node (from a list of multiple modifiers) + fn get_phase_specifier(&self, modifiers: &Option) -> DiagnosticResult> { + Ok(self.build_phase(&self.get_modifier("phase_specifier", modifiers)?)) + } + + /// Given a phase specifier node parse the correct phase + fn build_phase(&self, maybe_access_modifer: &Option) -> Option { + match maybe_access_modifer { + Some(access_modifier) => match self.node_text(access_modifier) { + "inflight" => Some(Phase::Inflight), + "unphased" => Some(Phase::Independent), + other => panic!("Unexpected phase specifier: \"{}\"", other), + }, + None => None, + } + } + /// Get the access modifier an elements (closure/class/field/method) modifiers node (from a list of multiple modifiers) fn get_access_modifier(&self, modifiers: &Option) -> DiagnosticResult { Ok(self.build_access_modifier(&self.get_modifier("access_modifier", modifiers)?)) @@ -1857,7 +1948,7 @@ impl<'s> Parser<'s> { } } - fn build_type_annotation(&self, type_node: Option, phase: Phase) -> DiagnosticResult { + fn build_type_annotation(&self, type_node: Option, scope_phase: Phase) -> DiagnosticResult { let type_node = &match type_node { Some(node) => node, None => { @@ -1895,7 +1986,7 @@ impl<'s> Parser<'s> { other => return self.report_unimplemented_grammar(other, "builtin", type_node), }, "optional" => { - let inner_type = self.build_type_annotation(type_node.named_child(0), phase)?; + let inner_type = self.build_type_annotation(type_node.named_child(0), scope_phase)?; Ok(TypeAnnotation { kind: TypeAnnotationKind::Optional(Box::new(inner_type)), span, @@ -1908,7 +1999,7 @@ impl<'s> Parser<'s> { let mut parameters = vec![]; for param_type in param_type_list_node.named_children(&mut cursor) { - let t = self.build_type_annotation(Some(param_type), phase)?; + let t = self.build_type_annotation(Some(param_type), scope_phase)?; parameters.push(FunctionParameter { name: "".into(), @@ -1918,18 +2009,28 @@ impl<'s> Parser<'s> { }) } + let phase_node = type_node.child_by_field_name("phase_specifier"); + let phase = match self.build_phase(&phase_node) { + Some(phase) => phase, + None => scope_phase, + }; + if phase == Phase::Independent { + self + .with_error::( + "Unphased functions are not yet supported - see https://github.com/winglang/wing/issues/435", + &phase_node.unwrap_or(*type_node), + ) + .err(); + } + Ok(TypeAnnotation { kind: TypeAnnotationKind::Function(FunctionSignature { parameters, return_type: Box::new(self.build_type_annotation( Some(get_actual_child_by_field_name(*type_node, "return_type").unwrap()), - phase, + scope_phase, )?), - phase: if type_node.child_by_field_name("inflight").is_some() { - Phase::Inflight - } else { - phase // inherit from scope - }, + phase, }), span, }) @@ -1953,27 +2054,27 @@ impl<'s> Parser<'s> { let element_type = get_actual_child_by_field_name(*type_node, "type_parameter"); match container_type { "Map" => Ok(TypeAnnotation { - kind: TypeAnnotationKind::Map(Box::new(self.build_type_annotation(element_type, phase)?)), + kind: TypeAnnotationKind::Map(Box::new(self.build_type_annotation(element_type, scope_phase)?)), span, }), "MutMap" => Ok(TypeAnnotation { - kind: TypeAnnotationKind::MutMap(Box::new(self.build_type_annotation(element_type, phase)?)), + kind: TypeAnnotationKind::MutMap(Box::new(self.build_type_annotation(element_type, scope_phase)?)), span, }), "Array" => Ok(TypeAnnotation { - kind: TypeAnnotationKind::Array(Box::new(self.build_type_annotation(element_type, phase)?)), + kind: TypeAnnotationKind::Array(Box::new(self.build_type_annotation(element_type, scope_phase)?)), span, }), "MutArray" => Ok(TypeAnnotation { - kind: TypeAnnotationKind::MutArray(Box::new(self.build_type_annotation(element_type, phase)?)), + kind: TypeAnnotationKind::MutArray(Box::new(self.build_type_annotation(element_type, scope_phase)?)), span, }), "Set" => Ok(TypeAnnotation { - kind: TypeAnnotationKind::Set(Box::new(self.build_type_annotation(element_type, phase)?)), + kind: TypeAnnotationKind::Set(Box::new(self.build_type_annotation(element_type, scope_phase)?)), span, }), "MutSet" => Ok(TypeAnnotation { - kind: TypeAnnotationKind::MutSet(Box::new(self.build_type_annotation(element_type, phase)?)), + kind: TypeAnnotationKind::MutSet(Box::new(self.build_type_annotation(element_type, scope_phase)?)), span, }), "ERROR" => self.with_error("Expected builtin container type", type_node)?, @@ -2161,7 +2262,7 @@ impl<'s> Parser<'s> { expression_span, )) } - "binary_expression" | "unwrap_or" => Ok(Expr::new( + "binary_expression" => Ok(Expr::new( ExprKind::Binary { left: Box::new(self.build_expression(&expression_node.child_by_field_name("left").unwrap(), phase)?), right: Box::new(self.build_expression(&expression_node.child_by_field_name("right").unwrap(), phase)?), diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index bbf2afdbbff..66d0e30fa5d 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -5338,6 +5338,8 @@ new cloud.Function(@inflight("./handler.ts"), lifts: { bucket: ["put"] }); /// * `phase` - initializer phase /// fn check_class_field_initialization(&mut self, scope: &Scope, fields: &[ClassField], phase: Phase) { + // Traverse the AST of the constructor (preflight or inflight) to find all initialized fields + // that were initialized during its execution. let mut visit_init = VisitClassInit::default(); visit_init.analyze_statements(&scope.statements); let initialized_fields = visit_init.fields; @@ -5348,9 +5350,13 @@ new cloud.Function(@inflight("./handler.ts"), lifts: { bucket: ["put"] }); ("Preflight", Phase::Inflight) }; + // For each field on the class... for field in fields.iter() { + // Check if a field with that name was initialized in this phase's constructor... let matching_field = initialized_fields.iter().find(|&s| &s.name == &field.name.name); - // inflight or static fields cannot be initialized in the initializer + + // If the field is static or in the wrong phase, then it shouldn't have been initialized here, + // so we raise an error. if field.phase == forbidden_phase || field.is_static { if let Some(matching_field) = matching_field { self.spanned_error( @@ -5365,7 +5371,8 @@ new cloud.Function(@inflight("./handler.ts"), lifts: { bucket: ["put"] }); continue; } - if matching_field == None { + // If the field does match the constructor's phase and it wasn't initialized, then we raise an error. + if field.phase == phase && matching_field == None { self.spanned_error( &field.name, format!("{} field \"{}\" is not initialized", current_phase, field.name.name), diff --git a/tools/hangar/__snapshots__/invalid.ts.snap b/tools/hangar/__snapshots__/invalid.ts.snap index 88b8aee8130..89b2f9aa509 100644 --- a/tools/hangar/__snapshots__/invalid.ts.snap +++ b/tools/hangar/__snapshots__/invalid.ts.snap @@ -1643,14 +1643,14 @@ Duration " `; exports[`explicit_lift_qualification.test.w 1`] = ` -"error: Lift blocks are only allowed in inflight phase +"error: Lift blocks are only allowed in inflight code blocks --> ../../../examples/tests/invalid/explicit_lift_qualification.test.w:11:5 | 11 | lift {bucket: [put]}{} // Lift statment in preflight method | ^^^^^^^^^^^^^^^^^^^^^^ -error: Lift blocks are only allowed in inflight phase +error: Lift blocks are only allowed in inflight code blocks --> ../../../examples/tests/invalid/explicit_lift_qualification.test.w:21:1 | 21 | lift {bucket: [put]}{} @@ -2820,7 +2820,7 @@ error: Access modifiers are not allowed in interfaces --> ../../../examples/tests/invalid/interface.test.w:38:3 | 38 | pub method2(): str; - | ^^^^^^^^^^^^^^^^^^^ + | ^^^ error: Expected type annotation @@ -2837,13 +2837,6 @@ error: Expected type annotation | ^ -error: Unknown parser error - --> ../../../examples/tests/invalid/interface.test.w:38:14 - | -38 | pub method2(): str; - | ^^ - - error: Unknown symbol "IB" --> ../../../examples/tests/invalid/interface.test.w:4:22 | @@ -5302,6 +5295,84 @@ error: Member "methodWhichIsNotPartOfBucketApi" does not exist in "Bucket" +Tests 1 failed (1) +Snapshots 1 skipped +Test Files 1 failed (1) +Duration " +`; + +exports[`unphased.test.w 1`] = ` +"error: Unphased classes are not yet supported - see https://github.com/winglang/wing/issues/435 + --> ../../../examples/tests/invalid/unphased.test.w:1:1 + | +1 | unphased class UnphasedClass {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +error: Unphased interfaces are not yet supported - see https://github.com/winglang/wing/issues/435 + --> ../../../examples/tests/invalid/unphased.test.w:4:1 + | +4 | unphased interface UnphasedInterface {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +error: Unphased functions are not yet supported - see https://github.com/winglang/wing/issues/435 + --> ../../../examples/tests/invalid/unphased.test.w:7:18 + | + 7 | let unphasedFn = unphased () => { + | /------------------^ + 8 | | // ^ Error: Unphased functions are not yet supported + 9 | | log("Hello, world!"); +10 | | }; + | \\-^ + + +error: Unphased functions are not yet supported - see https://github.com/winglang/wing/issues/435 + --> ../../../examples/tests/invalid/unphased.test.w:12:35 + | +12 | let fnWithUnphasedParamType = (f: unphased (str): num) => { + | ^^^^^^^^ + + +error: Unphased functions are not yet supported - see https://github.com/winglang/wing/issues/435 + --> ../../../examples/tests/invalid/unphased.test.w:16:37 + | +16 | let fnWithUnphasedReturnType = (): (unphased (str): num)? => { + | ^^^^^^^^ + + +error: Class fields cannot be unphased + --> ../../../examples/tests/invalid/unphased.test.w:22:3 + | +22 | unphased myField: str; + | ^^^^^^^^^^^^^^^^^^^^^^ + + +error: Class constructors cannot be unphased + --> ../../../examples/tests/invalid/unphased.test.w:24:3 + | +24 | unphased new() {} + | ^^^^^^^^ + + +error: Unphased functions are not yet supported - see https://github.com/winglang/wing/issues/435 + --> ../../../examples/tests/invalid/unphased.test.w:26:3 + | +26 | unphased myMethod() {} + | ^^^^^^^^^^^^^^^^^^^^^^ + + +error: Unphased methods on interfaces are not yet supported - see https://github.com/winglang/wing/issues/435 + --> ../../../examples/tests/invalid/unphased.test.w:30:1 + | +30 | / interface IFoo { +31 | | unphased bar(): void; +32 | | // ^ Error: Unphased methods on interfaces are not yet supported +33 | | } + | \\-^ + + + Tests 1 failed (1) Snapshots 1 skipped Test Files 1 failed (1)