From 3f09a3e37acf334ae60c6dc990ec549187a2b948 Mon Sep 17 00:00:00 2001 From: yoav-steinberg Date: Fri, 15 Sep 2023 21:38:41 +0300 Subject: [PATCH] feat(compiler): access modifiers (#4133) See #108 This PR adds `public` and `protected` support to class members and while doing so makes all other members **private** by default. It also verifies interface method implementations must be `public`. It also includes support for JSII access modifiers. To get this working I did the following refactors: * Cleaned up fields in the `SymbolEnv` and instead added a `kind` field distinguishing between 3 types of environments: a simple code block (`Scope`) a function/method/closure environemt (`Function`) and an environment used to store members of a type (`Type`). This was primarily needed so I can use the result of a symbol env lookup to figure out in what ancestor class a given member was defined. The added benefit was code clarity and sanity checks which, btw, found a non-related bug in super constructor type checking (see below). One piece of such clarity was the long awaited removal of `return_type` from `SymbolEnv`. * Cleaned up the `ContextVisitor` which I needed to add to the `TypeChecker`. Here I removed the requirement to store the `init_env` in `push_class` which didn't make sense, and is probably a leftover of some iteration on our lifting code. I also cleaned up the `push_method` code to make more sense and now it's a more general `push_function` stack with a helper for when we're just interested in methods. * I added some more stuff to `VisitorWithContext` trait, for easy "push -> do stuff or even return -> pop" pattern. Specifically I needed the `TypeChecker` to push function definitions and current statement safely. * Use a `VisitorContext` in `TypeChecker` to track context so I know in what class I am for access modifier checking. This approach also provides a better way of tracking the current `return_type` so I could remove it from the env. This also removed all sorts of dirt from the `TypeChecker` struct (like `in_json`, etc.). * The `type_check_statement` method was getting way to big, so I moved the contents of its match statements into separate methods. * The sanity checks when creating new `SymbolEnv`s detected that we're creating a weird env in `type_check_super_constructor_against_parent_initializer`. I removed a large bunch of code there that didn't seem correct to me. * The actual access modifier verification takes place inside `get_property_from_class_like` (**<= this is where you want to look**). This way I avoid cascading errors and "unhydrated" classes. To make this work for all cases I updated `resolve_referece` for static type references to also use this method instead of doing its own thing. I think this is cleaner. Still left and possible candidate for followup PRs: - [x] Tests for JSII imports with access modifiers - [x] Handle method overriding rules - [x] Support `public` for types (export types from module) -> out of scope, will be handled added by our module system. - [x] Decide if we allow `public` fields: tracking issue #4180 - [x] Decide about `internal`: tracking issue #4156 ## 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)*. --- docs/docs/03-language-reference.md | 119 +- docs/docs/07-examples/08-classes.md | 16 +- examples/proposed/cacheable-function.w | 2 +- examples/proposed/counting-semaphore.w | 8 +- .../tests/invalid/access_modifiers.main.w | 169 ++ .../access_static_from_instance.main.w | 4 +- .../call_inflight_from_preflight.main.w | 2 +- examples/tests/invalid/impl_interface.main.w | 2 +- ...t_class_interface_structural_typing.main.w | 2 +- .../invalid/inflight_ref_explicit_ops.main.w | 12 +- .../inflight_ref_resource_sub_method.main.w | 6 +- .../invalid/inflight_ref_unknown_op.main.w | 2 +- .../invalid/jsii_access_modifiers.main.w | 17 + examples/tests/invalid/nil.main.w | 2 +- .../invalid/preflight_from_inflight.main.w | 2 +- .../reassign_to_nonreassignable.main.w | 2 +- .../resource_access_field_as_method.main.w | 2 +- .../tests/sdk_tests/function/logging.main.w | 2 +- .../tests/sdk_tests/queue/set_consumer.main.w | 2 +- .../tests/sdk_tests/topic/on_message.main.w | 2 +- examples/tests/sdk_tests/util/sleep.main.w | 2 +- examples/tests/sdk_tests/util/uuidv4.main.w | 4 +- .../tests/sdk_tests/util/wait-until.main.w | 2 +- .../tests/sdk_tests/website/website.main.w | 2 +- examples/tests/valid/assertions.w | 6 +- examples/tests/valid/baz.w | 2 +- examples/tests/valid/bring_local.main.w | 2 +- .../tests/valid/call_static_of_myself.main.w | 10 +- .../valid/calling_inflight_variants.main.w | 4 +- .../capture_reassigable_class_field.main.w | 6 +- .../capture_resource_with_no_inflight.main.w | 4 +- examples/tests/valid/capture_tokens.main.w | 4 +- examples/tests/valid/class.main.w | 22 +- examples/tests/valid/closure_class.main.w | 2 +- examples/tests/valid/double_reference.main.w | 6 +- examples/tests/valid/doubler.main.w | 2 +- examples/tests/valid/dynamo.main.w | 4 +- examples/tests/valid/dynamo_awscdk.main.w | 6 +- .../tests/valid/extern_implementation.main.w | 8 +- examples/tests/valid/impl_interface.main.w | 12 +- .../valid/inflight_capture_static.main.w | 6 +- .../inflight_class_as_struct_members.main.w | 2 +- .../valid/inflight_class_capture_const.main.w | 2 +- .../valid/inflight_class_definitions.main.w | 16 +- ...nflight_class_inner_capture_mutable.main.w | 2 +- ...light_class_inside_inflight_closure.main.w | 6 +- ...ight_class_outside_inflight_closure.main.w | 2 +- ...t_class_structural_interace_handler.main.w | 2 +- .../valid/inflights_calling_inflights.main.w | 2 +- examples/tests/valid/json.main.w | 2 +- .../tests/valid/lift_expr_with_this.main.w | 2 +- examples/tests/valid/lift_this.main.w | 2 +- examples/tests/valid/lift_via_closure.main.w | 4 +- examples/tests/valid/nil.main.w | 6 +- examples/tests/valid/optionals.main.w | 8 +- examples/tests/valid/reassignment.main.w | 4 +- examples/tests/valid/resource.main.w | 18 +- .../valid/resource_as_inflight_literal.main.w | 2 +- .../tests/valid/resource_call_static.main.w | 2 +- examples/tests/valid/resource_captures.main.w | 32 +- .../valid/resource_captures_globals.main.w | 14 +- examples/tests/valid/static_members.main.w | 8 +- examples/tests/valid/store.w | 2 +- examples/tests/valid/subdir/bar.w | 2 +- examples/tests/valid/subdir/foo.w | 6 +- examples/tests/valid/super_call.main.w | 14 +- libs/tree-sitter-wing/grammar.js | 2 +- .../corpus/statements/class_and_resource.txt | 26 +- libs/wingc/src/ast.rs | 39 +- libs/wingc/src/closure_transform.rs | 9 +- libs/wingc/src/fold.rs | 10 +- libs/wingc/src/jsify.rs | 8 +- .../base_class_captures_inflight.snap | 2 +- .../base_class_captures_preflight.snap | 2 +- .../snapshots/base_class_lift_indirect.snap | 2 +- .../base_class_with_fields_inflight.snap | 2 +- .../base_class_with_fields_preflight.snap | 2 +- .../base_class_with_lifted_field_object.snap | 2 +- .../base_class_with_lifted_fields.snap | 2 +- ..._static_inflight_from_static_inflight.snap | 2 +- ...from_preflight_scope_with_method_call.snap | 2 +- ...om_preflight_scope_with_nested_object.snap | 2 +- .../snapshots/capture_type_static_method.snap | 2 +- ...ure_type_static_method_inflight_class.snap | 2 +- .../src/jsify/snapshots/closure_field.snap | 2 +- ..._with_lifted_field_object_unqualified.snap | 1 + .../snapshots/fails_on_preflight_static.snap | 1 + .../src/jsify/snapshots/indirect_capture.snap | 2 +- libs/wingc/src/jsify/snapshots/lift_this.snap | 2 +- .../jsify/snapshots/lift_var_with_this.snap | 2 +- ...ed_inflight_after_preflight_operation.snap | 2 +- .../snapshots/nested_preflight_operation.snap | 4 +- ...eflight_nested_object_with_operations.snap | 2 +- .../src/jsify/snapshots/preflight_object.snap | 4 +- .../preflight_object_through_property.snap | 2 +- .../snapshots/preflight_value_field.snap | 4 +- .../snapshots/reference_inflight_class.snap | 2 +- .../snapshots/reference_static_inflight.snap | 2 +- ...ght_which_references_preflight_object.snap | 2 +- .../static_external_inflight_class.snap | 2 +- .../static_external_preflight_class.snap | 2 +- .../snapshots/static_inflight_operation.snap | 2 +- .../static_local_inflight_class.snap | 2 +- .../jsify/snapshots/transitive_reference.snap | 2 +- ...ansitive_reference_via_inflight_class.snap | 2 +- .../transitive_reference_via_static.snap | 4 +- libs/wingc/src/jsify/tests.rs | 72 +- libs/wingc/src/lib.rs | 4 +- libs/wingc/src/lifting.rs | 19 +- libs/wingc/src/lsp/hover.rs | 8 +- libs/wingc/src/parser.rs | 35 +- libs/wingc/src/type_check.rs | 1904 +++++++++-------- libs/wingc/src/type_check/jsii_importer.rs | 70 +- libs/wingc/src/type_check/symbol_env.rs | 72 +- libs/wingc/src/visit.rs | 8 +- libs/wingc/src/visit_context.rs | 91 +- tools/hangar/__snapshots__/invalid.ts.snap | 253 ++- 117 files changed, 2079 insertions(+), 1278 deletions(-) create mode 100644 examples/tests/invalid/access_modifiers.main.w create mode 100644 examples/tests/invalid/jsii_access_modifiers.main.w diff --git a/docs/docs/03-language-reference.md b/docs/docs/03-language-reference.md index 2330fc6f7cc..223e6ba3b24 100644 --- a/docs/docs/03-language-reference.md +++ b/docs/docs/03-language-reference.md @@ -654,7 +654,73 @@ Static class fields are not supported yet, see https://github.com/winglang/wing/ --- -### 1.5 Reassignability +### 1.5 Access Modifiers (member visibility) + +Class members, by default, can only be accessed from within the implementation code of the same class (private). +Inner classes or closures can access private members of their containing class. +```TS +class Foo { + private_field: num; // This is private by default + + init() {this.private_field = 1;} + + method() { + log(this.private_field); // We can access `private_field` since we're in Foo + + class InnerFoo { + method(f: Foo) { + log(f.private_field); // We can access `private_field` since we're in Foo + } + } + } +} +``` +Accessing class members of a super class can be done by adding the the `protected` access modifier. +```TS +class Foo { + protected protected_method() {}; // This is a `protected` method +} + +class Bar extends Foo { + method() { + this.protected_method(); // We can access `protected_method` from a subclass + } +} +``` +The `pub` access modifier makes the class member accessible from anywhere. +Interface members are always public. +Implementing interface members in a class requires explicitly flagging them as `pub`. +```TS +interface FooInterface { + interface_method(): void; // Interface definitions are always implicitly `pub` +} + +class Foo impl FooInterface { + pub public_method() {} // This can be accessed from outside of the class implemenetation + pub interface_method() {} // This must be explicitly defined as `pub` since it's an interface implementation +} +let f = new Foo(); +f.public_method(); // We can call this method from outside the class - it's public +``` + +Access modifier rules apply for both fields and methods of a class. +Struct fields are always public and do not have access modifiers. + +#### 1.5.1 Method overriding and access modifiers +Private methods cannot be overriden. +Overriding a method of a parent class requires the parent class's method to be either `pub` or `protected`. +The overriding method can have either the same access modifier as the original method or a more permissive one. +You cannot "decrease" the access level down the inheritence hierarchy, only "increase" it. +In practice this means: +* `protected` methods can be overidden by either a `protected` or a `pub` method. +* `pub` methods can be overriden by a `pub` method. + +Note that method overriding only applies to instance methods. `static` methods are not treated as part of the inheritence hierarcy. + +[`▲ top`][top] + +--- +### 1.6 Reassignability Re-assignment to variables that are defined with `let` is not allowed in Wing. @@ -700,7 +766,7 @@ let f = (arg1: num, var arg2: num) => { --- -### 1.6 Optionality +### 1.7 Optionality Nullity is a primary source of bugs in software. Being able to guarantee that a value will never be null makes it easier to write safe code without constantly having to take nullity into account. @@ -723,9 +789,9 @@ Here's a quick summary of how optionality works in Wing: * The keyword `nil` can be used in assignment scenarios to indicate that an optional doesn't have a value. It cannot be used to test if an optional has a value or not. -#### 1.6.1 Declaration +#### 1.7.1 Declaration -##### 1.6.1.1 Struct fields +##### 1.7.1.1 Struct fields One of the more common use cases for optionals is to use them in struct declarations. @@ -746,7 +812,7 @@ assert(david.address? == false); assert(jonathan.address? == true); ``` -##### 1.6.1.2 Variables +##### 1.7.1.2 Variables Use `T?` to indicate that a variable is optional. To initialize it without a value use `= nil`. @@ -764,7 +830,7 @@ x = nil; assert(x? == false); ``` -##### 1.6.1.3 Class fields +##### 1.7.1.3 Class fields Similarly to struct fields, fields of classes can be also defined as optional using `T?`: @@ -784,7 +850,7 @@ class Foo { } ``` -##### 1.6.1.4 Function arguments +##### 1.7.1.4 Function arguments In the following example, the argument `by` is optional, so it is possible to call `increment()` without supplying a value for `by`: @@ -826,7 +892,7 @@ f(myRequired: "hello"); f(myOptional: 12, myRequired: "dang"); ``` -##### 1.6.1.5 Function return types +##### 1.7.1.5 Function return types If a function returns an optional type, use the `return nil;` statement to indicate that the value is not defined. @@ -852,7 +918,7 @@ if let name = tryParseName("Neo Matrix") { } ``` -#### 1.6.2 Testing using `x?` +#### 1.7.2 Testing using `x?` To test if an optional has a value or not, you can either use `x == nil` or `x != nil` or the special syntax `x?`. @@ -883,7 +949,7 @@ if myPerson.address == nil { } ``` -#### 1.6.3 Unwrapping using `if let` +#### 1.7.3 Unwrapping using `if let` The `if let` statement (or `if let var` for a reassignable variable) can be used to test if an optional is defined and *unwrap* it into a non-optional variable defined inside the block: @@ -899,7 +965,7 @@ if let address = myPerson.address { > multiple conditions, or unwrapping multiple optionals. This is something we might consider in the > future. -#### 1.6.4 Unwrapping or default value using `??` +#### 1.7.4 Unwrapping or default value using `??` The `??` operator can be used to unwrap or provide a default value. This returns a value of `T` that can safely be used. @@ -908,7 +974,7 @@ can safely be used. let address: str = myPerson.address ?? "Planet Earth"; ``` -#### 1.6.5 Optional chaining using `?.` +#### 1.7.5 Optional chaining using `?.` The `?.` syntax can be used for optional chaining. Optional chaining returns a value of type `T?` which must be unwrapped in order to be used. @@ -925,7 +991,7 @@ if let ip = ipAddress { --- -#### 1.6.6 Roadmap +#### 1.7.6 Roadmap The following features are not yet implemented, but we are planning to add them in the future: @@ -944,7 +1010,7 @@ The following features are not yet implemented, but we are planning to add them * Support `??` for different types if they have a common ancestor (and also think of interfaces). See https://github.com/winglang/wing/issues/2103 to track. -### 1.7 Type Inference +### 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 @@ -972,7 +1038,7 @@ Type annotations are required for method arguments and their return value but op --- -### 1.8 Error Handling +### 1.9 Error Handling Exceptions and `try/catch/finally` are the error mechanism. Mechanics directly translate to JavaScript. You can create a new exception with a `throw` call. @@ -997,7 +1063,7 @@ In the presence of `catch` the variable holding the exception (`e` in the exampl --- -### 1.9 Recommended Formatting +### 1.10 Recommended Formatting Wing recommends the following formatting and naming conventions: @@ -1013,7 +1079,7 @@ Wing recommends the following formatting and naming conventions: --- -### 1.10 Memory Management +### 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). @@ -1022,7 +1088,7 @@ Wing and is garbage collected (relying on JSII target GC for the meantime). --- -### 1.11 Execution Model +### 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. @@ -1043,7 +1109,7 @@ case of CDK for Terraform target. --- -### 1.12 Asynchronous Model +### 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 @@ -1056,17 +1122,17 @@ Main concepts to understand: Contrary to JavaScript, any call to an async function is implicitly awaited in Wing. -#### 1.12.1 Roadmap +#### 1.13.1 Roadmap The following features are not yet implemented, but we are planning to add them in the future: * `await`/`defer` statements - see https://github.com/winglang/wing/issues/116 to track. * Promise function type - see https://github.com/winglang/wing/issues/1004 to track. -### 1.13 Roadmap +### 1.14 Roadmap -Access modifiers (`private`/`public`/`internal`/`protected`) are not yet implemented. -See https://github.com/winglang/wing/issues/108 to track. +* Module type visibility (exports/`pub` types) is not implemented yet - see https://github.com/winglang/wing/issues/130 to track. +* `internal` access modifier is not yet implemented - see https://github.com/winglang/wing/issues/4156 to track. ## 2. Statements @@ -1310,7 +1376,7 @@ class Bar { this.z = new Foo(); this.log(); // OK to call here } - public log() { + pub log() { log("${this.y}"); } } @@ -1331,7 +1397,7 @@ their "strict" mode. class Foo { x: num; init() { this.x = 0; } - public method() { } + pub method() { } } class Boo extends Foo { init() { @@ -1349,7 +1415,7 @@ Classes can implement interfaces iff the interfaces do not contain `inflight`. class Foo { x: num; init() { this.x = 0; } - public method() { } + pub method() { } } class Boo extends Foo { init() { super(); this.x = 10; } @@ -1372,7 +1438,6 @@ In methods if return type is missing, `: void` is assumed. The following features are not yet implemented, but we are planning to add them in the future: * Overloading class methods (including `init`) - see https://github.com/winglang/wing/issues/3123 to track. -* Overriding class methods - see https://github.com/winglang/wing/issues/1124 to track. * Using the `final` keyword to stop the inheritance chain - see https://github.com/winglang/wing/issues/460 to track. [`▲ top`][top] diff --git a/docs/docs/07-examples/08-classes.md b/docs/docs/07-examples/08-classes.md index 60691e4b9dc..cb78702f43d 100644 --- a/docs/docs/07-examples/08-classes.md +++ b/docs/docs/07-examples/08-classes.md @@ -28,7 +28,7 @@ class Foo { log("at inflight init"); } - inflight doStuff() { + pub inflight doStuff() { // all code is async and runs on the cloud log("field3[0]='${this.field3.at(0)}'"); util.sleep(1s); @@ -55,7 +55,7 @@ interface IProfile { } inflight class WingPerson impl IProfile { - inflight name(): str { + pub inflight name(): str { return "Fairy Wing"; } } @@ -84,10 +84,10 @@ class BucketBasedKeyValueStore impl IKVStore { init() { this.bucket = new cloud.Bucket(); } - inflight get(key: str): Json { + pub inflight get(key: str): Json { return this.bucket.getJson(key); } - inflight set(key: str, value: Json): void { + pub inflight set(key: str, value: Json): void { this.bucket.putJson(key, value); } } @@ -108,10 +108,10 @@ class BucketBasedKeyValueStore impl IKVStore { init() { this.bucket = new cloud.Bucket(); } - inflight get(key: str): Json { + pub inflight get(key: str): Json { return this.bucket.getJson(key); } - inflight set(key: str, value: Json): void { + pub inflight set(key: str, value: Json): void { this.bucket.putJson(key, value); } } @@ -127,10 +127,10 @@ class TableBasedKeyValueStore impl IKVStore { } ); } - inflight get(key: str): Json { + pub inflight get(key: str): Json { return this.table.get(key); } - inflight set(key: str, value: Json): str { + pub inflight set(key: str, value: Json): str { this.table.insert(key, value); } } diff --git a/examples/proposed/cacheable-function.w b/examples/proposed/cacheable-function.w index 0fdb66ca309..40312c7118d 100644 --- a/examples/proposed/cacheable-function.w +++ b/examples/proposed/cacheable-function.w @@ -9,7 +9,7 @@ resource CachableFunction extends cloud.Function { this._cache = new cloud.Bucket(); } - public override inflight invoke(event: any): any { + pub override inflight invoke(event: any): any { let key = hashof(event); let cached = this._cache.try_get(key); if cached { diff --git a/examples/proposed/counting-semaphore.w b/examples/proposed/counting-semaphore.w index b73f2e18da7..babbab1dda6 100644 --- a/examples/proposed/counting-semaphore.w +++ b/examples/proposed/counting-semaphore.w @@ -5,7 +5,7 @@ struct CountingSemaphoreProps { } resource CountingSemaphore { - public limit: num; + pub limit: num; _counter: cloud.Counter; // need some ttl solution here, @@ -19,7 +19,7 @@ resource CountingSemaphore { // some stable unique instance id is wanted in the inflight context // so that a resource instance can be properly claimed and later released // when used in conjunction with a key-value store - public inflight try_acquire(): bool { + pub inflight try_acquire(): bool { if this.is_at_capacity() { return false; } @@ -35,7 +35,7 @@ resource CountingSemaphore { // probably key-value store is wanted, // so that a specific resource instance can be released // rather than naively releasing a count - public inflight release() { + pub inflight release() { if this._counter.peek() <= 0 { return; } @@ -43,7 +43,7 @@ resource CountingSemaphore { this._counter.dec(); } - public inflight is_at_capacity(): bool { + pub inflight is_at_capacity(): bool { return this._counter.peek() >= this.limit; } } diff --git a/examples/tests/invalid/access_modifiers.main.w b/examples/tests/invalid/access_modifiers.main.w new file mode 100644 index 00000000000..cf0cc12b912 --- /dev/null +++ b/examples/tests/invalid/access_modifiers.main.w @@ -0,0 +1,169 @@ +interface SomeInterface { + public_method(): void; + protected_method(): void; + private_method(): void; +} + +class Foo impl SomeInterface { + protected protected_field: str; + private_field: str; + pub public_field: str; + + init() { + this.protected_field = "hello"; + this.private_field = "world"; + this.public_field = "!"; + } + + method() { + // All of these are valid + log(this.protected_field); + log(this.private_field); + log(this.public_field); + this.public_method(); + this.protected_method(); + this.private_method(); + + class InnerFoo { + method(f: Foo) { + // Check access from inner class + log(f.protected_field); + log(f.private_field); + log(f.public_field); + } + } + } + + pub public_method() {} + protected protected_method() {} + // ^ Method is not public but it's part of an interface + private_method() {} + //^ Method is not public but it's part of an interface + + pub static public_static_method() {} + protected static protected_static_method() {} + static private_static_method() {} +} + +class Bar extends Foo { + method() { + // Check protected access + log(this.protected_field); + log(this.private_field); + // ^ Cannot access private member + log(this.public_field); + this.public_method(); + this.protected_method(); + this.private_method(); + // ^ Cannot access private member + } + + static static_method() { + // Check static protected access + Foo.public_static_method(); + Foo.protected_static_method(); + Foo.private_static_method(); + // ^ Cannot access private member + } +} + +// Check 2nd level inheritance +class Baz extends Bar { + method() { + // Check protected access + log(this.protected_field); + log(this.private_field); + // ^ Cannot access private member + log(this.public_field); + this.public_method(); + this.protected_method(); + this.private_method(); + // ^ Cannot access private member + } + + static static_method() { + // Check static protected access + Foo.public_static_method(); + Foo.protected_static_method(); + Foo.private_static_method(); + // ^ Cannot access private member + // Check static protected access using parent class (in which the methods are not defined) + Bar.public_static_method(); + Bar.protected_static_method(); + Bar.private_static_method(); + // ^ Cannot access private member + } +} + +let foo = new Foo(); +// Chck access from non class +log(foo.protected_field); +// ^ Cannot access protected member +log(foo.private_field); +// ^ Cannot access private member +log(foo.public_field); +Foo.public_static_method(); +Foo.protected_static_method(); +// ^ Cannot access protected member +Foo.private_static_method(); +// ^ Cannot access private member + +// Check access from non child class (protected is not allowed) +class BarBar { + method() { + log(foo.protected_field); + // ^ Cannot access protected member + log(foo.private_field); + // ^ Cannot access private member + log(foo.public_field); + foo.public_method(); + foo.protected_method(); + // ^ Cannot access protected member + foo.private_method(); + // ^ Cannot access private member + } + + static static_method() { + Foo.public_static_method(); + Foo.protected_static_method(); + // ^ Cannot access protected member + Foo.private_static_method(); + // ^ Cannot access private member + } +} + +// Test overriding methods with different access modifiers +// to from +// private private err +// private protected err +// private public err +// protected private err +// protected protected ok +// protected public err +// public private err +// public protected ok +// public public ok + +class FooOverrideToPrivate extends Foo { + public_method() {} + //^ Cannot override + protected_method() {} + //^ Cannot override + private_method() {} + //^ Cannot override +} + +class FooOverrideToProtected extends Foo { + protected public_method() {} + // ^ Cannot override + protected protected_method() {} + protected private_method() {} + // ^ Cannot override +} + +class FooOverrideToPublic extends Foo { + pub public_method() {} + pub protected_method() {} + pub private_method() {} + // ^ Cannot override +} diff --git a/examples/tests/invalid/access_static_from_instance.main.w b/examples/tests/invalid/access_static_from_instance.main.w index e12b91b56ca..708a248b5b3 100644 --- a/examples/tests/invalid/access_static_from_instance.main.w +++ b/examples/tests/invalid/access_static_from_instance.main.w @@ -1,9 +1,9 @@ class Foo { instanceField: num; - static f: num; + pub static f: num; - static m() { + pub static m() { this.instanceField = 1; // Can't access instance fields from static methods this.f = 1; // Can't access static fields through `this` } diff --git a/examples/tests/invalid/call_inflight_from_preflight.main.w b/examples/tests/invalid/call_inflight_from_preflight.main.w index 0a385ddb614..7c18edd1007 100644 --- a/examples/tests/invalid/call_inflight_from_preflight.main.w +++ b/examples/tests/invalid/call_inflight_from_preflight.main.w @@ -5,7 +5,7 @@ util.sleep(1s); //^^^^^^^^^^^^^^ Cannot call into inflight phase while preflight class Foo { - inflight do() {} + pub inflight do() {} } let foo = new Foo(); // Call an inflight method diff --git a/examples/tests/invalid/impl_interface.main.w b/examples/tests/invalid/impl_interface.main.w index 05012fec881..0587c593145 100644 --- a/examples/tests/invalid/impl_interface.main.w +++ b/examples/tests/invalid/impl_interface.main.w @@ -5,7 +5,7 @@ class A impl cloud.IQueueSetConsumerHandler { } class B impl cloud.IQueueSetConsumerHandler { - inflight handle(x: num) { + pub inflight handle(x: num) { // Error: Expected type to be "inflight (str): void", but got "inflight (num): void" instead return; } diff --git a/examples/tests/invalid/inflight_class_interface_structural_typing.main.w b/examples/tests/invalid/inflight_class_interface_structural_typing.main.w index 0c45c952c42..d5c3d5dba9d 100644 --- a/examples/tests/invalid/inflight_class_interface_structural_typing.main.w +++ b/examples/tests/invalid/inflight_class_interface_structural_typing.main.w @@ -13,7 +13,7 @@ inflight class NotGoo { test "f" { inflight class YesGoo impl IGoo { - notHandle(): void { + pub notHandle(): void { log("i am goo"); } } diff --git a/examples/tests/invalid/inflight_ref_explicit_ops.main.w b/examples/tests/invalid/inflight_ref_explicit_ops.main.w index beb54b518e2..4ec287fab90 100644 --- a/examples/tests/invalid/inflight_ref_explicit_ops.main.w +++ b/examples/tests/invalid/inflight_ref_explicit_ops.main.w @@ -2,14 +2,14 @@ bring cloud; class Another { myQueue: cloud.Queue; - anotherStr: str; + pub anotherStr: str; init () { this.myQueue = new cloud.Queue(); this.anotherStr = "bang"; } - inflight inflightReturnsResource(): cloud.Queue { + pub inflight inflightReturnsResource(): cloud.Queue { return this.myQueue; // ^^^^^^^^ Cannot qualify access to a lifted object } @@ -30,7 +30,7 @@ class Test { this.justStr = "hello"; } - inflight test1() { + pub inflight test1() { let x = this.b; // ^ Cannot qualify access to a lifted object x.put("hello", "world"); @@ -38,17 +38,17 @@ class Test { this.justBucket.put("hello", "world"); } - inflight test2() { + pub inflight test2() { let q = this.another.inflightReturnsResource(); q.push("push!"); } - inflight test3() { + pub inflight test3() { let b = this.array.at(1); assert(b.list().length == 0); } - inflight test4() { + pub inflight test4() { assert(this.another.anotherStr == "bang"); } } diff --git a/examples/tests/invalid/inflight_ref_resource_sub_method.main.w b/examples/tests/invalid/inflight_ref_resource_sub_method.main.w index 7722e57194d..b2dd9a15825 100644 --- a/examples/tests/invalid/inflight_ref_resource_sub_method.main.w +++ b/examples/tests/invalid/inflight_ref_resource_sub_method.main.w @@ -9,12 +9,12 @@ class Another { this.myQueue = new cloud.Queue(); } - inflight inflightReturnsResource(): cloud.Queue { + pub inflight inflightReturnsResource(): cloud.Queue { return this.myQueue; // ^^^^^^^^ Cannot qualify access to a lifted object } - inflight inflightReturnsResource2(): cloud.Queue { + pub inflight inflightReturnsResource2(): cloud.Queue { return globalQueue; // ^^^^^^^^^^^^ Cannot qualify access to a lifted object } @@ -27,7 +27,7 @@ class Test { this.another = new Another(); } - inflight test() { + pub inflight test() { let q = this.another.inflightReturnsResource(); q.push("push!"); diff --git a/examples/tests/invalid/inflight_ref_unknown_op.main.w b/examples/tests/invalid/inflight_ref_unknown_op.main.w index c981024a9d1..a0a89280dfc 100644 --- a/examples/tests/invalid/inflight_ref_unknown_op.main.w +++ b/examples/tests/invalid/inflight_ref_unknown_op.main.w @@ -9,7 +9,7 @@ class Test { this.b = new cloud.Bucket(); } - inflight test() { + pub inflight test() { let x = this.b; // ^ Cannot qualify which operations are performed on object x.put("hello", "world"); diff --git a/examples/tests/invalid/jsii_access_modifiers.main.w b/examples/tests/invalid/jsii_access_modifiers.main.w new file mode 100644 index 00000000000..c1f79fb6097 --- /dev/null +++ b/examples/tests/invalid/jsii_access_modifiers.main.w @@ -0,0 +1,17 @@ +// Import a JSII module (our sdk) +bring cloud; + +let b = new cloud.Bucket(); + +class MyBucket extends cloud.Bucket { + method() { + // Call protected method from withing subclass (should work) + b.createTopic(cloud.BucketEventType.CREATE); + } +} + +b.createTopic(cloud.BucketEventType.CREATE); +//^ Cannot access protected member + +// Call public method from outside the class (should work) +b.addObject("k", "v"); \ No newline at end of file diff --git a/examples/tests/invalid/nil.main.w b/examples/tests/invalid/nil.main.w index 235d80a6d84..1f702841714 100644 --- a/examples/tests/invalid/nil.main.w +++ b/examples/tests/invalid/nil.main.w @@ -9,7 +9,7 @@ class Foo { this.bar = 1; } - inflight setBar(b: num) { + pub inflight setBar(b: num) { this.bar = b; } } diff --git a/examples/tests/invalid/preflight_from_inflight.main.w b/examples/tests/invalid/preflight_from_inflight.main.w index 59e1babca86..1602b84d2de 100644 --- a/examples/tests/invalid/preflight_from_inflight.main.w +++ b/examples/tests/invalid/preflight_from_inflight.main.w @@ -1,7 +1,7 @@ bring cloud; class MyResource { - myPreflight() { } + pub myPreflight() { } } class Test { diff --git a/examples/tests/invalid/reassign_to_nonreassignable.main.w b/examples/tests/invalid/reassign_to_nonreassignable.main.w index 748066a2932..64a8c75278a 100644 --- a/examples/tests/invalid/reassign_to_nonreassignable.main.w +++ b/examples/tests/invalid/reassign_to_nonreassignable.main.w @@ -3,7 +3,7 @@ let x = 5; x = x + 1; class InnerR { - inflight inner: num; + pub inflight inner: num; inflight init() { this.inner = 1; } diff --git a/examples/tests/invalid/resource_access_field_as_method.main.w b/examples/tests/invalid/resource_access_field_as_method.main.w index d313e1b6ed3..1d3b2d5f405 100644 --- a/examples/tests/invalid/resource_access_field_as_method.main.w +++ b/examples/tests/invalid/resource_access_field_as_method.main.w @@ -1,5 +1,5 @@ class SomeResource { - name: str; + pub name: str; init() { this.name = "John Doe"; } diff --git a/examples/tests/sdk_tests/function/logging.main.w b/examples/tests/sdk_tests/function/logging.main.w index 0fa007c92b2..b33d0190cdf 100644 --- a/examples/tests/sdk_tests/function/logging.main.w +++ b/examples/tests/sdk_tests/function/logging.main.w @@ -1,7 +1,7 @@ bring cloud; class Util { - extern "./logging.js" static inflight logging(): void; + extern "./logging.js" pub static inflight logging(): void; } let f1 = new cloud.Function(inflight (input: str): void => { diff --git a/examples/tests/sdk_tests/queue/set_consumer.main.w b/examples/tests/sdk_tests/queue/set_consumer.main.w index 0119929cbed..fb863d004f2 100644 --- a/examples/tests/sdk_tests/queue/set_consumer.main.w +++ b/examples/tests/sdk_tests/queue/set_consumer.main.w @@ -12,7 +12,7 @@ class Predicate { this.c = c; } - inflight test(): bool{ + pub inflight test(): bool{ return this.c.peek() == 2; } } diff --git a/examples/tests/sdk_tests/topic/on_message.main.w b/examples/tests/sdk_tests/topic/on_message.main.w index 563825e949d..126da265559 100644 --- a/examples/tests/sdk_tests/topic/on_message.main.w +++ b/examples/tests/sdk_tests/topic/on_message.main.w @@ -10,7 +10,7 @@ class Predicate { this.c = c; } - inflight test(): bool{ + pub inflight test(): bool{ return this.c.peek() == 10; } } diff --git a/examples/tests/sdk_tests/util/sleep.main.w b/examples/tests/sdk_tests/util/sleep.main.w index 80de505b1c6..5793ac20349 100644 --- a/examples/tests/sdk_tests/util/sleep.main.w +++ b/examples/tests/sdk_tests/util/sleep.main.w @@ -1,7 +1,7 @@ bring util; class JSHelper { - extern "./sleep-helper.js" static inflight getTime(): num; + extern "./sleep-helper.js" pub static inflight getTime(): num; } let oneHundredMiliseconds = 0.1s; diff --git a/examples/tests/sdk_tests/util/uuidv4.main.w b/examples/tests/sdk_tests/util/uuidv4.main.w index 4dfbfb6d216..da958280ad6 100644 --- a/examples/tests/sdk_tests/util/uuidv4.main.w +++ b/examples/tests/sdk_tests/util/uuidv4.main.w @@ -1,14 +1,14 @@ bring util; class JSHelper { - extern "./uuidv4-helper.js" static validateUUIDv4(uuidv4: str): bool; + extern "./uuidv4-helper.js" pub static validateUUIDv4(uuidv4: str): bool; } let data = util.uuidv4(); assert(JSHelper.validateUUIDv4(data) == true); class JSHelperInflight { - extern "./uuidv4-helper.js" static inflight validateUUIDv4(uuidv4: str): bool; + extern "./uuidv4-helper.js" pub static inflight validateUUIDv4(uuidv4: str): bool; } test "inflight uuidv4" { diff --git a/examples/tests/sdk_tests/util/wait-until.main.w b/examples/tests/sdk_tests/util/wait-until.main.w index 73e5bbee064..44e454d451c 100644 --- a/examples/tests/sdk_tests/util/wait-until.main.w +++ b/examples/tests/sdk_tests/util/wait-until.main.w @@ -2,7 +2,7 @@ bring cloud; bring util; class JSHelper { - extern "./sleep-helper.js" static inflight getTime(): num; + extern "./sleep-helper.js" pub static inflight getTime(): num; } let invokeCounter = new cloud.Counter(); diff --git a/examples/tests/sdk_tests/website/website.main.w b/examples/tests/sdk_tests/website/website.main.w index 83703fdaa18..88c1f0ee0a2 100644 --- a/examples/tests/sdk_tests/website/website.main.w +++ b/examples/tests/sdk_tests/website/website.main.w @@ -5,7 +5,7 @@ let w = new cloud.Website(path: "./website"); let config = Json { json: 1 }; class Util { - extern "../external/fs.js" static readFile(path: str): str; + extern "../external/fs.js" pub static readFile(path: str): str; } let indexFile = Util.readFile("./website/website/index.html"); diff --git a/examples/tests/valid/assertions.w b/examples/tests/valid/assertions.w index d406d818bee..a62a5bf74a1 100644 --- a/examples/tests/valid/assertions.w +++ b/examples/tests/valid/assertions.w @@ -1,5 +1,5 @@ inflight class Assert { - static equalStr(a: str, b: str): bool { + pub static equalStr(a: str, b: str): bool { try { assert(a == b); } catch e { @@ -7,7 +7,7 @@ inflight class Assert { } } - static isNil(a: str?): bool { + pub static isNil(a: str?): bool { try { assert(a == nil); } catch e { @@ -16,7 +16,7 @@ inflight class Assert { } } - static equalNum(a: num, b: num): bool{ + pub static equalNum(a: num, b: num): bool{ try { assert(a == b); } catch e { diff --git a/examples/tests/valid/baz.w b/examples/tests/valid/baz.w index 3fd35230b61..6ede63e6be9 100644 --- a/examples/tests/valid/baz.w +++ b/examples/tests/valid/baz.w @@ -1,7 +1,7 @@ // used by bring_local_normalization.w class Baz { - static baz(): str { + pub static baz(): str { return "baz"; } } diff --git a/examples/tests/valid/bring_local.main.w b/examples/tests/valid/bring_local.main.w index cfa77a75442..1af2ed68182 100644 --- a/examples/tests/valid/bring_local.main.w +++ b/examples/tests/valid/bring_local.main.w @@ -23,7 +23,7 @@ assert(c != file1.Color.RED); // interfaces from other files can be used class Triangle impl file1.Shape { - area(): num { + pub area(): num { return 1; } } diff --git a/examples/tests/valid/call_static_of_myself.main.w b/examples/tests/valid/call_static_of_myself.main.w index 0c94eafae31..e82bc2bd72e 100644 --- a/examples/tests/valid/call_static_of_myself.main.w +++ b/examples/tests/valid/call_static_of_myself.main.w @@ -1,17 +1,17 @@ class Foo { - static inflight foo(): num { return 1; } + pub static inflight foo(): num { return 1; } static inflight bar(): num { return Foo.foo(); } - inflight callThis(): num { + pub inflight callThis(): num { return Foo.bar(); } } inflight class Bar { - static bar(): num { return 2; } + pub static bar(): num { return 2; } - callThis(): num { + pub callThis(): num { return Bar.bar(); } } @@ -20,7 +20,7 @@ let foo = new Foo(); test "test" { class Zoo { - static zoo(): num { return 3; } + pub static zoo(): num { return 3; } } let bar = new Bar(); diff --git a/examples/tests/valid/calling_inflight_variants.main.w b/examples/tests/valid/calling_inflight_variants.main.w index fa2bbf1c3f3..feb6fea9614 100644 --- a/examples/tests/valid/calling_inflight_variants.main.w +++ b/examples/tests/valid/calling_inflight_variants.main.w @@ -27,14 +27,14 @@ class Foo { } } - inflight callFn(x: bool): num { + pub inflight callFn(x: bool): num { // partialFn could be an inflight function created during preflight or inflight, // so we have to code-generate this to work for both cases let partialFn = this.makeFn(x); return partialFn(); } - inflight callFn2(): void { + pub inflight callFn2(): void { // now we call inflight1 and inflight2 directly which know they are handler classes let one = this.inflight1(); let two = this.inflight2(); diff --git a/examples/tests/valid/capture_reassigable_class_field.main.w b/examples/tests/valid/capture_reassigable_class_field.main.w index f81ad31889c..b3aaa6a26d3 100644 --- a/examples/tests/valid/capture_reassigable_class_field.main.w +++ b/examples/tests/valid/capture_reassigable_class_field.main.w @@ -9,15 +9,15 @@ class KeyValueStore { this.onUpdateCallback = inflight (k: str) => {}; } - onUpdate(fn: inflight (str):void){ + pub onUpdate(fn: inflight (str):void){ this.onUpdateCallback = fn; } - inflight get(key: str): Json { + pub inflight get(key: str): Json { this.onUpdateCallback(key); return this.bucket.getJson(key); } - inflight set(key: str, value: Json): void { + pub inflight set(key: str, value: Json): void { this.bucket.putJson(key, value); } } diff --git a/examples/tests/valid/capture_resource_with_no_inflight.main.w b/examples/tests/valid/capture_resource_with_no_inflight.main.w index d85b463b1ab..23881c55ffc 100644 --- a/examples/tests/valid/capture_resource_with_no_inflight.main.w +++ b/examples/tests/valid/capture_resource_with_no_inflight.main.w @@ -1,7 +1,7 @@ bring cloud; class A { - field: str; + pub field: str; counter: cloud.Counter; init() { @@ -13,7 +13,7 @@ class A { this.counter.inc(); } - inflight bar() { } + pub inflight bar() { } } let a = new A(); diff --git a/examples/tests/valid/capture_tokens.main.w b/examples/tests/valid/capture_tokens.main.w index de533ff6e99..de64a6c9b5a 100644 --- a/examples/tests/valid/capture_tokens.main.w +++ b/examples/tests/valid/capture_tokens.main.w @@ -4,14 +4,14 @@ class MyResource { api: cloud.Api; url: str; - extern "./url_utils.js" static inflight isValidUrl(url: str): bool; + extern "./url_utils.js" pub static inflight isValidUrl(url: str): bool; init() { this.api = new cloud.Api(); this.url = this.api.url; } - inflight foo() { + pub inflight foo() { assert(MyResource.isValidUrl(this.url)); assert(MyResource.isValidUrl(this.api.url)); } diff --git a/examples/tests/valid/class.main.w b/examples/tests/valid/class.main.w index ecb6c6526e2..3f6b21431d5 100644 --- a/examples/tests/valid/class.main.w +++ b/examples/tests/valid/class.main.w @@ -6,7 +6,7 @@ new C1(); // class with init and no arguments class C2 { - x: num; + pub x: num; init() { this.x = 1; } @@ -16,8 +16,8 @@ assert(c2.x == 1); // class with init and arguments class C3 { - x: num; - y: num; + pub x: num; + pub y: num; init(a: num, b: num) { this.x = a; if true { @@ -31,19 +31,19 @@ assert(c3.y == 2); // class with static method and no init class C4 { - static m():num {return 1;} + pub static m():num {return 1;} } assert(C4.m() == 1); // class with inflight field class C5 { - inflight x: num; - inflight var y: num; + pub inflight x: num; + pub inflight var y: num; inflight init() { this.x = 123; this.y = 321; } - inflight set(b: num) { + pub inflight set(b: num) { this.y = b; } } @@ -57,14 +57,14 @@ test "access inflight field" { } class Person { - name: str; + pub name: str; init(name: str) { this.name = name; } } class Student extends Person { - major: str; + pub major: str; init(name: str, major: str) { super(name); @@ -73,7 +73,7 @@ class Student extends Person { } class PaidStudent extends Student { - hrlyWage: num; + pub hrlyWage: num; init(name: str, major: str, hrlyWage: num) { super(name, major); this.hrlyWage = hrlyWage; @@ -103,7 +103,7 @@ test "devived class init body happens after super" { // Inflight inheritence inflight class A { - sound: str; + pub sound: str; inflight init(sound: str) { this.sound = sound; diff --git a/examples/tests/valid/closure_class.main.w b/examples/tests/valid/closure_class.main.w index 71877cba7ac..343d5268fd6 100644 --- a/examples/tests/valid/closure_class.main.w +++ b/examples/tests/valid/closure_class.main.w @@ -1,5 +1,5 @@ class MyClosure { - inflight another(): str { + pub inflight another(): str { return "hello"; } diff --git a/examples/tests/valid/double_reference.main.w b/examples/tests/valid/double_reference.main.w index dabd9849157..069c9898a39 100644 --- a/examples/tests/valid/double_reference.main.w +++ b/examples/tests/valid/double_reference.main.w @@ -7,16 +7,16 @@ class Foo { initCount.inc(); } - inflight method() { } + pub inflight method() { } } class Bar { - foo: Foo; + pub foo: Foo; init() { this.foo = new Foo(); } - inflight callFoo() { + pub inflight callFoo() { this.foo.method(); } } diff --git a/examples/tests/valid/doubler.main.w b/examples/tests/valid/doubler.main.w index bde9b2045e0..ae7b6c1330c 100644 --- a/examples/tests/valid/doubler.main.w +++ b/examples/tests/valid/doubler.main.w @@ -23,7 +23,7 @@ let fn = new Doubler(inflight (m: str): str => { class Doubler2 { // TODO: make into a static method - see https://github.com/winglang/wing/issues/2583 - makeFunc(handler: inflight (num): num): cloud.Function { + pub makeFunc(handler: inflight (num): num): cloud.Function { return new cloud.Function(inflight (x: str): str => { let xStr = num.fromStr(x); let y = handler(xStr); diff --git a/examples/tests/valid/dynamo.main.w b/examples/tests/valid/dynamo.main.w index e235cf44c06..62e4b2d8e4b 100644 --- a/examples/tests/valid/dynamo.main.w +++ b/examples/tests/valid/dynamo.main.w @@ -42,7 +42,7 @@ class DynamoTable { this.tableName = this.table.name; } - bind(host: std.IInflightHost, ops: Array) { + pub bind(host: std.IInflightHost, ops: Array) { if let host = aws.Function.from(host) { if ops.contains("putItem") { host.addPolicyStatements([aws.PolicyStatement { @@ -56,7 +56,7 @@ class DynamoTable { extern "./dynamo.js" static inflight _putItem(tableName: str, item: Json): void; - inflight putItem(item: Map) { + pub inflight putItem(item: Map) { let json = this._itemToJson(item); DynamoTable._putItem(this.tableName, json); } diff --git a/examples/tests/valid/dynamo_awscdk.main.w b/examples/tests/valid/dynamo_awscdk.main.w index 8c5f2c92959..5be0801c0e9 100644 --- a/examples/tests/valid/dynamo_awscdk.main.w +++ b/examples/tests/valid/dynamo_awscdk.main.w @@ -40,7 +40,7 @@ class DynamoTable { this.tableName = this.table.tableName; } - bind(host: std.IInflightHost, ops: Array) { + pub bind(host: std.IInflightHost, ops: Array) { if let host = aws.Function.from(host) { if ops.contains("putItem") { host.addPolicyStatements([aws.PolicyStatement { @@ -61,13 +61,13 @@ class DynamoTable { } extern "./dynamo.js" static inflight _putItem(tableName: str, item: Json): void; - inflight putItem(item: Map) { + pub inflight putItem(item: Map) { let json = this._itemToJson(item); DynamoTable._putItem(this.tableName, json); } extern "./dynamo.js" static inflight _getItem(tableName: str, key: Json): Json; - inflight getItem(key: Map): Json { + pub inflight getItem(key: Map): Json { let json = this._itemToJson(key); return DynamoTable._getItem(this.tableName, json); } diff --git a/examples/tests/valid/extern_implementation.main.w b/examples/tests/valid/extern_implementation.main.w index b5fac4684dd..c4da26f6e79 100644 --- a/examples/tests/valid/extern_implementation.main.w +++ b/examples/tests/valid/extern_implementation.main.w @@ -1,14 +1,14 @@ bring cloud; class Foo { - extern "./external_js.js" static getGreeting(name: str): str; + extern "./external_js.js" pub static getGreeting(name: str): str; extern "./external_js.js" static inflight regexInflight(pattern: str, text: str): bool; extern "./external_js.js" static inflight getUuid(): str; extern "./external_js.js" static inflight getData(): str; - extern "./external_js.js" static inflight print(msg: str); - extern "uuid" static v4(): str; + extern "./external_js.js" pub static inflight print(msg: str); + extern "uuid" pub static v4(): str; - inflight call() { + pub inflight call() { assert(Foo.regexInflight("[a-z]+-\\d+", "abc-123")); let uuid = Foo.getUuid(); assert(uuid.length == 36); diff --git a/examples/tests/valid/impl_interface.main.w b/examples/tests/valid/impl_interface.main.w index 5776ed36aa6..7e0db7a9320 100644 --- a/examples/tests/valid/impl_interface.main.w +++ b/examples/tests/valid/impl_interface.main.w @@ -1,7 +1,7 @@ bring cloud; class A impl cloud.IQueueSetConsumerHandler { - inflight handle(msg: str) { + pub inflight handle(msg: str) { return; } } @@ -25,13 +25,13 @@ interface I3 extends I2 { } class r impl I3 { - method1(x: num): num { + pub method1(x: num): num { return x; } - inflight method2(x: str): str { + pub inflight method2(x: str): str { return x; } - method3(x: Array): Array { + pub method3(x: Array): Array { return x; } } @@ -42,7 +42,7 @@ interface IAnimal { } class Dog impl IAnimal { - inflight eat() { + pub inflight eat() { return; } } @@ -51,7 +51,7 @@ let z: IAnimal = new Dog(); // base class is checked for implemention of interface class Terrier extends Dog { - inflight eat() { + pub inflight eat() { return; } } diff --git a/examples/tests/valid/inflight_capture_static.main.w b/examples/tests/valid/inflight_capture_static.main.w index 6af093db724..ffc45595046 100644 --- a/examples/tests/valid/inflight_capture_static.main.w +++ b/examples/tests/valid/inflight_capture_static.main.w @@ -1,13 +1,13 @@ bring util; class Preflight { - static inflight staticMethod(a: num): str { + pub static inflight staticMethod(a: num): str { return "foo-${a}"; } } inflight class OuterInflight { - static staticMethod(b: str): num { + pub static staticMethod(b: str): num { return b.length; } } @@ -22,7 +22,7 @@ test "call static method of an outer inflight class" { test "call static method of an inner inflight class" { class InnerInflight { - static staticMethod(): str { return "hello"; } + pub static staticMethod(): str { return "hello"; } } assert(InnerInflight.staticMethod() == "hello"); diff --git a/examples/tests/valid/inflight_class_as_struct_members.main.w b/examples/tests/valid/inflight_class_as_struct_members.main.w index 7893a91eb7e..36ddb68735e 100644 --- a/examples/tests/valid/inflight_class_as_struct_members.main.w +++ b/examples/tests/valid/inflight_class_as_struct_members.main.w @@ -1,5 +1,5 @@ inflight class Foo { - get(): num { return 42; } + pub get(): num { return 42; } } struct Bar { diff --git a/examples/tests/valid/inflight_class_capture_const.main.w b/examples/tests/valid/inflight_class_capture_const.main.w index 2805c2a9cc9..36226868c35 100644 --- a/examples/tests/valid/inflight_class_capture_const.main.w +++ b/examples/tests/valid/inflight_class_capture_const.main.w @@ -3,7 +3,7 @@ bring cloud; let myConst = "bang bang"; inflight class Foo { - getValue(): str { + pub getValue(): str { return myConst; } } diff --git a/examples/tests/valid/inflight_class_definitions.main.w b/examples/tests/valid/inflight_class_definitions.main.w index 620a004cf52..aea6633422a 100644 --- a/examples/tests/valid/inflight_class_definitions.main.w +++ b/examples/tests/valid/inflight_class_definitions.main.w @@ -1,18 +1,18 @@ class A { - foo(): str { return "a1"; } - inflight goo(): str { return "a2"; } + pub foo(): str { return "a1"; } + pub inflight goo(): str { return "a2"; } } let a = new A(); assert(a.foo() == "a1"); inflight class B { - foo(): str { return "b1"; } + pub foo(): str { return "b1"; } } let fn = inflight () => { inflight class C { - foo(): str { return "c1"; } + pub foo(): str { return "c1"; } } let c = new C(); @@ -24,14 +24,14 @@ class D { init() { class E { - foo(): str { return "e1"; } + pub foo(): str { return "e1"; } } let pb = new E(); assert(pb.foo() == "e1"); inflight class F { - foo(): str { return "f1"; } + pub foo(): str { return "f1"; } } this.inner = inflight (): str => { @@ -39,11 +39,11 @@ class D { }; } - getInner(): inflight (): str { + pub getInner(): inflight (): str { return this.inner; } - inflight callInner(): str { + pub inflight callInner(): str { return this.inner(); } } diff --git a/examples/tests/valid/inflight_class_inner_capture_mutable.main.w b/examples/tests/valid/inflight_class_inner_capture_mutable.main.w index 435d97b7a40..0ec5ee9acb7 100644 --- a/examples/tests/valid/inflight_class_inner_capture_mutable.main.w +++ b/examples/tests/valid/inflight_class_inner_capture_mutable.main.w @@ -5,7 +5,7 @@ test "inner inflight class capture immutable" { let var i = 10; class Inner { - dang(): num { + pub dang(): num { y.push(2); // since the inner class is defined within the same scope, it is actually possible to reassign diff --git a/examples/tests/valid/inflight_class_inside_inflight_closure.main.w b/examples/tests/valid/inflight_class_inside_inflight_closure.main.w index 6eb51931381..af7f6f23c03 100644 --- a/examples/tests/valid/inflight_class_inside_inflight_closure.main.w +++ b/examples/tests/valid/inflight_class_inside_inflight_closure.main.w @@ -5,7 +5,7 @@ class PreflightClass { init() { this.b = new cloud.Bucket(); } - preflight_method(): cloud.Function { + pub preflight_method(): cloud.Function { let inflight_closure = inflight (payload: str) => { this.b.put("k","v"); // Here `this` is the parent class instance inflight class InflightClass { @@ -13,7 +13,7 @@ class PreflightClass { init() { this.field = "value"; } - inflight method() { + pub inflight method() { assert(this.field == "value"); // Here `this` is the inflight class instance } } @@ -34,7 +34,7 @@ test "it works" { test "inflight class inside closure captures from closure" { let x = 12; class Foo { - getX(): num { return x; } + pub getX(): num { return x; } } let foo = new Foo(); diff --git a/examples/tests/valid/inflight_class_outside_inflight_closure.main.w b/examples/tests/valid/inflight_class_outside_inflight_closure.main.w index 7e91940e625..8f29146c218 100644 --- a/examples/tests/valid/inflight_class_outside_inflight_closure.main.w +++ b/examples/tests/valid/inflight_class_outside_inflight_closure.main.w @@ -9,7 +9,7 @@ inflight class BinaryOperation { this.rhs = rhs; } - add(): num { + pub add(): num { return this.lhs + this.rhs; } } diff --git a/examples/tests/valid/inflight_class_structural_interace_handler.main.w b/examples/tests/valid/inflight_class_structural_interace_handler.main.w index 6d62e2adf12..e32ea32e6d6 100644 --- a/examples/tests/valid/inflight_class_structural_interace_handler.main.w +++ b/examples/tests/valid/inflight_class_structural_interace_handler.main.w @@ -12,7 +12,7 @@ inflight class NotGoo { test "structure interface types for 'handle'" { inflight class YesGoo impl IGoo { - handle(): num { + pub handle(): num { return 456; } diff --git a/examples/tests/valid/inflights_calling_inflights.main.w b/examples/tests/valid/inflights_calling_inflights.main.w index 5f1d64e27b7..d94f331c780 100644 --- a/examples/tests/valid/inflights_calling_inflights.main.w +++ b/examples/tests/valid/inflights_calling_inflights.main.w @@ -29,7 +29,7 @@ class MyResource { }; } - inflight foo(): str { + pub inflight foo(): str { return this.closure("anything"); } } diff --git a/examples/tests/valid/json.main.w b/examples/tests/valid/json.main.w index 9cd8d710eec..54b09ee3d51 100644 --- a/examples/tests/valid/json.main.w +++ b/examples/tests/valid/json.main.w @@ -41,7 +41,7 @@ let jj3 = Json getStr(); assert(jj3 == Json "hello"); class Foo { - SumStr: str; + pub SumStr: str; init() { this.SumStr = "wow!"; } diff --git a/examples/tests/valid/lift_expr_with_this.main.w b/examples/tests/valid/lift_expr_with_this.main.w index f660bbe8e77..4751d2b7564 100644 --- a/examples/tests/valid/lift_expr_with_this.main.w +++ b/examples/tests/valid/lift_expr_with_this.main.w @@ -1,5 +1,5 @@ class Foo { - value: str; + pub value: str; init() { this.value = "hello"; } } diff --git a/examples/tests/valid/lift_this.main.w b/examples/tests/valid/lift_this.main.w index bb521ca58b0..e7de134e292 100644 --- a/examples/tests/valid/lift_this.main.w +++ b/examples/tests/valid/lift_this.main.w @@ -6,7 +6,7 @@ class Foo { inflight bar(): num { return this.x; } - inflight foo(): num { + pub inflight foo(): num { return this.bar() / 2; } } diff --git a/examples/tests/valid/lift_via_closure.main.w b/examples/tests/valid/lift_via_closure.main.w index bcac2a57d52..42233fe7de4 100644 --- a/examples/tests/valid/lift_via_closure.main.w +++ b/examples/tests/valid/lift_via_closure.main.w @@ -7,7 +7,7 @@ let fn = inflight () => { }; class MyClosure { - bucket: cloud.Bucket; + pub bucket: cloud.Bucket; init() { this.bucket = new cloud.Bucket(); @@ -23,7 +23,7 @@ class MyClosure { this.bucket.put("hello", "world"); } - inflight listFiles(): Array { + pub inflight listFiles(): Array { bucket2.put("b2", "world"); return this.bucket.list(); } diff --git a/examples/tests/valid/nil.main.w b/examples/tests/valid/nil.main.w index 8490ab94382..f7d91d800e0 100644 --- a/examples/tests/valid/nil.main.w +++ b/examples/tests/valid/nil.main.w @@ -7,18 +7,18 @@ class Foo { this.optionalVar = nil; } - inflight returnNil(t: bool): str? { + pub inflight returnNil(t: bool): str? { if t { return "hello"; } return nil; } - inflight setOptionalValue(msg: str?) { + pub inflight setOptionalValue(msg: str?) { this.optionalVar = msg; } - inflight getOptionalValue(): str? { + pub inflight getOptionalValue(): str? { return this.optionalVar; } } diff --git a/examples/tests/valid/optionals.main.w b/examples/tests/valid/optionals.main.w index 4833e18a16a..e4ef16812a7 100644 --- a/examples/tests/valid/optionals.main.w +++ b/examples/tests/valid/optionals.main.w @@ -10,7 +10,7 @@ let y: num = x ?? 5; assert(y == 4); class Super { - name: str; + pub name: str; init() { this.name = "Super"; } } class Sub extends Super { @@ -136,9 +136,9 @@ assert(fun("hello") == "hello"); assert(fun(nil) == "default"); class Node { - value: num; - left: Node?; - right: Node?; + pub value: num; + pub left: Node?; + pub right: Node?; init(value: num, left: Node?, right: Node?) { this.value = value; diff --git a/examples/tests/valid/reassignment.main.w b/examples/tests/valid/reassignment.main.w index 3fdf48a3ff4..646723c9c45 100644 --- a/examples/tests/valid/reassignment.main.w +++ b/examples/tests/valid/reassignment.main.w @@ -12,7 +12,7 @@ z -= 1; assert(z == 2); class R { - var f: num; + pub var f: num; f1: num; init() { // Initialize fields in `init` but in an inner scope to make sure @@ -23,7 +23,7 @@ class R { } } - inc() { + pub inc() { this.f = this.f + 1; } } diff --git a/examples/tests/valid/resource.main.w b/examples/tests/valid/resource.main.w index 811c3ead4a8..21394369c63 100644 --- a/examples/tests/valid/resource.main.w +++ b/examples/tests/valid/resource.main.w @@ -3,7 +3,7 @@ bring cloud; // User defined resource class Foo { c: cloud.Counter; // Use SDK built in resource in the user defined resource - inflight inflightField: num; + pub inflight inflightField: num; init() { this.c = new cloud.Counter(); @@ -19,16 +19,16 @@ class Foo { } // Inflight method - inflight fooInc() { + pub inflight fooInc() { // Call the SDK built in resource's inflight method from our inflight code this.c.inc(); } - inflight fooGet(): num { + pub inflight fooGet(): num { return this.c.peek(); } - static inflight fooStatic(): str { + pub static inflight fooStatic(): str { return "foo static"; } } @@ -43,7 +43,7 @@ class Bar { name: str; b: cloud.Bucket; // Use a user defined resource inside another user defined resource - foo: Foo; + pub foo: Foo; // Use an enum inside a user defined resource to verify enum capturing works e: MyEnum; @@ -58,7 +58,7 @@ class Bar { return "bar static"; } - inflight myMethod(): str { + pub inflight myMethod(): str { // Call user defined inflight code from another user defined resource this.foo.fooInc(); @@ -69,7 +69,7 @@ class Bar { return this.b.get("foo"); } - inflight testTypeAccess() { + pub inflight testTypeAccess() { // We purposefully run these test in an inner scope to validate the compiler analyzes the code // correctly and finds type access in inner scopes. if true { @@ -124,13 +124,13 @@ class BigPublisher { }); } - inflight publish(s: str) { + pub inflight publish(s: str) { this.t.publish(s); this.q.push(s); this.b2.put("foo", s); } - inflight getObjectCount(): num { + pub inflight getObjectCount(): num { return this.b.list().length; } } diff --git a/examples/tests/valid/resource_as_inflight_literal.main.w b/examples/tests/valid/resource_as_inflight_literal.main.w index 148dd23caf5..6c422e17b2e 100644 --- a/examples/tests/valid/resource_as_inflight_literal.main.w +++ b/examples/tests/valid/resource_as_inflight_literal.main.w @@ -1,7 +1,7 @@ bring cloud; class Foo impl cloud.IFunctionHandler { - inflight handle(message: str): str { + pub inflight handle(message: str): str { return "hello world!"; } } diff --git a/examples/tests/valid/resource_call_static.main.w b/examples/tests/valid/resource_call_static.main.w index f9427b190a7..23d2adb9bb2 100644 --- a/examples/tests/valid/resource_call_static.main.w +++ b/examples/tests/valid/resource_call_static.main.w @@ -3,7 +3,7 @@ bring cloud; let globalCounter = new cloud.Counter(); class Another { - static inflight myStaticMethod(): num { + pub static inflight myStaticMethod(): num { return globalCounter.peek(); } } diff --git a/examples/tests/valid/resource_captures.main.w b/examples/tests/valid/resource_captures.main.w index 3db9d76aa3d..98cd744a29e 100644 --- a/examples/tests/valid/resource_captures.main.w +++ b/examples/tests/valid/resource_captures.main.w @@ -1,7 +1,7 @@ bring cloud; class First { - myResource: cloud.Bucket; + pub myResource: cloud.Bucket; init() { this.myResource = new cloud.Bucket(); @@ -9,19 +9,19 @@ class First { } class Another { - myField: str; - first: First; + pub myField: str; + pub first: First; init () { this.myField = "hello!"; this.first = new First(); } - inflight meaningOfLife(): num { + pub inflight meaningOfLife(): num { return 42; } - inflight anotherFunc(): str { + pub inflight anotherFunc(): str { return "42"; } } @@ -67,13 +67,13 @@ class MyResource { this.unusedResource = new cloud.Counter(); } - inflight testNoCapture() { + pub inflight testNoCapture() { let arr = [1,2,3]; assert(arr.length == 3); log("array.len=${arr.length}"); } - inflight testCaptureCollectionsOfData() { + pub inflight testCaptureCollectionsOfData() { assert(this.arrayOfStr.length == 2); assert(this.arrayOfStr.at(0) == "s1"); assert(this.arrayOfStr.at(1) == "s2"); @@ -84,13 +84,13 @@ class MyResource { assert(!this.setOfStr.has("s3")); } - inflight testCapturePrimitives() { + pub inflight testCapturePrimitives() { assert(this.myStr == "myString"); assert(this.myNum == 42); assert(this.myBool == true); } - inflight testCaptureOptional() { + pub inflight testCaptureOptional() { assert(this.myOptStr ?? "" == "myOptString"); } @@ -98,39 +98,39 @@ class MyResource { return this.another; } - inflight testCaptureResource() { + pub inflight testCaptureResource() { this.myResource.put("f1.txt", "f1"); assert(this.myResource.get("f1.txt") == "f1"); assert(this.myResource.list().length == 1); } - inflight testNestedInflightField() { + pub inflight testNestedInflightField() { assert(this.another.myField == "hello!"); log("field=${this.another.myField}"); } - inflight testNestedResource() { + pub inflight testNestedResource() { assert(this.another.first.myResource.list().length == 0); this.another.first.myResource.put("hello", this.myStr); log("this.another.first.myResource:${this.another.first.myResource.get("hello")}"); } // expression within an expression - inflight testExpressionRecursive() { + pub inflight testExpressionRecursive() { this.myQueue.push(this.myStr); } - inflight testExternal() { + pub inflight testExternal() { assert(this.extBucket.list().length == 0); assert(this.extNum == 12); } - inflight testUserDefinedResource() { + pub inflight testUserDefinedResource() { assert(this.another.meaningOfLife() == 42); assert(this.another.anotherFunc() == "42"); } - inflight testInflightField() { + pub inflight testInflightField() { assert(this.inflightField == 123); } } diff --git a/examples/tests/valid/resource_captures_globals.main.w b/examples/tests/valid/resource_captures_globals.main.w index 67ad768d502..9a9ea99e3ba 100644 --- a/examples/tests/valid/resource_captures_globals.main.w +++ b/examples/tests/valid/resource_captures_globals.main.w @@ -10,7 +10,7 @@ let globalMapOfNum = Map{ "a" => -5, "b" => 2 }; let globalSetOfStr = Set{ "a", "b" }; class First { - myResource: cloud.Bucket; + pub myResource: cloud.Bucket; init() { this.myResource = new cloud.Bucket(); @@ -18,8 +18,8 @@ class First { } class Another { - myField: str; - first: First; + pub myField: str; + pub first: First; init () { this.myField = "hello!"; @@ -30,12 +30,12 @@ class Another { assert(globalCounter.peek() == 0); } - inflight myMethod(): num { + pub inflight myMethod(): num { globalCounter.inc(); return globalCounter.peek(); } - static inflight myStaticMethod(): num { + pub static inflight myStaticMethod(): num { return globalCounter.peek(); } } @@ -51,7 +51,7 @@ class MyResource { let $parentThis = this; class R impl cloud.ITopicOnMessageHandler { - inflight handle() { + pub inflight handle() { globalCounter.inc(); $parentThis.localCounter.inc(); } @@ -59,7 +59,7 @@ class MyResource { this.localTopic.onMessage(new R()); } - inflight myPut() { + pub inflight myPut() { this.localTopic.publish("hello"); globalBucket.put("key", "value"); assert(globalStr == "hello"); diff --git a/examples/tests/valid/static_members.main.w b/examples/tests/valid/static_members.main.w index fae8e3e1691..f0143b37691 100644 --- a/examples/tests/valid/static_members.main.w +++ b/examples/tests/valid/static_members.main.w @@ -1,13 +1,13 @@ bring cloud; class Foo { - instanceField: num; + pub instanceField: num; // TODO: Static field initialization not supported yet (https://github.com/winglang/wing/issues/1668) // static staticField: str = "Static resource value"; // static inflight inflightStaticField: str = "Inflight static resource value"; - static m(): num { return 99; } + pub static m(): num { return 99; } init() { this.instanceField = 100; @@ -27,10 +27,10 @@ assert(Foo.m() == 99); test "test" { inflight class InflightClass { init() {} - inflight inflightMethod(): str { + pub inflight inflightMethod(): str { return "Inflight method"; } - static inflight staticInflightMethod(): str { + pub static inflight staticInflightMethod(): str { return "Static inflight method"; } diff --git a/examples/tests/valid/store.w b/examples/tests/valid/store.w index 4f80fb24e01..78343eefb71 100644 --- a/examples/tests/valid/store.w +++ b/examples/tests/valid/store.w @@ -14,7 +14,7 @@ class Store { this.b.put("data.txt", ""); }); } - inflight store(data: str) { + pub inflight store(data: str) { this.b.put("data.txt", data); } } diff --git a/examples/tests/valid/subdir/bar.w b/examples/tests/valid/subdir/bar.w index 12c36fd5f7f..cb9820be165 100644 --- a/examples/tests/valid/subdir/bar.w +++ b/examples/tests/valid/subdir/bar.w @@ -1,7 +1,7 @@ // used by bring_local_normalization.w class Bar { - static bar(): str { + pub static bar(): str { return "bar"; } } diff --git a/examples/tests/valid/subdir/foo.w b/examples/tests/valid/subdir/foo.w index b52d46a3895..3b9f630b1ce 100644 --- a/examples/tests/valid/subdir/foo.w +++ b/examples/tests/valid/subdir/foo.w @@ -4,13 +4,13 @@ bring "./bar.w" as bar; bring "../baz.w" as baz; class Foo { - static foo(): str { + pub static foo(): str { return "foo"; } - static bar(): str { + pub static bar(): str { return bar.Bar.bar(); } - static baz(): str { + pub static baz(): str { return baz.Baz.baz(); } } diff --git a/examples/tests/valid/super_call.main.w b/examples/tests/valid/super_call.main.w index 046aa48130f..bcda2042b81 100644 --- a/examples/tests/valid/super_call.main.w +++ b/examples/tests/valid/super_call.main.w @@ -7,13 +7,13 @@ class A { } class B extends A { - description(): str { + pub description(): str { return "B"; } } class C extends B { - description(): str { + pub description(): str { return "C extends ${super.description()}"; } } @@ -23,7 +23,7 @@ class D extends C { } class E extends D { - description(): str { + pub description(): str { return "E extends ${super.description()}"; } } @@ -33,14 +33,14 @@ let e = new E(); assert(e.description() == "E extends C extends B"); inflight class InflightA { - description(): str { + pub description(): str { return "InflightA"; } } // Test super calls on inflight classes inflight class InflightB extends InflightA { - description(): str { + pub description(): str { return "InflightB extends ${super.description()}"; } } @@ -54,13 +54,13 @@ test "super call inflight" { bring cloud; let b = new cloud.Bucket(); class BaseClass { - inflight do(): str { + pub inflight do(): str { return b.get("k"); // BaseClass class required read acceess to b } } class ExtendedClass extends BaseClass { - inflight do(): str { + pub inflight do(): str { b.put("k", "value"); // This should require write access to b return super.do(); // We expect to add binding permissions based on what `super.do()` requires (read) } diff --git a/libs/tree-sitter-wing/grammar.js b/libs/tree-sitter-wing/grammar.js index 7ba31d51c26..04f65e7eeb8 100644 --- a/libs/tree-sitter-wing/grammar.js +++ b/libs/tree-sitter-wing/grammar.js @@ -532,7 +532,7 @@ module.exports = grammar({ async_modifier: ($) => "async", - access_modifier: ($) => choice("public", "private", "protected"), + access_modifier: ($) => choice("pub", "protected", "internal"), variadic: ($) => "...", 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 d150b03d7c0..e131747a2df 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 @@ -74,12 +74,15 @@ class A { inflight init() {} preflight_func() {} async preflight_func() {} - public preflight_func2() {} + pub preflight_func2() {} inflight inflight_func() {} - public inflight inflight_func2(): num {} + pub inflight inflight_func2(): num {} + protected inflight inflight_func3(): num {} pf_member: str; + internal internal_member: str; inflight if_member: str; - public inflight var if_member2: str; + pub inflight var if_member2: str; + protected inflight var if_member3: str; static inflight inflight_field: num; static inflight inflight_method() {} } @@ -123,11 +126,28 @@ class A { parameter_list: (parameter_list) type: (builtin_type) block: (block)) + (inflight_method_definition + access_modifier: (access_modifier) + phase_modifier: (inflight_specifier) + name: (identifier) + parameter_list: (parameter_list) + type: (builtin_type) + block: (block)) (class_field name: (identifier) type: (builtin_type)) (class_field + access_modifier: (access_modifier) + name: (identifier) + type: (builtin_type)) + (class_field + phase_modifier: (inflight_specifier) + name: (identifier) + type: (builtin_type)) + (class_field + access_modifier: (access_modifier) phase_modifier: (inflight_specifier) + reassignable: (reassignable) name: (identifier) type: (builtin_type)) (class_field diff --git a/libs/wingc/src/ast.rs b/libs/wingc/src/ast.rs index 2242310087d..9e661dc37f4 100644 --- a/libs/wingc/src/ast.rs +++ b/libs/wingc/src/ast.rs @@ -297,6 +297,8 @@ pub struct FunctionDefinition { pub signature: FunctionSignature, /// Whether this function is static or not. In case of a closure, this is always true. pub is_static: bool, + /// Function's access modifier. In case of a closure, this is always public. + pub access_modifier: AccessModifier, pub span: WingSpan, } @@ -432,6 +434,16 @@ pub enum AssignmentKind { AssignDecr, } +#[derive(Debug)] +pub struct IfLet { + pub reassignable: bool, + pub var_name: Symbol, + pub value: Expr, + pub statements: Scope, + pub elif_statements: Vec, + pub else_statements: Option, +} + #[derive(Debug)] pub enum StmtKind { Bring { @@ -456,14 +468,7 @@ pub enum StmtKind { condition: Expr, statements: Scope, }, - IfLet { - reassignable: bool, - var_name: Symbol, - value: Expr, - statements: Scope, - elif_statements: Vec, - else_statements: Option, - }, + IfLet(IfLet), If { condition: Expr, statements: Scope, @@ -513,6 +518,24 @@ pub struct ClassField { pub reassignable: bool, pub phase: Phase, pub is_static: bool, + pub access_modifier: AccessModifier, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum AccessModifier { + Private, + Public, + Protected, +} + +impl Display for AccessModifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AccessModifier::Private => write!(f, "private"), + AccessModifier::Public => write!(f, "public"), + AccessModifier::Protected => write!(f, "protected"), + } + } } #[derive(Debug)] diff --git a/libs/wingc/src/closure_transform.rs b/libs/wingc/src/closure_transform.rs index 90d5290c10b..1e5c9abadde 100644 --- a/libs/wingc/src/closure_transform.rs +++ b/libs/wingc/src/closure_transform.rs @@ -2,9 +2,9 @@ use indexmap::IndexMap; use crate::{ ast::{ - ArgList, AssignmentKind, CalleeKind, Class, ClassField, Expr, ExprKind, FunctionBody, FunctionDefinition, - FunctionParameter, FunctionSignature, Literal, NewExpr, Phase, Reference, Scope, Stmt, StmtKind, Symbol, - TypeAnnotation, TypeAnnotationKind, UserDefinedType, + AccessModifier, ArgList, AssignmentKind, CalleeKind, Class, ClassField, Expr, ExprKind, FunctionBody, + FunctionDefinition, FunctionParameter, FunctionSignature, Literal, NewExpr, Phase, Reference, Scope, Stmt, + StmtKind, Symbol, TypeAnnotation, TypeAnnotationKind, UserDefinedType, }, diagnostic::WingSpan, fold::{self, Fold}, @@ -180,6 +180,7 @@ impl Fold for ClosureTransformer { // Anonymous functions are always static -- since the function code is now an instance method on a class, // we need to set this to false. is_static: false, + access_modifier: AccessModifier::Public, }; // class_init_body := @@ -272,6 +273,7 @@ impl Fold for ClosureTransformer { is_static: true, body: FunctionBody::Statements(Scope::new(class_init_body, WingSpan::for_file(file_id))), span: WingSpan::for_file(file_id), + access_modifier: AccessModifier::Public, }, fields: class_fields, implements: vec![], @@ -290,6 +292,7 @@ impl Fold for ClosureTransformer { is_static: false, body: FunctionBody::Statements(Scope::new(vec![], WingSpan::for_file(file_id))), span: WingSpan::for_file(file_id), + access_modifier: AccessModifier::Public, }, }), idx: self.nearest_stmt_idx, diff --git a/libs/wingc/src/fold.rs b/libs/wingc/src/fold.rs index 456bdc7b24c..140fd8768a4 100644 --- a/libs/wingc/src/fold.rs +++ b/libs/wingc/src/fold.rs @@ -1,7 +1,7 @@ use crate::{ ast::{ ArgList, BringSource, CalleeKind, CatchBlock, Class, ClassField, ElifBlock, ElifLetBlock, Expr, ExprKind, - FunctionBody, FunctionDefinition, FunctionParameter, FunctionSignature, Interface, InterpolatedString, + FunctionBody, FunctionDefinition, FunctionParameter, FunctionSignature, IfLet, Interface, InterpolatedString, InterpolatedStringPart, Literal, NewExpr, Reference, Scope, Stmt, StmtKind, StructField, Symbol, TypeAnnotation, TypeAnnotationKind, UserDefinedType, }, @@ -113,14 +113,14 @@ where condition: f.fold_expr(condition), statements: f.fold_scope(statements), }, - StmtKind::IfLet { + StmtKind::IfLet(IfLet { value, statements, reassignable, var_name, elif_statements, else_statements, - } => StmtKind::IfLet { + }) => StmtKind::IfLet(IfLet { value: f.fold_expr(value), statements: f.fold_scope(statements), reassignable, @@ -135,7 +135,7 @@ where }) .collect(), else_statements: else_statements.map(|statements| f.fold_scope(statements)), - }, + }), StmtKind::If { condition, statements, @@ -233,6 +233,7 @@ where reassignable: node.reassignable, phase: node.phase, is_static: node.is_static, + access_modifier: node.access_modifier, } } @@ -408,6 +409,7 @@ where signature: f.fold_function_signature(node.signature), is_static: node.is_static, span: node.span, + access_modifier: node.access_modifier, } } diff --git a/libs/wingc/src/jsify.rs b/libs/wingc/src/jsify.rs index a757e6b29ef..22d68f3d61d 100644 --- a/libs/wingc/src/jsify.rs +++ b/libs/wingc/src/jsify.rs @@ -11,7 +11,7 @@ use std::{borrow::Borrow, cell::RefCell, cmp::Ordering, collections::BTreeMap, v use crate::{ ast::{ ArgList, AssignmentKind, BinaryOperator, BringSource, CalleeKind, Class as AstClass, ElifLetBlock, Expr, ExprKind, - FunctionBody, FunctionDefinition, InterpolatedStringPart, Literal, NewExpr, Phase, Reference, Scope, Stmt, + FunctionBody, FunctionDefinition, IfLet, InterpolatedStringPart, Literal, NewExpr, Phase, Reference, Scope, Stmt, StmtKind, Symbol, UnaryOperator, UserDefinedType, }, comp_ctx::{CompilationContext, CompilationPhase}, @@ -789,14 +789,14 @@ impl<'a> JSifier<'a> { } StmtKind::Break => CodeMaker::one_line("break;"), StmtKind::Continue => CodeMaker::one_line("continue;"), - StmtKind::IfLet { + StmtKind::IfLet(IfLet { reassignable, value, statements, var_name, elif_statements, else_statements, - } => { + }) => { let mut code = CodeMaker::default(); // To enable shadowing variables in if let statements, the following does some scope trickery // take for example the following wing code: @@ -1476,7 +1476,7 @@ fn get_public_symbols(scope: &Scope) -> Vec { StmtKind::Let { .. } => {} StmtKind::ForLoop { .. } => {} StmtKind::While { .. } => {} - StmtKind::IfLet { .. } => {} + StmtKind::IfLet(IfLet { .. }) => {} StmtKind::If { .. } => {} StmtKind::Break => {} StmtKind::Continue => {} diff --git a/libs/wingc/src/jsify/snapshots/base_class_captures_inflight.snap b/libs/wingc/src/jsify/snapshots/base_class_captures_inflight.snap index e4cb52a7035..f1e34035fbf 100644 --- a/libs/wingc/src/jsify/snapshots/base_class_captures_inflight.snap +++ b/libs/wingc/src/jsify/snapshots/base_class_captures_inflight.snap @@ -8,7 +8,7 @@ source: libs/wingc/src/jsify/tests.rs let x = "hello"; class Base { - inflight bar() { + protected inflight bar() { log(x); } } diff --git a/libs/wingc/src/jsify/snapshots/base_class_captures_preflight.snap b/libs/wingc/src/jsify/snapshots/base_class_captures_preflight.snap index b10451c2573..5fa25f2f65c 100644 --- a/libs/wingc/src/jsify/snapshots/base_class_captures_preflight.snap +++ b/libs/wingc/src/jsify/snapshots/base_class_captures_preflight.snap @@ -8,7 +8,7 @@ source: libs/wingc/src/jsify/tests.rs let x = "hello"; class Base { - bar() { + protected bar() { log(x); } } diff --git a/libs/wingc/src/jsify/snapshots/base_class_lift_indirect.snap b/libs/wingc/src/jsify/snapshots/base_class_lift_indirect.snap index 1b8f5ea0d87..bb69f7560dd 100644 --- a/libs/wingc/src/jsify/snapshots/base_class_lift_indirect.snap +++ b/libs/wingc/src/jsify/snapshots/base_class_lift_indirect.snap @@ -13,7 +13,7 @@ source: libs/wingc/src/jsify/tests.rs this.b = new cloud.Bucket(); } - inflight put() { + protected inflight put() { this.b.put("hello", "world"); this.b.list(); } diff --git a/libs/wingc/src/jsify/snapshots/base_class_with_fields_inflight.snap b/libs/wingc/src/jsify/snapshots/base_class_with_fields_inflight.snap index ff1a9d66a57..934e3c797ad 100644 --- a/libs/wingc/src/jsify/snapshots/base_class_with_fields_inflight.snap +++ b/libs/wingc/src/jsify/snapshots/base_class_with_fields_inflight.snap @@ -6,7 +6,7 @@ source: libs/wingc/src/jsify/tests.rs ```w class Base { - inflight f: str; + protected inflight f: str; inflight init() { this.f = "hello"; } diff --git a/libs/wingc/src/jsify/snapshots/base_class_with_fields_preflight.snap b/libs/wingc/src/jsify/snapshots/base_class_with_fields_preflight.snap index 97329763e41..8cdbce1a9bb 100644 --- a/libs/wingc/src/jsify/snapshots/base_class_with_fields_preflight.snap +++ b/libs/wingc/src/jsify/snapshots/base_class_with_fields_preflight.snap @@ -6,7 +6,7 @@ source: libs/wingc/src/jsify/tests.rs ```w class Base { - f: str; + protected f: str; init() { this.f = "hello"; } diff --git a/libs/wingc/src/jsify/snapshots/base_class_with_lifted_field_object.snap b/libs/wingc/src/jsify/snapshots/base_class_with_lifted_field_object.snap index fd2757e8668..c0e85f849fd 100644 --- a/libs/wingc/src/jsify/snapshots/base_class_with_lifted_field_object.snap +++ b/libs/wingc/src/jsify/snapshots/base_class_with_lifted_field_object.snap @@ -8,7 +8,7 @@ source: libs/wingc/src/jsify/tests.rs bring cloud; class Base { - b: cloud.Bucket; + protected b: cloud.Bucket; init() { this.b = new cloud.Bucket(); } diff --git a/libs/wingc/src/jsify/snapshots/base_class_with_lifted_fields.snap b/libs/wingc/src/jsify/snapshots/base_class_with_lifted_fields.snap index 4336a445f65..99a0935e579 100644 --- a/libs/wingc/src/jsify/snapshots/base_class_with_lifted_fields.snap +++ b/libs/wingc/src/jsify/snapshots/base_class_with_lifted_fields.snap @@ -8,7 +8,7 @@ source: libs/wingc/src/jsify/tests.rs let x = "hello"; class Base { - f: str; + protected f: str; init() { this.f = x; } diff --git a/libs/wingc/src/jsify/snapshots/call_static_inflight_from_static_inflight.snap b/libs/wingc/src/jsify/snapshots/call_static_inflight_from_static_inflight.snap index c09e128ac8a..fed5fb60204 100644 --- a/libs/wingc/src/jsify/snapshots/call_static_inflight_from_static_inflight.snap +++ b/libs/wingc/src/jsify/snapshots/call_static_inflight_from_static_inflight.snap @@ -6,7 +6,7 @@ source: libs/wingc/src/jsify/tests.rs ```w class A { - static inflight foo() { log("hello"); } + pub static inflight foo() { log("hello"); } } inflight class B { diff --git a/libs/wingc/src/jsify/snapshots/capture_identifier_from_preflight_scope_with_method_call.snap b/libs/wingc/src/jsify/snapshots/capture_identifier_from_preflight_scope_with_method_call.snap index b3c3590d331..712ea37c7bb 100644 --- a/libs/wingc/src/jsify/snapshots/capture_identifier_from_preflight_scope_with_method_call.snap +++ b/libs/wingc/src/jsify/snapshots/capture_identifier_from_preflight_scope_with_method_call.snap @@ -6,7 +6,7 @@ source: libs/wingc/src/jsify/tests.rs ```w class Foo { - inflight bar() {} + pub inflight bar() {} } let f = new Foo(); diff --git a/libs/wingc/src/jsify/snapshots/capture_identifier_from_preflight_scope_with_nested_object.snap b/libs/wingc/src/jsify/snapshots/capture_identifier_from_preflight_scope_with_nested_object.snap index 5b6c73b029c..e3231f34797 100644 --- a/libs/wingc/src/jsify/snapshots/capture_identifier_from_preflight_scope_with_nested_object.snap +++ b/libs/wingc/src/jsify/snapshots/capture_identifier_from_preflight_scope_with_nested_object.snap @@ -8,7 +8,7 @@ source: libs/wingc/src/jsify/tests.rs bring cloud; class Foo { - b: cloud.Bucket; + pub b: cloud.Bucket; init() { this.b = new cloud.Bucket(); } diff --git a/libs/wingc/src/jsify/snapshots/capture_type_static_method.snap b/libs/wingc/src/jsify/snapshots/capture_type_static_method.snap index 5b8ba4ee037..5380faba38c 100644 --- a/libs/wingc/src/jsify/snapshots/capture_type_static_method.snap +++ b/libs/wingc/src/jsify/snapshots/capture_type_static_method.snap @@ -6,7 +6,7 @@ source: libs/wingc/src/jsify/tests.rs ```w class Foo { - static inflight bars(): str { return "bar"; } + pub static inflight bars(): str { return "bar"; } } test "test" { diff --git a/libs/wingc/src/jsify/snapshots/capture_type_static_method_inflight_class.snap b/libs/wingc/src/jsify/snapshots/capture_type_static_method_inflight_class.snap index 8775dcd5873..cb71bb818a3 100644 --- a/libs/wingc/src/jsify/snapshots/capture_type_static_method_inflight_class.snap +++ b/libs/wingc/src/jsify/snapshots/capture_type_static_method_inflight_class.snap @@ -6,7 +6,7 @@ source: libs/wingc/src/jsify/tests.rs ```w inflight class Foo { - static bar(): str { return "bar"; } + pub static bar(): str { return "bar"; } } test "test" { diff --git a/libs/wingc/src/jsify/snapshots/closure_field.snap b/libs/wingc/src/jsify/snapshots/closure_field.snap index f8947d99ed9..cac7ba51314 100644 --- a/libs/wingc/src/jsify/snapshots/closure_field.snap +++ b/libs/wingc/src/jsify/snapshots/closure_field.snap @@ -18,7 +18,7 @@ source: libs/wingc/src/jsify/tests.rs }; } - inflight foo(): str { + pub inflight foo(): str { return this.closure("anything"); } } diff --git a/libs/wingc/src/jsify/snapshots/fails_base_class_with_lifted_field_object_unqualified.snap b/libs/wingc/src/jsify/snapshots/fails_base_class_with_lifted_field_object_unqualified.snap index 0a014c0fb8e..39c7cef06a2 100644 --- a/libs/wingc/src/jsify/snapshots/fails_base_class_with_lifted_field_object_unqualified.snap +++ b/libs/wingc/src/jsify/snapshots/fails_base_class_with_lifted_field_object_unqualified.snap @@ -2,4 +2,5 @@ source: libs/wingc/src/jsify/tests.rs --- ## Errors +Cannot access private member "b" of "Derived" 12:13 Cannot qualify access to a lifted object of type "Bucket" (see https://github.com/winglang/wing/issues/76 for more details) 12:8 diff --git a/libs/wingc/src/jsify/snapshots/fails_on_preflight_static.snap b/libs/wingc/src/jsify/snapshots/fails_on_preflight_static.snap index d0946e458d3..448f6bbbfbc 100644 --- a/libs/wingc/src/jsify/snapshots/fails_on_preflight_static.snap +++ b/libs/wingc/src/jsify/snapshots/fails_on_preflight_static.snap @@ -2,4 +2,5 @@ source: libs/wingc/src/jsify/tests.rs --- ## Errors +Cannot access private member "staticMethod" of "MyType" 6:17 Cannot call into preflight phase while inflight 6:10 diff --git a/libs/wingc/src/jsify/snapshots/indirect_capture.snap b/libs/wingc/src/jsify/snapshots/indirect_capture.snap index 31ddd9efbee..1b5991e2b14 100644 --- a/libs/wingc/src/jsify/snapshots/indirect_capture.snap +++ b/libs/wingc/src/jsify/snapshots/indirect_capture.snap @@ -16,7 +16,7 @@ source: libs/wingc/src/jsify/tests.rs b.list(); } - inflight goo() { + pub inflight goo() { this.foo(); } } diff --git a/libs/wingc/src/jsify/snapshots/lift_this.snap b/libs/wingc/src/jsify/snapshots/lift_this.snap index 5b4aa12c190..7cb3247c887 100644 --- a/libs/wingc/src/jsify/snapshots/lift_this.snap +++ b/libs/wingc/src/jsify/snapshots/lift_this.snap @@ -13,7 +13,7 @@ source: libs/wingc/src/jsify/tests.rs inflight bar(): num { return this.x; } - inflight foo(): num { + pub inflight foo(): num { return this.bar() / 2; } } diff --git a/libs/wingc/src/jsify/snapshots/lift_var_with_this.snap b/libs/wingc/src/jsify/snapshots/lift_var_with_this.snap index 12743983963..f5ce5b9cb08 100644 --- a/libs/wingc/src/jsify/snapshots/lift_var_with_this.snap +++ b/libs/wingc/src/jsify/snapshots/lift_var_with_this.snap @@ -6,7 +6,7 @@ source: libs/wingc/src/jsify/tests.rs ```w class Foo { - value: str; + pub value: str; init() { this.value = "hello"; } } diff --git a/libs/wingc/src/jsify/snapshots/nested_inflight_after_preflight_operation.snap b/libs/wingc/src/jsify/snapshots/nested_inflight_after_preflight_operation.snap index 904051771c1..cff99aab1a7 100644 --- a/libs/wingc/src/jsify/snapshots/nested_inflight_after_preflight_operation.snap +++ b/libs/wingc/src/jsify/snapshots/nested_inflight_after_preflight_operation.snap @@ -7,7 +7,7 @@ source: libs/wingc/src/jsify/tests.rs bring cloud; class YourType { - inflight b: str; + pub inflight b: str; inflight init() { this.b = "hello"; } diff --git a/libs/wingc/src/jsify/snapshots/nested_preflight_operation.snap b/libs/wingc/src/jsify/snapshots/nested_preflight_operation.snap index e0abb54aadb..246fa7742f5 100644 --- a/libs/wingc/src/jsify/snapshots/nested_preflight_operation.snap +++ b/libs/wingc/src/jsify/snapshots/nested_preflight_operation.snap @@ -7,13 +7,13 @@ source: libs/wingc/src/jsify/tests.rs bring cloud; class YourType { - b: cloud.Bucket; + pub b: cloud.Bucket; init() { this.b = new cloud.Bucket(); } } class MyType { - y: YourType; + pub y: YourType; init() { this.y = new YourType(); } diff --git a/libs/wingc/src/jsify/snapshots/preflight_nested_object_with_operations.snap b/libs/wingc/src/jsify/snapshots/preflight_nested_object_with_operations.snap index 0076165bd22..1223a3691cb 100644 --- a/libs/wingc/src/jsify/snapshots/preflight_nested_object_with_operations.snap +++ b/libs/wingc/src/jsify/snapshots/preflight_nested_object_with_operations.snap @@ -8,7 +8,7 @@ source: libs/wingc/src/jsify/tests.rs bring cloud; let b = new cloud.Bucket(); class A { - bucky: cloud.Bucket; + pub bucky: cloud.Bucket; init() { this.bucky = b; } diff --git a/libs/wingc/src/jsify/snapshots/preflight_object.snap b/libs/wingc/src/jsify/snapshots/preflight_object.snap index 61f69324a93..f77db85d6c7 100644 --- a/libs/wingc/src/jsify/snapshots/preflight_object.snap +++ b/libs/wingc/src/jsify/snapshots/preflight_object.snap @@ -6,8 +6,8 @@ source: libs/wingc/src/jsify/tests.rs ```w class A { - inflight hello() {} - inflight goodbye() {} + pub inflight hello() {} + pub inflight goodbye() {} } let pf_obj = new A(); test "test" { diff --git a/libs/wingc/src/jsify/snapshots/preflight_object_through_property.snap b/libs/wingc/src/jsify/snapshots/preflight_object_through_property.snap index 69d44c46470..a004d3a9079 100644 --- a/libs/wingc/src/jsify/snapshots/preflight_object_through_property.snap +++ b/libs/wingc/src/jsify/snapshots/preflight_object_through_property.snap @@ -8,7 +8,7 @@ source: libs/wingc/src/jsify/tests.rs bring cloud; class MyType { - b: cloud.Bucket; + pub b: cloud.Bucket; init() { this.b = new cloud.Bucket(); } diff --git a/libs/wingc/src/jsify/snapshots/preflight_value_field.snap b/libs/wingc/src/jsify/snapshots/preflight_value_field.snap index 024c05c5c9d..2ef2189fcc5 100644 --- a/libs/wingc/src/jsify/snapshots/preflight_value_field.snap +++ b/libs/wingc/src/jsify/snapshots/preflight_value_field.snap @@ -6,8 +6,8 @@ source: libs/wingc/src/jsify/tests.rs ```w class MyType { - name: str; - last: str; + pub name: str; + pub last: str; init() { this.name = "hello"; diff --git a/libs/wingc/src/jsify/snapshots/reference_inflight_class.snap b/libs/wingc/src/jsify/snapshots/reference_inflight_class.snap index ca2b24327fb..6c23f30c8e9 100644 --- a/libs/wingc/src/jsify/snapshots/reference_inflight_class.snap +++ b/libs/wingc/src/jsify/snapshots/reference_inflight_class.snap @@ -6,7 +6,7 @@ source: libs/wingc/src/jsify/tests.rs ```w inflight class Foo { - static a(): str { return "a"; } + pub static a(): str { return "a"; } } test "test" { diff --git a/libs/wingc/src/jsify/snapshots/reference_static_inflight.snap b/libs/wingc/src/jsify/snapshots/reference_static_inflight.snap index 7ca285246ec..d0fdf926c88 100644 --- a/libs/wingc/src/jsify/snapshots/reference_static_inflight.snap +++ b/libs/wingc/src/jsify/snapshots/reference_static_inflight.snap @@ -6,7 +6,7 @@ source: libs/wingc/src/jsify/tests.rs ```w class MyType { - static inflight myStaticMethod(): str {} + pub static inflight myStaticMethod(): str {} } test "test" { diff --git a/libs/wingc/src/jsify/snapshots/reference_static_inflight_which_references_preflight_object.snap b/libs/wingc/src/jsify/snapshots/reference_static_inflight_which_references_preflight_object.snap index ac4fd7c3bef..59da0f9a5a6 100644 --- a/libs/wingc/src/jsify/snapshots/reference_static_inflight_which_references_preflight_object.snap +++ b/libs/wingc/src/jsify/snapshots/reference_static_inflight_which_references_preflight_object.snap @@ -10,7 +10,7 @@ source: libs/wingc/src/jsify/tests.rs let b = new cloud.Bucket(); class MyType { - static inflight staticMethod(): str { + pub static inflight staticMethod(): str { b.list(); return "foo"; } diff --git a/libs/wingc/src/jsify/snapshots/static_external_inflight_class.snap b/libs/wingc/src/jsify/snapshots/static_external_inflight_class.snap index 7bbc0b52b86..ac7a0f7be2b 100644 --- a/libs/wingc/src/jsify/snapshots/static_external_inflight_class.snap +++ b/libs/wingc/src/jsify/snapshots/static_external_inflight_class.snap @@ -6,7 +6,7 @@ source: libs/wingc/src/jsify/tests.rs ```w inflight class A { - static foo() { log("hello"); } + pub static foo() { log("hello"); } } test "test" { diff --git a/libs/wingc/src/jsify/snapshots/static_external_preflight_class.snap b/libs/wingc/src/jsify/snapshots/static_external_preflight_class.snap index 6ead7cfac7a..951b2abea28 100644 --- a/libs/wingc/src/jsify/snapshots/static_external_preflight_class.snap +++ b/libs/wingc/src/jsify/snapshots/static_external_preflight_class.snap @@ -6,7 +6,7 @@ source: libs/wingc/src/jsify/tests.rs ```w class A { - static inflight foo() { log("hello"); } + pub static inflight foo() { log("hello"); } } test "test" { diff --git a/libs/wingc/src/jsify/snapshots/static_inflight_operation.snap b/libs/wingc/src/jsify/snapshots/static_inflight_operation.snap index a1f195aeac1..613f03e3b9a 100644 --- a/libs/wingc/src/jsify/snapshots/static_inflight_operation.snap +++ b/libs/wingc/src/jsify/snapshots/static_inflight_operation.snap @@ -9,7 +9,7 @@ source: libs/wingc/src/jsify/tests.rs let b = new cloud.Bucket(); class A { - static inflight myop() { + pub static inflight myop() { b.list(); } } diff --git a/libs/wingc/src/jsify/snapshots/static_local_inflight_class.snap b/libs/wingc/src/jsify/snapshots/static_local_inflight_class.snap index d2a2ff479a5..873a6455a5b 100644 --- a/libs/wingc/src/jsify/snapshots/static_local_inflight_class.snap +++ b/libs/wingc/src/jsify/snapshots/static_local_inflight_class.snap @@ -7,7 +7,7 @@ source: libs/wingc/src/jsify/tests.rs test "test" { class A { - static foo() { log("hello"); } + pub static foo() { log("hello"); } } class B { diff --git a/libs/wingc/src/jsify/snapshots/transitive_reference.snap b/libs/wingc/src/jsify/snapshots/transitive_reference.snap index bbd93d73e5b..27cb2f70fb7 100644 --- a/libs/wingc/src/jsify/snapshots/transitive_reference.snap +++ b/libs/wingc/src/jsify/snapshots/transitive_reference.snap @@ -18,7 +18,7 @@ source: libs/wingc/src/jsify/tests.rs return this.b.list().length == 0; } - inflight checkIfEmpty() { + pub inflight checkIfEmpty() { if this.isEmpty() { log("empty!"); } diff --git a/libs/wingc/src/jsify/snapshots/transitive_reference_via_inflight_class.snap b/libs/wingc/src/jsify/snapshots/transitive_reference_via_inflight_class.snap index e01e02afe4c..28689f1c406 100644 --- a/libs/wingc/src/jsify/snapshots/transitive_reference_via_inflight_class.snap +++ b/libs/wingc/src/jsify/snapshots/transitive_reference_via_inflight_class.snap @@ -10,7 +10,7 @@ source: libs/wingc/src/jsify/tests.rs let b = new cloud.Bucket(); inflight class MyInflightClass { - putInBucket() { + pub putInBucket() { b.put("in", "bucket"); } } diff --git a/libs/wingc/src/jsify/snapshots/transitive_reference_via_static.snap b/libs/wingc/src/jsify/snapshots/transitive_reference_via_static.snap index 50ac0947cc5..a9b24f2075e 100644 --- a/libs/wingc/src/jsify/snapshots/transitive_reference_via_static.snap +++ b/libs/wingc/src/jsify/snapshots/transitive_reference_via_static.snap @@ -10,13 +10,13 @@ source: libs/wingc/src/jsify/tests.rs let b = new cloud.Bucket(); class MyType { - static inflight putInBucket() { + pub static inflight putInBucket() { b.put("in", "bucket"); } } class YourType { - inflight putIndirect() { + pub inflight putIndirect() { MyType.putInBucket(); } } diff --git a/libs/wingc/src/jsify/tests.rs b/libs/wingc/src/jsify/tests.rs index bd496b91158..42426c70d21 100644 --- a/libs/wingc/src/jsify/tests.rs +++ b/libs/wingc/src/jsify/tests.rs @@ -48,7 +48,7 @@ fn call_static_inflight_from_static_inflight() { assert_compile_ok!( r#" class A { - static inflight foo() { log("hello"); } + pub static inflight foo() { log("hello"); } } inflight class B { @@ -66,7 +66,7 @@ fn static_local_inflight_class() { r#" test "test" { class A { - static foo() { log("hello"); } + pub static foo() { log("hello"); } } class B { @@ -86,7 +86,7 @@ fn static_external_inflight_class() { assert_compile_ok!( r#" inflight class A { - static foo() { log("hello"); } + pub static foo() { log("hello"); } } test "test" { @@ -106,7 +106,7 @@ fn static_external_preflight_class() { assert_compile_ok!( r#" class A { - static inflight foo() { log("hello"); } + pub static inflight foo() { log("hello"); } } test "test" { @@ -177,8 +177,8 @@ fn preflight_object() { assert_compile_ok!( r#" class A { - inflight hello() {} - inflight goodbye() {} + pub inflight hello() {} + pub inflight goodbye() {} } let pf_obj = new A(); test "test" { @@ -247,7 +247,7 @@ fn preflight_nested_object_with_operations() { bring cloud; let b = new cloud.Bucket(); class A { - bucky: cloud.Bucket; + pub bucky: cloud.Bucket; init() { this.bucky = b; } @@ -269,7 +269,7 @@ fn static_inflight_operation() { let b = new cloud.Bucket(); class A { - static inflight myop() { + pub static inflight myop() { b.list(); } } @@ -399,7 +399,7 @@ fn capture_type_static_method() { assert_compile_ok!( r#" class Foo { - static inflight bars(): str { return "bar"; } + pub static inflight bars(): str { return "bar"; } } test "test" { @@ -414,7 +414,7 @@ fn capture_type_static_method_inflight_class() { assert_compile_ok!( r#" inflight class Foo { - static bar(): str { return "bar"; } + pub static bar(): str { return "bar"; } } test "test" { @@ -545,7 +545,7 @@ fn capture_identifier_from_preflight_scope_with_method_call() { assert_compile_ok!( r#" class Foo { - inflight bar() {} + pub inflight bar() {} } let f = new Foo(); @@ -563,7 +563,7 @@ fn capture_identifier_from_preflight_scope_with_nested_object() { bring cloud; class Foo { - b: cloud.Bucket; + pub b: cloud.Bucket; init() { this.b = new cloud.Bucket(); } @@ -692,8 +692,8 @@ fn preflight_value_field() { assert_compile_ok!( r#" class MyType { - name: str; - last: str; + pub name: str; + pub last: str; init() { this.name = "hello"; @@ -735,13 +735,13 @@ fn nested_preflight_operation() { r#" bring cloud; class YourType { - b: cloud.Bucket; + pub b: cloud.Bucket; init() { this.b = new cloud.Bucket(); } } class MyType { - y: YourType; + pub y: YourType; init() { this.y = new YourType(); } @@ -762,7 +762,7 @@ fn nested_inflight_after_preflight_operation() { r#" bring cloud; class YourType { - inflight b: str; + pub inflight b: str; inflight init() { this.b = "hello"; } @@ -802,7 +802,7 @@ fn preflight_object_through_property() { bring cloud; class MyType { - b: cloud.Bucket; + pub b: cloud.Bucket; init() { this.b = new cloud.Bucket(); } @@ -837,7 +837,7 @@ fn reference_static_inflight() { assert_compile_ok!( r#" class MyType { - static inflight myStaticMethod(): str {} + pub static inflight myStaticMethod(): str {} } test "test" { @@ -858,7 +858,7 @@ fn reference_static_inflight_which_references_preflight_object() { let b = new cloud.Bucket(); class MyType { - static inflight staticMethod(): str { + pub static inflight staticMethod(): str { b.list(); return "foo"; } @@ -962,7 +962,7 @@ fn transitive_reference() { return this.b.list().length == 0; } - inflight checkIfEmpty() { + pub inflight checkIfEmpty() { if this.isEmpty() { log("empty!"); } @@ -1012,13 +1012,13 @@ fn transitive_reference_via_static() { let b = new cloud.Bucket(); class MyType { - static inflight putInBucket() { + pub static inflight putInBucket() { b.put("in", "bucket"); } } class YourType { - inflight putIndirect() { + pub inflight putIndirect() { MyType.putInBucket(); } } @@ -1053,7 +1053,7 @@ fn transitive_reference_via_inflight_class() { let b = new cloud.Bucket(); inflight class MyInflightClass { - putInBucket() { + pub putInBucket() { b.put("in", "bucket"); } } @@ -1071,7 +1071,7 @@ fn reference_inflight_class() { assert_compile_ok!( r#" inflight class Foo { - static a(): str { return "a"; } + pub static a(): str { return "a"; } } test "test" { @@ -1476,7 +1476,7 @@ fn base_class_captures_preflight() { let x = "hello"; class Base { - bar() { + protected bar() { log(x); } } @@ -1497,7 +1497,7 @@ fn base_class_captures_inflight() { let x = "hello"; class Base { - inflight bar() { + protected inflight bar() { log(x); } } @@ -1516,7 +1516,7 @@ fn base_class_with_fields_preflight() { assert_compile_ok!( r#" class Base { - f: str; + protected f: str; init() { this.f = "hello"; } @@ -1542,7 +1542,7 @@ fn base_class_with_fields_inflight() { assert_compile_ok!( r#" class Base { - inflight f: str; + protected inflight f: str; inflight init() { this.f = "hello"; } @@ -1570,7 +1570,7 @@ fn base_class_with_lifted_fields() { let x = "hello"; class Base { - f: str; + protected f: str; init() { this.f = x; } @@ -1615,7 +1615,7 @@ fn base_class_with_lifted_field_object() { bring cloud; class Base { - b: cloud.Bucket; + protected b: cloud.Bucket; init() { this.b = new cloud.Bucket(); } @@ -1642,7 +1642,7 @@ fn base_class_lift_indirect() { this.b = new cloud.Bucket(); } - inflight put() { + protected inflight put() { this.b.put("hello", "world"); this.b.list(); } @@ -1691,7 +1691,7 @@ fn indirect_capture() { b.list(); } - inflight goo() { + pub inflight goo() { this.foo(); } } @@ -1746,7 +1746,7 @@ fn lift_this() { inflight bar(): num { return this.x; } - inflight foo(): num { + pub inflight foo(): num { return this.bar() / 2; } } @@ -1859,7 +1859,7 @@ fn closure_field() { }; } - inflight foo(): str { + pub inflight foo(): str { return this.closure("anything"); } } @@ -1910,7 +1910,7 @@ fn lift_var_with_this() { assert_compile_ok!( r#" class Foo { - value: str; + pub value: str; init() { this.value = "hello"; } } diff --git a/libs/wingc/src/lib.rs b/libs/wingc/src/lib.rs index 7f006257797..874d133e03e 100644 --- a/libs/wingc/src/lib.rs +++ b/libs/wingc/src/lib.rs @@ -21,7 +21,7 @@ use lifting::LiftVisitor; use parser::parse_wing_project; use struct_schema::StructSchemaVisitor; use type_check::jsii_importer::JsiiImportSpec; -use type_check::symbol_env::StatementIdx; +use type_check::symbol_env::{StatementIdx, SymbolEnvKind}; use type_check::{FunctionSignature, SymbolKind, Type}; use type_check_assert::TypeCheckAssert; use valid_json_visitor::ValidJsonVisitor; @@ -200,7 +200,7 @@ pub fn type_check( jsii_types: &mut TypeSystem, jsii_imports: &mut Vec, ) { - let env = types.add_symbol_env(SymbolEnv::new(None, types.void(), false, false, Phase::Preflight, 0)); + let env = types.add_symbol_env(SymbolEnv::new(None, SymbolEnvKind::Scope, Phase::Preflight, 0)); types.set_scope_env(scope, env); // note: Globals are emitted here and wrapped in "{ ... }" blocks. Wrapping makes these emissions, actual diff --git a/libs/wingc/src/lifting.rs b/libs/wingc/src/lifting.rs index 5e131ab4bc3..7db1fb698e7 100644 --- a/libs/wingc/src/lifting.rs +++ b/libs/wingc/src/lifting.rs @@ -215,7 +215,7 @@ impl<'a> Visit<'a> for LiftVisitor<'a> { let mut lifts = v.lifts_stack.pop().unwrap(); let is_field = code.contains("this."); // TODO: starts_with? - lifts.lift(v.ctx.current_method(), property, &code, is_field); + lifts.lift(v.ctx.current_method().map(|(m,_)|m), property, &code, is_field); lifts.capture(&Liftable::Expr(node.id), &code, is_field); v.lifts_stack.push(lifts); return; @@ -273,7 +273,7 @@ impl<'a> Visit<'a> for LiftVisitor<'a> { } let mut lifts = self.lifts_stack.pop().unwrap(); - lifts.lift(self.ctx.current_method(), property, &code, false); + lifts.lift(self.ctx.current_method().map(|(m, _)| m), property, &code, false); self.lifts_stack.push(lifts); } @@ -299,8 +299,8 @@ impl<'a> Visit<'a> for LiftVisitor<'a> { match &node.body { FunctionBody::Statements(scope) => { self.ctx.push_function_definition( - &node.name, - &node.signature.phase, + node.name.as_ref(), + &node.signature, self.jsify.types.get_scope_env(&scope), ); @@ -336,22 +336,13 @@ impl<'a> Visit<'a> for LiftVisitor<'a> { return; } - // extract the "env" from the class initializer and push it to the context - // because this is the environment in which we want to resolve references - // as oppose to the environment of the class definition itself. - let init_env = if let FunctionBody::Statements(ref s) = node.initializer.body { - Some(self.jsify.types.get_scope_env(&s)) - } else { - None - }; - let udt = UserDefinedType { root: node.name.clone(), fields: vec![], span: node.name.span.clone(), }; - self.ctx.push_class(udt.clone(), &node.phase, init_env); + self.ctx.push_class(udt.clone(), &node.phase); self.lifts_stack.push(Lifts::new()); diff --git a/libs/wingc/src/lsp/hover.rs b/libs/wingc/src/lsp/hover.rs index 08b0df20111..8e61fb9240f 100644 --- a/libs/wingc/src/lsp/hover.rs +++ b/libs/wingc/src/lsp/hover.rs @@ -1,6 +1,6 @@ use crate::ast::{ - CalleeKind, Class, Expr, ExprKind, FunctionBody, FunctionDefinition, Phase, Reference, Scope, Stmt, StmtKind, Symbol, - UserDefinedType, + CalleeKind, Class, Expr, ExprKind, FunctionBody, FunctionDefinition, IfLet, Phase, Reference, Scope, Stmt, StmtKind, + Symbol, UserDefinedType, }; use crate::diagnostic::WingSpan; use crate::docs::Documented; @@ -162,14 +162,14 @@ impl<'a> Visit<'a> for HoverVisitor<'a> { self.visit_scope(finally_statements); } } - StmtKind::IfLet { + StmtKind::IfLet(IfLet { var_name, value, statements, reassignable: _, elif_statements, else_statements, - } => { + }) => { self.with_scope(statements, |v| { v.visit_symbol(var_name); }); diff --git a/libs/wingc/src/parser.rs b/libs/wingc/src/parser.rs index fa3cd90fd6c..2b9ccd10592 100644 --- a/libs/wingc/src/parser.rs +++ b/libs/wingc/src/parser.rs @@ -8,10 +8,10 @@ use tree_sitter::Node; use tree_sitter_traversal::{traverse, Order}; use crate::ast::{ - ArgList, AssignmentKind, BinaryOperator, BringSource, CalleeKind, CatchBlock, Class, ClassField, ElifBlock, - ElifLetBlock, Expr, ExprKind, FunctionBody, FunctionDefinition, FunctionParameter, FunctionSignature, Interface, - InterpolatedString, InterpolatedStringPart, Literal, NewExpr, Phase, Reference, Scope, Spanned, Stmt, StmtKind, - StructField, Symbol, TypeAnnotation, TypeAnnotationKind, UnaryOperator, UserDefinedType, + AccessModifier, ArgList, AssignmentKind, BinaryOperator, BringSource, CalleeKind, CatchBlock, Class, ClassField, + ElifBlock, ElifLetBlock, Expr, ExprKind, FunctionBody, FunctionDefinition, FunctionParameter, FunctionSignature, + IfLet, Interface, InterpolatedString, InterpolatedStringPart, Literal, NewExpr, Phase, Reference, Scope, Spanned, + Stmt, StmtKind, StructField, Symbol, TypeAnnotation, TypeAnnotationKind, UnaryOperator, UserDefinedType, }; use crate::comp_ctx::{CompilationContext, CompilationPhase}; use crate::diagnostic::{report_diagnostic, Diagnostic, DiagnosticResult, WingSpan}; @@ -27,7 +27,7 @@ static UNIMPLEMENTED_GRAMMARS: phf::Map<&'static str, &'static str> = phf_map! { "any" => "https://github.com/winglang/wing/issues/434", "Promise" => "https://github.com/winglang/wing/issues/529", "storage_modifier" => "https://github.com/winglang/wing/issues/107", - "access_modifier" => "https://github.com/winglang/wing/issues/108", + "internal" => "https://github.com/winglang/wing/issues/4156", "await_expression" => "https://github.com/winglang/wing/issues/116", "defer_expression" => "https://github.com/winglang/wing/issues/116", }; @@ -78,7 +78,8 @@ static RESERVED_WORDS: phf::Set<&'static str> = phf_set! { "package", "private", "protected", - "public", + "pub", + "internal", "return", "short", "static", @@ -615,14 +616,14 @@ impl<'s> Parser<'s> { } else { None }; - Ok(StmtKind::IfLet { + Ok(StmtKind::IfLet(IfLet { var_name: name, reassignable, value, statements: if_block, elif_statements: elif_vec, else_statements: else_block, - }) + })) } fn build_if_statement(&self, statement_node: &Node, phase: Phase) -> DiagnosticResult { @@ -909,6 +910,7 @@ impl<'s> Parser<'s> { reassignable: class_element.child_by_field_name("reassignable").is_some(), is_static, phase, + access_modifier: self.build_access_modifier(class_element.child_by_field_name("access_modifier"))?, }) } "initializer" => { @@ -961,6 +963,7 @@ impl<'s> Parser<'s> { }, is_static: false, span: self.node_span(&class_element), + access_modifier: AccessModifier::Public, }) } else { initializer = Some(FunctionDefinition { @@ -975,6 +978,7 @@ impl<'s> Parser<'s> { phase: Phase::Preflight, }, span: self.node_span(&class_element), + access_modifier: AccessModifier::Public, }) } } @@ -1018,6 +1022,7 @@ impl<'s> Parser<'s> { body: FunctionBody::Statements(Scope::new(vec![], WingSpan::default())), is_static: false, span: WingSpan::default(), + access_modifier: AccessModifier::Public, }, }; @@ -1042,6 +1047,7 @@ impl<'s> Parser<'s> { body: FunctionBody::Statements(Scope::new(vec![], WingSpan::default())), is_static: false, span: WingSpan::default(), + access_modifier: AccessModifier::Public, }, }; @@ -1233,6 +1239,7 @@ impl<'s> Parser<'s> { signature, is_static, span: self.node_span(func_def_node), + access_modifier: self.build_access_modifier(func_def_node.child_by_field_name("access_modifier"))?, }) } @@ -1305,6 +1312,17 @@ impl<'s> Parser<'s> { } } + fn build_access_modifier(&self, am_node: Option) -> DiagnosticResult { + match am_node { + Some(am_node) => match self.node_text(&am_node) { + "pub" => Ok(AccessModifier::Public), + "protected" => Ok(AccessModifier::Protected), + other => self.report_unimplemented_grammar(other, "access modifier", &am_node), + }, + None => Ok(AccessModifier::Private), + } + } + fn build_type_annotation(&self, type_node: Option, phase: Phase) -> DiagnosticResult { let type_node = &match type_node { Some(node) => node, @@ -2106,6 +2124,7 @@ impl<'s> Parser<'s> { }, is_static: true, span: statements_span.clone(), + access_modifier: AccessModifier::Public, }), statements_span.clone(), ); diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index ad776ccd667..cb0f5217e34 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -5,7 +5,8 @@ pub mod lifts; pub mod symbol_env; use crate::ast::{ - self, AssignmentKind, BringSource, CalleeKind, ClassField, ExprId, FunctionDefinition, NewExpr, TypeAnnotationKind, + self, AccessModifier, AssignmentKind, BringSource, CalleeKind, ClassField, ExprId, FunctionDefinition, IfLet, + NewExpr, TypeAnnotationKind, }; use crate::ast::{ ArgList, BinaryOperator, Class as AstClass, Expr, ExprKind, FunctionBody, FunctionParameter as AstFunctionParameter, @@ -15,6 +16,8 @@ use crate::ast::{ use crate::comp_ctx::{CompilationContext, CompilationPhase}; use crate::diagnostic::{report_diagnostic, Diagnostic, TypeError, WingSpan}; use crate::docs::Docs; +use crate::type_check::symbol_env::SymbolEnvKind; +use crate::visit_context::{VisitContext, VisitorWithContext}; use crate::visit_types::{VisitType, VisitTypeMut}; use crate::{ dbg_panic, debug, WINGSDK_ARRAY, WINGSDK_ASSEMBLY_NAME, WINGSDK_BRINGABLE_MODULES, WINGSDK_DURATION, WINGSDK_JSON, @@ -110,17 +113,21 @@ pub struct VariableInfo { pub phase: Phase, /// The kind of variable pub kind: VariableKind, + /// Access rules for this variable (only applies to methods and fields) + pub access_modifier: AccessModifier, pub docs: Option, } impl SymbolKind { + #[allow(clippy::too_many_arguments)] // TODO: refactor this pub fn make_member_variable( name: Symbol, type_: TypeRef, reassignable: bool, is_static: bool, phase: Phase, + access_modifier: AccessModifier, docs: Option, ) -> Self { SymbolKind::Variable(VariableInfo { @@ -133,6 +140,7 @@ impl SymbolKind { } else { VariableKind::InstanceMember }, + access_modifier, docs, }) } @@ -144,6 +152,7 @@ impl SymbolKind { reassignable, phase, kind: VariableKind::Free, + access_modifier: AccessModifier::Public, docs: None, }) } @@ -353,7 +362,7 @@ impl Display for Interface { type ClassLikeIterator<'a> = FilterMap, fn(::Item) -> Option<(String, TypeRef)>>; -pub trait ClassLike { +pub trait ClassLike: Display { fn get_env(&self) -> &SymbolEnv; fn methods(&self, with_ancestry: bool) -> ClassLikeIterator<'_> { @@ -441,6 +450,12 @@ pub struct Struct { pub env: SymbolEnv, } +impl Display for Struct { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name.name) + } +} + #[derive(Debug)] pub struct Enum { pub name: Symbol, @@ -912,6 +927,14 @@ impl TypeRef { } } + pub fn as_mut_struct(&mut self) -> Option<&mut Struct> { + if let Type::Struct(ref mut st) = **self { + Some(st) + } else { + None + } + } + pub fn as_interface(&self) -> Option<&Interface> { if let Type::Interface(ref iface) = **self { Some(iface) @@ -1283,10 +1306,7 @@ impl Types { types.push(Box::new(Type::Unresolved)); let err_idx = types.len() - 1; - // TODO: this is hack to create the top-level mapping from lib names to symbols - // We construct a void ref by hand since we can't call self.void() while constructing the Types struct - let void_ref = UnsafeRef::(&*types[void_idx] as *const Type); - let libraries = SymbolEnv::new(None, void_ref, false, false, Phase::Preflight, 0); + let libraries = SymbolEnv::new(None, SymbolEnvKind::Scope, Phase::Preflight, 0); Self { types, @@ -1595,7 +1615,7 @@ pub struct TypeChecker<'a> { /// makes it a lot simpler. Ideally we should avoid returning anything here and have some way /// to iterate over the inner scopes given the outer scope. For this we need to model our AST /// so all nodes implement some basic "tree" interface. For now this is good enough. - inner_scopes: Vec<*const Scope>, + inner_scopes: Vec<(*const Scope, VisitContext)>, /// The path to the source file being type checked. source_path: &'a Utf8Path, @@ -1607,13 +1627,9 @@ pub struct TypeChecker<'a> { /// The JSII type system jsii_types: &'a mut TypeSystem, - // Nesting level within JSON literals, a value larger than 0 means we're currently in a JSON literal - in_json: u64, - is_in_mut_json: bool, - /// Index of the current statement being type checked within the current scope - statement_idx: usize, + ctx: VisitContext, } impl<'a> TypeChecker<'a> { @@ -1629,9 +1645,8 @@ impl<'a> TypeChecker<'a> { jsii_types, source_path, jsii_imports, - in_json: 0, is_in_mut_json: false, - statement_idx: 0, + ctx: VisitContext::new(), } } @@ -1724,6 +1739,7 @@ impl<'a> TypeChecker<'a> { reassignable: false, phase: Phase::Independent, kind: VariableKind::Error, + access_modifier: AccessModifier::Public, docs: None, } } @@ -1870,7 +1886,7 @@ impl<'a> TypeChecker<'a> { } = new_expr; // Type check everything let class_type = self - .resolve_user_defined_type(class, env, self.statement_idx) + .resolve_user_defined_type(class, env, self.ctx.current_stmt_idx()) .unwrap_or_else(|e| self.type_error(e)); let obj_scope_type = obj_scope.as_ref().map(|x| self.type_check_exp(x, env).0); let obj_id_type = obj_id.as_ref().map(|x| self.type_check_exp(x, env).0); @@ -1952,7 +1968,7 @@ impl<'a> TypeChecker<'a> { let obj_scope_type = if obj_scope_type.is_none() { // If this returns None, this means we're instantiating a preflight object in the global scope, which is valid env - .lookup(&"this".into(), Some(self.statement_idx)) + .lookup(&"this".into(), Some(self.ctx.current_stmt_idx())) .map(|v| v.as_variable().expect("Expected \"this\" to be a variable").type_) } else { // If this is a non-standard preflight class, make sure the object's scope isn't explicitly set (using the `in` keywords) @@ -2127,7 +2143,7 @@ impl<'a> TypeChecker<'a> { } }; (container_type, element_type) - } else if self.in_json > 0 { + } else if self.ctx.in_json() { let json_data = JsonData { expression_id: exp.id, kind: JsonDataKind::List(vec![]), @@ -2153,7 +2169,7 @@ impl<'a> TypeChecker<'a> { } } - if self.in_json == 0 { + if !self.ctx.in_json() { // If we're not in a Json literal, validate the type of each element self.validate_type(t, element_type, item); element_type = self.types.maybe_unwrap_inference(element_type); @@ -2270,7 +2286,7 @@ impl<'a> TypeChecker<'a> { // Verify that no unexpected fields are present for (name, _t) in field_types.iter() { - if st.env.lookup(name, Some(self.statement_idx)).is_none() { + if st.env.lookup(name, Some(self.ctx.current_stmt_idx())).is_none() { self.spanned_error(exp, format!("\"{}\" is not a field of \"{}\"", name.name, st.name.name)); } } @@ -2282,12 +2298,12 @@ impl<'a> TypeChecker<'a> { self.is_in_mut_json = true; } - self.in_json += 1; + self.ctx.push_json(); let (known_type, _) = self.type_check_exp(&element, env); - self.in_json -= 1; + self.ctx.pop_json(); // When we are no longer in a Json literal, we reset the is_in_mut_json flag - if self.in_json == 0 { + if !self.ctx.in_json() { self.is_in_mut_json = false; } @@ -2384,7 +2400,7 @@ impl<'a> TypeChecker<'a> { &mut self, arg_list: &ArgList, func_sig: &FunctionSignature, - exp: &impl Spanned, + call_span: &impl Spanned, arg_list_types: ArgListTypes, ) -> Option { // Verify arity @@ -2395,7 +2411,7 @@ impl<'a> TypeChecker<'a> { "Expected {} positional argument(s) but got {}", min_args, pos_args_count ); - self.spanned_error(exp, err_text); + self.spanned_error(call_span, err_text); return Some(self.types.error()); } @@ -2404,19 +2420,22 @@ impl<'a> TypeChecker<'a> { Some(arg) => arg.typeref.maybe_unwrap_option(), None => { self.spanned_error( - exp, - format!("Expected 0 named arguments for func at {}", exp.span().to_string()), + call_span, + format!( + "Expected 0 named arguments for func at {}", + call_span.span().to_string() + ), ); return Some(self.types.error()); } }; if !last_arg.is_struct() { - self.spanned_error(exp, "No named arguments expected"); + self.spanned_error(call_span, "No named arguments expected"); return Some(self.types.error()); } - self.validate_structural_type(&arg_list_types.named_args, &last_arg, exp); + self.validate_structural_type(&arg_list_types.named_args, &last_arg, call_span); } // Count number of optional parameters from the end of the function's params @@ -2454,7 +2473,7 @@ impl<'a> TypeChecker<'a> { min_args, max_args, arg_count ) }; - self.spanned_error(exp, err_text); + self.spanned_error(call_span, err_text); } let params = func_sig.parameters.iter(); @@ -2499,6 +2518,7 @@ impl<'a> TypeChecker<'a> { fn type_check_closure(&mut self, func_def: &ast::FunctionDefinition, env: &SymbolEnv) -> (TypeRef, Phase) { // TODO: make sure this function returns on all control paths when there's a return type (can be done by recursively traversing the statements and making sure there's a "return" statements in all control paths) // https://github.com/winglang/wing/issues/457 + // Create a type_checker function signature from the AST function definition let function_type = self.resolve_type_annotation(&func_def.signature.to_type_annotation(), env); let sig = function_type.as_function_sig().unwrap(); @@ -2506,24 +2526,27 @@ impl<'a> TypeChecker<'a> { // Create an environment for the function let mut function_env = self.types.add_symbol_env(SymbolEnv::new( Some(env.get_ref()), - sig.return_type, - false, - true, + SymbolEnvKind::Function { + is_init: false, + sig: function_type, + }, func_def.signature.phase, - self.statement_idx, + self.ctx.current_stmt_idx(), )); self.add_arguments_to_env(&func_def.signature.parameters, &sig, &mut function_env); - // Type check the function body - if let FunctionBody::Statements(scope) = &func_def.body { - self.types.set_scope_env(scope, function_env); + self.with_function_def(None, &func_def.signature, function_env, |tc| { + // Type check the function body + if let FunctionBody::Statements(scope) = &func_def.body { + tc.types.set_scope_env(scope, function_env); - self.inner_scopes.push(scope); + tc.inner_scopes.push((scope, tc.ctx.clone())); - (function_type, sig.phase) - } else { - (function_type, sig.phase) - } + (function_type, sig.phase) + } else { + (function_type, sig.phase) + } + }) } /// Validate that a given map can be assigned to a variable of given struct type @@ -2793,17 +2816,20 @@ impl<'a> TypeChecker<'a> { // reverse list to type check later scopes first // Why? To improve the inference algorithm. Earlier inner_scopes for closures may need to infer types from later inner_scopes let inner_scopes = self.inner_scopes.drain(..).rev().collect::>(); - for inner_scope in inner_scopes { + for (inner_scope, ctx) in inner_scopes { let scope = unsafe { &*inner_scope }; + self.ctx = ctx; self.type_check_scope(scope); } - if env.is_function { - if let Type::Inferred(n) = &*env.return_type { + + if let SymbolEnvKind::Function { sig, .. } = env.kind { + let mut return_type = sig.as_function_sig().expect("a fucntion type").return_type; + if let Type::Inferred(n) = &*return_type { if self.types.get_inference_by_id(*n).is_none() { // If function types don't return anything then we should set the return type to void self.types.update_inferred_type(*n, self.types.void(), &scope.span); } - self.update_known_inferences(&mut env.return_type, &scope.span); + self.update_known_inferences(&mut return_type, &scope.span); } } @@ -2877,7 +2903,7 @@ impl<'a> TypeChecker<'a> { self.types.add_type(Type::Function(sig)) } TypeAnnotationKind::UserDefined(user_defined_type) => self - .resolve_user_defined_type(user_defined_type, env, self.statement_idx) + .resolve_user_defined_type(user_defined_type, env, self.ctx.current_stmt_idx()) .unwrap_or_else(|e| self.type_error(e)), TypeAnnotationKind::Array(v) => { let value_type = self.resolve_type_annotation(v, env); @@ -2941,166 +2967,33 @@ impl<'a> TypeChecker<'a> { named_args: named_arg_types, } } + fn type_check_statement(&mut self, stmt: &Stmt, env: &mut SymbolEnv) { CompilationContext::set(CompilationPhase::TypeChecking, &stmt.span); - // Set the current statement index for symbol lookup checks. We can safely assume we're - // not overwriting the current statement index because `type_check_statement` is never - // recursively called (we use a breadth-first traversal of the AST statements). - self.statement_idx = stmt.idx; - - match &stmt.kind { + // Set the current statement index for symbol lookup checks. + self.with_stmt(stmt.idx, |tc| match &stmt.kind { StmtKind::Let { reassignable, var_name, initial_value, type_, } => { - let explicit_type = type_.as_ref().map(|t| self.resolve_type_annotation(t, env)); - let (mut inferred_type, _) = self.type_check_exp(initial_value, env); - if inferred_type.is_void() { - self.spanned_error( - var_name, - format!("Cannot assign expression of type \"{}\" to a variable", inferred_type), - ); - } - if explicit_type.is_none() && inferred_type.is_nil() { - self.spanned_error( - initial_value, - "Cannot assign nil value to variables without explicit optional type", - ); - } - if let Some(explicit_type) = explicit_type { - self.validate_type(inferred_type, explicit_type, initial_value); - let final_type = if !*reassignable && explicit_type.is_json() && inferred_type.is_json() { - // If both types are Json, use the inferred type in case it has more information - inferred_type - } else { - explicit_type - }; - match env.define( - var_name, - SymbolKind::make_free_variable(var_name.clone(), final_type, *reassignable, env.phase), - StatementIdx::Index(stmt.idx), - ) { - Err(type_error) => { - self.type_error(type_error); - } - _ => {} - }; - } else { - if *reassignable && inferred_type.is_json() { - if let Type::Json(Some(_)) = *inferred_type { - // We do not have the required analysis to know the type of the Json data after reassignment - inferred_type = self.types.json(); - } - } - match env.define( - var_name, - SymbolKind::make_free_variable(var_name.clone(), inferred_type, *reassignable, env.phase), - StatementIdx::Index(stmt.idx), - ) { - Err(type_error) => { - self.type_error(type_error); - } - _ => {} - }; - } + tc.type_check_let(type_, env, initial_value, var_name, reassignable); } StmtKind::ForLoop { iterator, iterable, statements, } => { - // TODO: Expression must be iterable - let (exp_type, _) = self.type_check_exp(iterable, env); - - if !exp_type.is_iterable() { - self.spanned_error(iterable, format!("Unable to iterate over \"{}\"", &exp_type)); - } - - let iterator_type = match &*exp_type { - // These are builtin iterables that have a clear/direct iterable type - Type::Array(t) => *t, - Type::Set(t) => *t, - Type::MutArray(t) => *t, - Type::MutSet(t) => *t, - Type::Anything => exp_type, - _t => self.types.error(), - }; - - let mut scope_env = self.types.add_symbol_env(SymbolEnv::new( - Some(env.get_ref()), - env.return_type, - false, - false, - env.phase, - stmt.idx, - )); - match scope_env.define( - &iterator, - SymbolKind::make_free_variable(iterator.clone(), iterator_type, false, env.phase), - StatementIdx::Top, - ) { - Err(type_error) => { - self.type_error(type_error); - } - _ => {} - }; - self.types.set_scope_env(statements, scope_env); - - self.inner_scopes.push(statements); + tc.type_check_for_loop(iterable, env, iterator, statements); } StmtKind::While { condition, statements } => { - let (cond_type, _) = self.type_check_exp(condition, env); - self.validate_type(cond_type, self.types.bool(), condition); - - let scope_env = self.types.add_symbol_env(SymbolEnv::new( - Some(env.get_ref()), - env.return_type, - false, - false, - env.phase, - stmt.idx, - )); - self.types.set_scope_env(statements, scope_env); - - self.inner_scopes.push(statements); + tc.type_check_while(condition, env, statements); } StmtKind::Break | StmtKind::Continue => {} - StmtKind::IfLet { - value, - statements, - reassignable, - var_name, - elif_statements, - else_statements, - } => { - self.type_check_if_let_statement(value, statements, reassignable, var_name, stmt, env); - - for elif_scope in elif_statements { - self.type_check_if_let_statement( - &elif_scope.value, - &elif_scope.statements, - &elif_scope.reassignable, - &elif_scope.var_name, - stmt, - env, - ); - } - - if let Some(else_scope) = else_statements { - let else_scope_env = self.types.add_symbol_env(SymbolEnv::new( - Some(env.get_ref()), - env.return_type, - false, - false, - env.phase, - stmt.idx, - )); - self.types.set_scope_env(else_scope, else_scope_env); - self.inner_scopes.push(else_scope); - } + StmtKind::IfLet(iflet) => { + tc.type_check_iflet(env, iflet); } StmtKind::If { condition, @@ -3108,587 +3001,823 @@ impl<'a> TypeChecker<'a> { elif_statements, else_statements, } => { - self.type_check_if_statement(condition, statements, stmt, env); - - for elif_scope in elif_statements { - self.type_check_if_statement(&elif_scope.condition, &elif_scope.statements, stmt, env); - } - - if let Some(else_scope) = else_statements { - let else_scope_env = self.types.add_symbol_env(SymbolEnv::new( - Some(env.get_ref()), - env.return_type, - false, - false, - env.phase, - stmt.idx, - )); - self.types.set_scope_env(else_scope, else_scope_env); - self.inner_scopes.push(else_scope); - } + tc.type_check_if(condition, statements, env, elif_statements, else_statements); } StmtKind::Expression(e) => { - self.type_check_exp(e, env); + tc.type_check_exp(e, env); } StmtKind::Assignment { kind, variable, value } => { - let (exp_type, _) = self.type_check_exp(value, env); - - // TODO: we need to verify that if this variable is defined in a parent environment (i.e. - // being captured) it cannot be reassigned: https://github.com/winglang/wing/issues/3069 - - let (var, var_phase) = self.resolve_reference(&variable, env); - - if !var.type_.is_unresolved() && !var.reassignable { - self.spanned_error(variable, "Variable is not reassignable".to_string()); - } else if var_phase == Phase::Preflight && env.phase == Phase::Inflight { - self.spanned_error(stmt, "Variable cannot be reassigned from inflight".to_string()); - } - - if matches!(&kind, AssignmentKind::AssignIncr | AssignmentKind::AssignDecr) { - self.validate_type(exp_type, self.types.number(), value); - self.validate_type(var.type_, self.types.number(), variable); - } - - self.validate_type(exp_type, var.type_, value); + tc.type_check_assignment(kind, value, env, variable); } StmtKind::Bring { source, identifier } => { - // library_name is the name of the library we are importing from the JSII world - let library_name: String; - // namespace_filter describes what types we are importing from the library - // e.g. [] means we are importing everything from `mylib` - // e.g. ["ns1", "ns2"] means we are importing everything from `mylib.ns1.ns2` - let namespace_filter: Vec; - // alias is the symbol we are giving to the imported library or namespace - let alias: &Symbol; - - match &source { - BringSource::BuiltinModule(name) => { - if WINGSDK_BRINGABLE_MODULES.contains(&name.name.as_str()) { - library_name = WINGSDK_ASSEMBLY_NAME.to_string(); - namespace_filter = vec![name.name.clone()]; - alias = identifier.as_ref().unwrap_or(&name); - } else if name.name.as_str() == WINGSDK_STD_MODULE { - self.spanned_error(stmt, format!("Redundant bring of \"{}\"", WINGSDK_STD_MODULE)); - return; - } else { - self.spanned_error(stmt, format!("\"{}\" is not a built-in module", name)); - return; - } - } - BringSource::JsiiModule(name) => { - library_name = name.name.to_string(); - // no namespace filter (we only support importing entire libraries at the moment) - namespace_filter = vec![]; - alias = identifier.as_ref().unwrap(); - } - BringSource::WingFile(name) => { - let brought_env = match self.types.source_file_envs.get(Utf8Path::new(&name.name)) { - Some(env) => *env, - None => { - self.spanned_error( - stmt, - format!("Could not type check \"{}\" due to cyclic bring statements", name), - ); - return; - } - }; - let ns = self.types.add_namespace(Namespace { - name: name.name.to_string(), - env: SymbolEnv::new( - Some(brought_env.get_ref()), - brought_env.return_type, - false, - false, - brought_env.phase, - 0, - ), - loaded: true, - }); - if let Err(e) = env.define( - identifier.as_ref().unwrap(), - SymbolKind::Namespace(ns), - StatementIdx::Top, - ) { - self.type_error(e); - } - return; - } - } - - self.add_module_to_env(env, library_name, namespace_filter, alias, Some(&stmt)); + tc.type_check_bring(source, identifier, stmt, env); } StmtKind::Scope(scope) => { - let scope_env = self.types.add_symbol_env(SymbolEnv::new( + let scope_env = tc.types.add_symbol_env(SymbolEnv::new( Some(env.get_ref()), - env.return_type, - false, - false, + SymbolEnvKind::Scope, env.phase, stmt.idx, )); - self.types.set_scope_env(scope, scope_env); - self.inner_scopes.push(scope) + tc.types.set_scope_env(scope, scope_env); + tc.inner_scopes.push((scope, tc.ctx.clone())); } StmtKind::Throw(exp) => { - let (exp_type, _) = self.type_check_exp(exp, env); - self.validate_type(exp_type, self.types.string(), exp); + tc.type_check_throw(exp, env); } StmtKind::Return(exp) => { - let return_type_inferred = self.update_known_inferences(&mut env.return_type, &stmt.span); - if let Some(return_expression) = exp { - let (return_type, _) = self.type_check_exp(return_expression, env); - if !env.return_type.is_void() { - self.validate_type(return_type, env.return_type, return_expression); - } else if env.is_in_function() { - if return_type_inferred { - self.spanned_error(stmt, "Unexpected return value from void function"); - } else { - self.spanned_error( - stmt, - "Unexpected return value from void function. Return type annotations are required for methods.", - ); - } - } else { - self.spanned_error(stmt, "Return statement outside of function cannot return a value"); - } - } else { - self.validate_type(self.types.void(), env.return_type, stmt); - } - - if let Type::Json(d) = &mut *env.return_type { - // We do not have the required analysis to know the type of the Json data after return - if d.is_some() { - d.take(); - } - } + tc.type_check_return(env, stmt, exp); } - StmtKind::Class(AstClass { - name, - fields, - methods, - parent, - implements, - initializer, - phase, - inflight_initializer, - }) => { - // preflight classes cannot be declared inside an inflight scope - // (the other way is okay) - if env.phase == Phase::Inflight && *phase == Phase::Preflight { - self.spanned_error(stmt, format!("Cannot declare a {} class in {} scope", phase, env.phase)); - } - // Verify parent is a known class and get their env - let (parent_class, parent_class_env) = self.extract_parent_class(parent.as_ref(), *phase, name, env); + StmtKind::Class(ast_class) => { + tc.type_check_class(env, stmt, ast_class); + } + StmtKind::Interface(AstInterface { name, methods, extends }) => { + tc.type_check_interface(env, extends, name, methods); + } + StmtKind::Struct { name, extends, fields } => { + tc.type_check_struct(fields, env, extends, name); + } + StmtKind::Enum { name, values } => { + tc.type_check_enum(name, values, env); + } + StmtKind::TryCatch { + try_statements, + catch_block, + finally_statements, + } => { + tc.type_check_try_catch(env, try_statements, catch_block, finally_statements); + } + StmtKind::CompilerDebugEnv => { + println!("[symbol environment at {}]", stmt.span); + println!("{}", env); + } + StmtKind::SuperConstructor { arg_list } => { + tc.type_check_super_constructor_against_parent_initializer(stmt, arg_list, env); + } + }); + } - // Create environment representing this class, for now it'll be empty just so we can support referencing ourselves from the class definition. - let dummy_env = SymbolEnv::new(None, self.types.void(), false, false, env.phase, stmt.idx); + fn type_check_try_catch( + &mut self, + env: &mut SymbolEnv, + try_statements: &Scope, + catch_block: &Option, + finally_statements: &Option, + ) { + // Create a new environment for the try block + let try_env = self.types.add_symbol_env(SymbolEnv::new( + Some(env.get_ref()), + SymbolEnvKind::Scope, + env.phase, + self.ctx.current_stmt_idx(), + )); + self.types.set_scope_env(try_statements, try_env); + self.inner_scopes.push((try_statements, self.ctx.clone())); + + // Create a new environment for the catch block + if let Some(catch_block) = catch_block { + let mut catch_env = self.types.add_symbol_env(SymbolEnv::new( + Some(env.get_ref()), + SymbolEnvKind::Scope, + env.phase, + self.ctx.current_stmt_idx(), + )); - let impl_interfaces = implements - .iter() - .filter_map(|i| { - let t = self - .resolve_user_defined_type(i, env, stmt.idx) - .unwrap_or_else(|e| self.type_error(e)); - if t.as_interface().is_some() { - Some(t) - } else { - self.spanned_error(i, format!("Expected an interface, instead found type \"{}\"", t)); - None - } - }) - .collect::>(); - - // Create the resource/class type and add it to the current environment (so class implementation can reference itself) - let class_spec = Class { - name: name.clone(), - fqn: None, - env: dummy_env, - parent: parent_class, - implements: impl_interfaces.clone(), - is_abstract: false, - phase: *phase, - type_parameters: None, // TODO no way to have generic args in wing yet - docs: Docs::default(), - std_construct_args: *phase == Phase::Preflight, - lifts: None, - }; - let mut class_type = self.types.add_type(Type::Class(class_spec)); - match env.define(name, SymbolKind::Type(class_type), StatementIdx::Top) { + // Add the exception variable to the catch block + if let Some(exception_var) = &catch_block.exception_var { + match catch_env.define( + exception_var, + SymbolKind::make_free_variable(exception_var.clone(), self.types.string(), false, env.phase), + StatementIdx::Top, + ) { Err(type_error) => { self.type_error(type_error); } _ => {} - }; - - // Create a the real class environment to be filled with the class AST types - let mut class_env = SymbolEnv::new(parent_class_env, self.types.void(), false, false, env.phase, stmt.idx); - - // Add fields to the class env - for field in fields.iter() { - let field_type = self.resolve_type_annotation(&field.member_type, env); - match class_env.define( - &field.name, - SymbolKind::make_member_variable( - field.name.clone(), - field_type, - field.reassignable, - field.is_static, - field.phase, - None, - ), - StatementIdx::Top, - ) { - Err(type_error) => { - self.type_error(type_error); - } - _ => {} - }; } + } + self.types.set_scope_env(&catch_block.statements, catch_env); + self.inner_scopes.push((&catch_block.statements, self.ctx.clone())); + } - // Add methods to the class env - for (method_name, method_def) in methods.iter() { - self.add_method_to_class_env( - &method_def.signature, - env, - if method_def.is_static { None } else { Some(class_type) }, - &mut class_env, - method_name, - ); - } + // Create a new environment for the finally block + if let Some(finally_statements) = finally_statements { + let finally_env = self.types.add_symbol_env(SymbolEnv::new( + Some(env.get_ref()), + SymbolEnvKind::Scope, + env.phase, + self.ctx.current_stmt_idx(), + )); + self.types.set_scope_env(finally_statements, finally_env); + self.inner_scopes.push((finally_statements, self.ctx.clone())); + } + } - // Add the constructor to the class env - let init_symb = Symbol { - name: CLASS_INIT_NAME.into(), - span: initializer.span.clone(), - }; + fn type_check_enum(&mut self, name: &Symbol, values: &IndexSet, env: &mut SymbolEnv) { + let enum_type_ref = self.types.add_type(Type::Enum(Enum { + name: name.clone(), + values: values.clone(), + docs: Default::default(), + })); - self.add_method_to_class_env(&initializer.signature, env, None, &mut class_env, &init_symb); + match env.define(name, SymbolKind::Type(enum_type_ref), StatementIdx::Top) { + Err(type_error) => { + self.type_error(type_error); + } + _ => {} + }; + } - let inflight_init_symb = Symbol { - name: CLASS_INFLIGHT_INIT_NAME.into(), - span: inflight_initializer.span.clone(), - }; + fn type_check_struct( + &mut self, + fields: &Vec, + env: &mut SymbolEnv, + extends: &Vec, + name: &Symbol, + ) { + // Note: structs don't have a parent environment, instead they flatten their parent's members into the struct's env. + // If we encounter an existing member with the same name and type we skip it, if the types are different we + // fail type checking. - // Add the inflight initializer to the class env - self.add_method_to_class_env( - &inflight_initializer.signature, - env, - Some(class_type), - &mut class_env, - &inflight_init_symb, - ); + // Create a dummy environment for the struct so we can create the struct type + let dummy_env = SymbolEnv::new( + None, + SymbolEnvKind::Type(self.types.void()), + Phase::Independent, + self.ctx.current_stmt_idx(), + ); - // Replace the dummy class environment with the real one before type checking the methods - if let Some(mut_class) = class_type.as_class_mut() { - mut_class.env = class_env; + // Collect types this struct extends + let extends_types = extends + .iter() + .filter_map(|ext| { + let t = self + .resolve_user_defined_type(ext, env, self.ctx.current_stmt_idx()) + .unwrap_or_else(|e| self.type_error(e)); + if t.as_struct().is_some() { + Some(t) } else { - panic!("Expected class type"); + self.spanned_error(ext, format!("Expected a struct, found type \"{}\"", t)); + None } - // let mut class_env = &mut mut_class.env; - - if let FunctionBody::Statements(scope) = &inflight_initializer.body { - self.check_class_field_initialization(&scope, fields, Phase::Inflight); - self.type_check_super_constructor_against_parent_initializer( - scope, - class_type, - &mut class_type.as_class_mut().unwrap().env, - CLASS_INFLIGHT_INIT_NAME, - ); - }; + }) + .collect::>(); - // Type check constructor - self.type_check_method(class_type, &init_symb, env, stmt.idx, initializer); + // Create the struct type + let mut struct_type = self.types.add_type(Type::Struct(Struct { + name: name.clone(), + extends: extends_types.clone(), + env: dummy_env, + docs: Docs::default(), + })); - // Verify if all fields of a class/resource are initialized in the initializer. - let init_statements = match &initializer.body { - FunctionBody::Statements(s) => s, - FunctionBody::External(_) => panic!("init cannot be extern"), - }; + // Create the actual environment for the struct + let mut struct_env = SymbolEnv::new( + None, + SymbolEnvKind::Type(struct_type), + Phase::Independent, + self.ctx.current_stmt_idx(), + ); - self.check_class_field_initialization(&init_statements, fields, Phase::Preflight); + // Add fields to the struct env + for field in fields.iter() { + let field_type = self.resolve_type_annotation(&field.member_type, env); + if field_type.is_mutable() { + self.spanned_error(&field.name, "Struct fields must have immutable types"); + } + match struct_env.define( + &field.name, + SymbolKind::make_member_variable( + field.name.clone(), + field_type, + false, + false, + Phase::Independent, + AccessModifier::Public, + None, + ), + StatementIdx::Top, + ) { + Err(type_error) => { + self.type_error(type_error); + } + _ => {} + }; + } - self.type_check_super_constructor_against_parent_initializer( - init_statements, - class_type, - &mut class_type.as_class_mut().unwrap().env, - CLASS_INIT_NAME, - ); + if let Err(e) = add_parent_members_to_struct_env(&extends_types, name, &mut struct_env) { + self.type_error(e); + } + match env.define(name, SymbolKind::Type(struct_type), StatementIdx::Top) { + Err(type_error) => { + self.type_error(type_error); + } + _ => {} + }; - // Type check the inflight initializer - self.type_check_method(class_type, &inflight_init_symb, env, stmt.idx, inflight_initializer); + // Replace the dummy struct environment with the real one + struct_type.as_mut_struct().unwrap().env = struct_env; + } - // TODO: handle member/method overrides in our env based on whatever rules we define in our spec - // https://github.com/winglang/wing/issues/1124 + fn type_check_interface( + &mut self, + env: &mut SymbolEnv, + extends: &Vec, + name: &Symbol, + methods: &Vec<(Symbol, ast::FunctionSignature)>, + ) { + // Create environment representing this interface, for now it'll be empty just so we can support referencing ourselves from the interface definition. + let dummy_env = SymbolEnv::new( + None, + SymbolEnvKind::Type(self.types.void()), + env.phase, + self.ctx.current_stmt_idx(), + ); - // Type check methods - for (method_name, method_def) in methods.iter() { - self.type_check_method(class_type, method_name, env, stmt.idx, method_def); + let extend_interfaces = extends + .iter() + .filter_map(|i| { + let t = self + .resolve_user_defined_type(i, env, self.ctx.current_stmt_idx()) + .unwrap_or_else(|e| self.type_error(e)); + if t.as_interface().is_some() { + Some(t) + } else { + // The type checker resolves non-existing definitions to `any`, so we avoid duplicate errors by checking for that here + if !t.is_unresolved() { + self.spanned_error(i, format!("Expected an interface, instead found type \"{}\"", t)); + } + None } + }) + .collect::>(); - // Check that the class satisfies all of its interfaces - for interface_type in impl_interfaces.iter() { - let interface_type = match interface_type.as_interface() { - Some(t) => t, - None => { - // No need to error here, it will be caught when `impl_interaces` was created - continue; - } - }; + // Create the interface type and add it to the current environment (so interface implementation can reference itself) + let interface_spec = Interface { + name: name.clone(), + docs: Docs::default(), + env: dummy_env, + extends: extend_interfaces.clone(), + }; + let mut interface_type = self.types.add_type(Type::Interface(interface_spec)); + match env.define(name, SymbolKind::Type(interface_type), StatementIdx::Top) { + Err(type_error) => { + self.type_error(type_error); + } + _ => {} + }; - // Check all methods are implemented - for (method_name, method_type) in interface_type.methods(true) { - if let Some(symbol) = &mut class_type - .as_class_mut() - .unwrap() - .env - .lookup(&method_name.as_str().into(), None) - { - let class_method_type = symbol.as_variable().expect("Expected method to be a variable").type_; - self.validate_type(class_method_type, method_type, name); - } else { - self.spanned_error( - name, - format!( - "Class \"{}\" does not implement method \"{}\" of interface \"{}\"", - name.name, method_name, interface_type.name.name - ), - ); - } - } + // Create the real interface environment to be filled with the interface AST types + let mut interface_env = SymbolEnv::new( + None, + SymbolEnvKind::Type(interface_type), + env.phase, + self.ctx.current_stmt_idx(), + ); - // Check all fields are implemented - for (field_name, field_type) in interface_type.fields(true) { - if let Some(symbol) = &mut class_type - .as_class_mut() - .unwrap() - .env - .lookup(&field_name.as_str().into(), None) - { - let class_field_type = symbol.as_variable().expect("Expected field to be a variable").type_; - self.validate_type(class_field_type, field_type, name); - } else { - self.spanned_error( - name, - format!( - "Class \"{}\" does not implement field \"{}\" of interface \"{}\"", - name.name, field_name, interface_type.name.name - ), - ); - } - } - } + // Add methods to the interface env + for (method_name, sig) in methods.iter() { + let mut method_type = self.resolve_type_annotation(&sig.to_type_annotation(), env); + // use the interface type as the function's "this" type + if let Type::Function(ref mut f) = *method_type { + f.this_type = Some(interface_type); + } else { + panic!("Expected method type to be a function"); } - StmtKind::Interface(AstInterface { name, methods, extends }) => { - // Create environment representing this interface, for now it'll be empty just so we can support referencing ourselves from the interface definition. - let dummy_env = SymbolEnv::new(None, self.types.void(), false, false, env.phase, stmt.idx); - let extend_interfaces = extends - .iter() - .filter_map(|i| { - let t = self - .resolve_user_defined_type(i, env, stmt.idx) - .unwrap_or_else(|e| self.type_error(e)); - if t.as_interface().is_some() { - Some(t) - } else { - // The type checker resolves non-existing definitions to `any`, so we avoid duplicate errors by checking for that here - if !t.is_unresolved() { - self.spanned_error(i, format!("Expected an interface, instead found type \"{}\"", t)); - } - None - } - }) - .collect::>(); + match interface_env.define( + method_name, + SymbolKind::make_member_variable( + method_name.clone(), + method_type, + false, + false, + sig.phase, + AccessModifier::Public, + None, + ), + StatementIdx::Top, + ) { + Err(type_error) => { + self.type_error(type_error); + } + _ => {} + }; + } - // Create the interface type and add it to the current environment (so interface implementation can reference itself) - let interface_spec = Interface { - name: name.clone(), - docs: Docs::default(), - env: dummy_env, - extends: extend_interfaces.clone(), - }; - let mut interface_type = self.types.add_type(Type::Interface(interface_spec)); - match env.define(name, SymbolKind::Type(interface_type), StatementIdx::Top) { - Err(type_error) => { - self.type_error(type_error); - } - _ => {} - }; + // add methods from all extended interfaces to the interface env + if let Err(e) = add_parent_members_to_iface_env(&extend_interfaces, name, &mut interface_env) { + self.type_error(e); + } - // Create the real interface environment to be filled with the interface AST types - let mut interface_env = SymbolEnv::new(None, self.types.void(), false, false, env.phase, stmt.idx); + // Replace the dummy interface environment with the real one + interface_type.as_mut_interface().unwrap().env = interface_env; + } - // Add methods to the interface env - for (method_name, sig) in methods.iter() { - let mut method_type = self.resolve_type_annotation(&sig.to_type_annotation(), env); - // use the interface type as the function's "this" type - if let Type::Function(ref mut f) = *method_type { - f.this_type = Some(interface_type); - } else { - panic!("Expected method type to be a function"); - } + fn type_check_class(&mut self, env: &mut SymbolEnv, stmt: &Stmt, ast_class: &AstClass) { + self.ctx.push_class(UserDefinedType::for_class(ast_class), &env.phase); - match interface_env.define( - method_name, - SymbolKind::make_member_variable(method_name.clone(), method_type, false, false, sig.phase, None), - StatementIdx::Top, - ) { - Err(type_error) => { - self.type_error(type_error); - } - _ => {} - }; + // preflight classes cannot be declared inside an inflight scope + // (the other way is okay) + if env.phase == Phase::Inflight && ast_class.phase == Phase::Preflight { + self.spanned_error( + stmt, + format!("Cannot declare a {} class in {} scope", ast_class.phase, env.phase), + ); + } + // Verify parent is a known class and get their env + let (parent_class, parent_class_env) = + self.extract_parent_class(ast_class.parent.as_ref(), ast_class.phase, &ast_class.name, env); + + // Create environment representing this class, for now it'll be empty just so we can support referencing ourselves from the class definition. + let dummy_env = SymbolEnv::new(None, SymbolEnvKind::Type(self.types.void()), env.phase, stmt.idx); + + let impl_interfaces = ast_class + .implements + .iter() + .filter_map(|i| { + let t = self + .resolve_user_defined_type(i, env, stmt.idx) + .unwrap_or_else(|e| self.type_error(e)); + if t.as_interface().is_some() { + Some(t) + } else { + self.spanned_error(i, format!("Expected an interface, instead found type \"{}\"", t)); + None } + }) + .collect::>(); + + // Create the resource/class type and add it to the current environment (so class implementation can reference itself) + let class_spec = Class { + name: ast_class.name.clone(), + fqn: None, + env: dummy_env, + parent: parent_class, + implements: impl_interfaces.clone(), + is_abstract: false, + phase: ast_class.phase, + type_parameters: None, // TODO no way to have generic args in wing yet + docs: Docs::default(), + std_construct_args: ast_class.phase == Phase::Preflight, + lifts: None, + }; + let mut class_type = self.types.add_type(Type::Class(class_spec)); + match env.define(&ast_class.name, SymbolKind::Type(class_type), StatementIdx::Top) { + Err(type_error) => { + self.type_error(type_error); + } + _ => {} + }; - // add methods from all extended interfaces to the interface env - if let Err(e) = add_parent_members_to_iface_env(&extend_interfaces, name, &mut interface_env) { - self.type_error(e); + // Create a the real class environment to be filled with the class AST types + let mut class_env = SymbolEnv::new(parent_class_env, SymbolEnvKind::Type(class_type), env.phase, stmt.idx); + + // Add fields to the class env + for field in ast_class.fields.iter() { + let field_type = self.resolve_type_annotation(&field.member_type, env); + match class_env.define( + &field.name, + SymbolKind::make_member_variable( + field.name.clone(), + field_type, + field.reassignable, + field.is_static, + field.phase, + field.access_modifier, + None, + ), + StatementIdx::Top, + ) { + Err(type_error) => { + self.type_error(type_error); } + _ => {} + }; + } - // Replace the dummy interface environment with the real one before type checking the methods - interface_type.as_mut_interface().unwrap().env = interface_env; - } - StmtKind::Struct { name, extends, fields } => { - // Note: structs don't have a parent environment, instead they flatten their parent's members into the struct's env. - // If we encounter an existing member with the same name and type we skip it, if the types are different we - // fail type checking. - - // Create an environment for the struct - let mut struct_env = SymbolEnv::new(None, self.types.void(), false, false, Phase::Independent, stmt.idx); - - // Add fields to the struct env - for field in fields.iter() { - let field_type = self.resolve_type_annotation(&field.member_type, env); - if field_type.is_mutable() { - self.spanned_error(&field.name, "Struct fields must have immutable types"); + // Add methods to the class env + for (method_name, method_def) in ast_class.methods.iter() { + self.add_method_to_class_env( + &method_def.signature, + env, + if method_def.is_static { None } else { Some(class_type) }, + method_def.access_modifier, + &mut class_env, + method_name, + ); + } + + // Add the constructor to the class env + let init_symb = Symbol { + name: CLASS_INIT_NAME.into(), + span: ast_class.initializer.span.clone(), + }; + + self.add_method_to_class_env( + &ast_class.initializer.signature, + env, + None, + ast_class.initializer.access_modifier, + &mut class_env, + &init_symb, + ); + + let inflight_init_symb = Symbol { + name: CLASS_INFLIGHT_INIT_NAME.into(), + span: ast_class.inflight_initializer.span.clone(), + }; + + // Add the inflight initializer to the class env + self.add_method_to_class_env( + &ast_class.inflight_initializer.signature, + env, + Some(class_type), + ast_class.inflight_initializer.access_modifier, + &mut class_env, + &inflight_init_symb, + ); + + // Replace the dummy class environment with the real one before type checking the methods + if let Some(mut_class) = class_type.as_class_mut() { + mut_class.env = class_env; + } else { + panic!("Expected class type"); + } + + if let FunctionBody::Statements(scope) = &ast_class.inflight_initializer.body { + self.check_class_field_initialization(&scope, &ast_class.fields, Phase::Inflight); + }; + + // Type check constructor + self.type_check_method(class_type, &init_symb, env, stmt.idx, &ast_class.initializer); + + // Verify if all fields of a class/resource are initialized in the initializer. + let init_statements = match &ast_class.initializer.body { + FunctionBody::Statements(s) => s, + FunctionBody::External(_) => panic!("init cannot be extern"), + }; + + self.check_class_field_initialization(&init_statements, &ast_class.fields, Phase::Preflight); + + // Type check the inflight initializer + self.type_check_method( + class_type, + &inflight_init_symb, + env, + stmt.idx, + &ast_class.inflight_initializer, + ); + + // TODO: handle member/method overrides in our env based on whatever rules we define in our spec + // https://github.com/winglang/wing/issues/1124 + + // Type check methods + for (method_name, method_def) in ast_class.methods.iter() { + self.type_check_method(class_type, method_name, env, stmt.idx, method_def); + } + + // Check that the class satisfies all of its interfaces + for interface_type in impl_interfaces.iter() { + let interface_type = match interface_type.as_interface() { + Some(t) => t, + None => { + // No need to error here, it will be caught when `impl_interaces` was created + continue; + } + }; + + // Check all methods are implemented + for (method_name, method_type) in interface_type.methods(true) { + if let Some(symbol) = &mut class_type + .as_class_mut() + .unwrap() + .env + .lookup(&method_name.as_str().into(), None) + { + let class_method_var = symbol.as_variable().expect("Expected method to be a variable"); + let class_method_type = class_method_var.type_; + self.validate_type(class_method_type, method_type, &ast_class.name); + // Make sure the method is public (interface methods must be public) + if class_method_var.access_modifier != AccessModifier::Public { + self.spanned_error( + &class_method_var.name, + format!( + "Method \"{method_name}\" is {} in \"{}\" but it's an implementation of \"{interface_type}\". Interface members must be public.", + class_method_var.access_modifier, ast_class.name, + ), + ); } - match struct_env.define( - &field.name, - SymbolKind::make_member_variable(field.name.clone(), field_type, false, false, Phase::Independent, None), - StatementIdx::Top, - ) { - Err(type_error) => { - self.type_error(type_error); - } - _ => {} - }; + } else { + self.spanned_error( + &ast_class.name, + format!( + "Class \"{}\" does not implement method \"{}\" of interface \"{}\"", + &ast_class.name, method_name, interface_type.name.name + ), + ); } + } + } + self.ctx.pop_class(); + } - // Add members from the structs parents - let extends_types = extends - .iter() - .filter_map(|ext| { - let t = self - .resolve_user_defined_type(ext, env, stmt.idx) - .unwrap_or_else(|e| self.type_error(e)); - if t.as_struct().is_some() { - Some(t) - } else { - self.spanned_error(ext, format!("Expected a struct, found type \"{}\"", t)); - None - } - }) - .collect::>(); + fn type_check_return(&mut self, env: &mut SymbolEnv, stmt: &Stmt, exp: &Option) { + // Type check the return expression + let return_type = exp.as_ref().map(|exp| (self.type_check_exp(exp, env).0, exp)); - if let Err(e) = add_parent_members_to_struct_env(&extends_types, name, &mut struct_env) { - self.type_error(e); + // Make sure we're inside a function + if self.ctx.current_function().is_none() { + self.spanned_error(stmt, "Return statement outside of function cannot return a value"); + return; + }; + + let cur_func_env = *self.ctx.current_function_env().expect("a function env"); + let SymbolEnvKind::Function{sig: cur_func_type, ..} = cur_func_env.kind else { + panic!("Expected function env"); + }; + let mut function_ret_type = cur_func_type.as_function_sig().expect("a function_type").return_type; + + let return_type_inferred = self.update_known_inferences(&mut function_ret_type, &stmt.span); + + if let Some((return_type, return_expression)) = return_type { + if !function_ret_type.is_void() { + self.validate_type(return_type, function_ret_type, return_expression); + } else { + if return_type_inferred { + self.spanned_error(stmt, "Unexpected return value from void function"); + } else { + self.spanned_error( + stmt, + "Unexpected return value from void function. Return type annotations are required for methods.", + ); } - match env.define( - name, - SymbolKind::Type(self.types.add_type(Type::Struct(Struct { - name: name.clone(), - extends: extends_types, - env: struct_env, - docs: Docs::default(), - }))), - StatementIdx::Top, - ) { - Err(type_error) => { - self.type_error(type_error); - } - _ => {} - }; } - StmtKind::Enum { name, values } => { - let enum_type_ref = self.types.add_type(Type::Enum(Enum { - name: name.clone(), - values: values.clone(), - docs: Default::default(), - })); + } else { + self.validate_type(self.types.void(), function_ret_type, stmt); + } - match env.define(name, SymbolKind::Type(enum_type_ref), StatementIdx::Top) { - Err(type_error) => { - self.type_error(type_error); - } - _ => {} - }; + if let Type::Json(d) = &mut *function_ret_type { + // We do not have the required analysis to know the type of the Json data after return + if d.is_some() { + d.take(); } - StmtKind::TryCatch { - try_statements, - catch_block, - finally_statements, - } => { - // Create a new environment for the try block - let try_env = self.types.add_symbol_env(SymbolEnv::new( - Some(env.get_ref()), - env.return_type, - false, - false, - env.phase, - stmt.idx, - )); - self.types.set_scope_env(try_statements, try_env); - self.inner_scopes.push(try_statements); - - // Create a new environment for the catch block - if let Some(catch_block) = catch_block { - let mut catch_env = self.types.add_symbol_env(SymbolEnv::new( - Some(env.get_ref()), - env.return_type, - false, - false, - env.phase, - stmt.idx, - )); + } + } - // Add the exception variable to the catch block - if let Some(exception_var) = &catch_block.exception_var { - match catch_env.define( - exception_var, - SymbolKind::make_free_variable(exception_var.clone(), self.types.string(), false, env.phase), - StatementIdx::Top, - ) { - Err(type_error) => { - self.type_error(type_error); - } - _ => {} - } - } - self.types.set_scope_env(&catch_block.statements, catch_env); - self.inner_scopes.push(&catch_block.statements); + fn type_check_throw(&mut self, exp: &Expr, env: &mut SymbolEnv) { + let (exp_type, _) = self.type_check_exp(exp, env); + self.validate_type(exp_type, self.types.string(), exp); + } + + fn type_check_bring(&mut self, source: &BringSource, identifier: &Option, stmt: &Stmt, env: &mut SymbolEnv) { + let library_name: String; + let namespace_filter: Vec; + let alias: &Symbol; + match &source { + BringSource::BuiltinModule(name) => { + if WINGSDK_BRINGABLE_MODULES.contains(&name.name.as_str()) { + library_name = WINGSDK_ASSEMBLY_NAME.to_string(); + namespace_filter = vec![name.name.clone()]; + alias = identifier.as_ref().unwrap_or(&name); + } else if name.name.as_str() == WINGSDK_STD_MODULE { + self.spanned_error(stmt, format!("Redundant bring of \"{}\"", WINGSDK_STD_MODULE)); + return; + } else { + self.spanned_error(stmt, format!("\"{}\" is not a built-in module", name)); + return; } - - // Create a new environment for the finally block - if let Some(finally_statements) = finally_statements { - let finally_env = self.types.add_symbol_env(SymbolEnv::new( - Some(env.get_ref()), - env.return_type, - false, - false, - env.phase, - stmt.idx, - )); - self.types.set_scope_env(finally_statements, finally_env); - self.inner_scopes.push(finally_statements); + } + BringSource::JsiiModule(name) => { + library_name = name.name.to_string(); + // no namespace filter (we only support importing entire libraries at the moment) + namespace_filter = vec![]; + alias = identifier.as_ref().unwrap(); + } + BringSource::WingFile(name) => { + let brought_env = match self.types.source_file_envs.get(Utf8Path::new(&name.name)) { + Some(env) => *env, + None => { + self.spanned_error( + stmt, + format!("Could not type check \"{}\" due to cyclic bring statements", name), + ); + return; + } + }; + let ns = self.types.add_namespace(Namespace { + name: name.name.to_string(), + env: SymbolEnv::new(Some(brought_env.get_ref()), SymbolEnvKind::Scope, brought_env.phase, 0), + loaded: true, + }); + if let Err(e) = env.define( + identifier.as_ref().unwrap(), + SymbolKind::Namespace(ns), + StatementIdx::Top, + ) { + self.type_error(e); } + return; } - StmtKind::CompilerDebugEnv => { - println!("[symbol environment at {}]", stmt.span); - println!("{}", env); + } + self.add_module_to_env(env, library_name, namespace_filter, alias, Some(&stmt)); + // library_name is the name of the library we are importing from the JSII world + // namespace_filter describes what types we are importing from the library + // e.g. [] means we are importing everything from `mylib` + // e.g. ["ns1", "ns2"] means we are importing everything from `mylib.ns1.ns2` + // alias is the symbol we are giving to the imported library or namespace + } + + fn type_check_assignment(&mut self, kind: &AssignmentKind, value: &Expr, env: &mut SymbolEnv, variable: &Reference) { + let (exp_type, _) = self.type_check_exp(value, env); + + // TODO: we need to verify that if this variable is defined in a parent environment (i.e. + // being captured) it cannot be reassigned: https://github.com/winglang/wing/issues/3069 + + let (var, var_phase) = self.resolve_reference(&variable, env); + + if !var.type_.is_unresolved() && !var.reassignable { + self.spanned_error(variable, "Variable is not reassignable".to_string()); + } else if var_phase == Phase::Preflight && env.phase == Phase::Inflight { + self.spanned_error(variable, "Variable cannot be reassigned from inflight".to_string()); + } + + if matches!(kind, AssignmentKind::AssignIncr | AssignmentKind::AssignDecr) { + self.validate_type(exp_type, self.types.number(), value); + self.validate_type(var.type_, self.types.number(), variable); + } + + self.validate_type(exp_type, var.type_, value); + } + + fn type_check_if( + &mut self, + condition: &Expr, + statements: &Scope, + env: &mut SymbolEnv, + elif_statements: &Vec, + else_statements: &Option, + ) { + self.type_check_if_statement(condition, statements, env); + + for elif_scope in elif_statements { + self.type_check_if_statement(&elif_scope.condition, &elif_scope.statements, env); + } + + if let Some(else_scope) = else_statements { + let else_scope_env = self.types.add_symbol_env(SymbolEnv::new( + Some(env.get_ref()), + SymbolEnvKind::Scope, + env.phase, + self.ctx.current_stmt_idx(), + )); + self.types.set_scope_env(else_scope, else_scope_env); + self.inner_scopes.push((else_scope, self.ctx.clone())); + } + } + + fn type_check_iflet(&mut self, env: &mut SymbolEnv, iflet: &IfLet) { + self.type_check_if_let_statement( + &iflet.value, + &iflet.statements, + &iflet.reassignable, + &iflet.var_name, + env, + ); + + for elif_scope in &iflet.elif_statements { + self.type_check_if_let_statement( + &elif_scope.value, + &elif_scope.statements, + &elif_scope.reassignable, + &elif_scope.var_name, + env, + ); + } + + if let Some(else_scope) = &iflet.else_statements { + let else_scope_env = self.types.add_symbol_env(SymbolEnv::new( + Some(env.get_ref()), + SymbolEnvKind::Scope, + env.phase, + self.ctx.current_stmt_idx(), + )); + self.types.set_scope_env(else_scope, else_scope_env); + self.inner_scopes.push((else_scope, self.ctx.clone())); + } + } + + fn type_check_while(&mut self, condition: &Expr, env: &mut SymbolEnv, statements: &Scope) { + let (cond_type, _) = self.type_check_exp(condition, env); + self.validate_type(cond_type, self.types.bool(), condition); + + let scope_env = self.types.add_symbol_env(SymbolEnv::new( + Some(env.get_ref()), + SymbolEnvKind::Scope, + env.phase, + self.ctx.current_stmt_idx(), + )); + self.types.set_scope_env(statements, scope_env); + + self.inner_scopes.push((statements, self.ctx.clone())); + } + + fn type_check_for_loop(&mut self, iterable: &Expr, env: &mut SymbolEnv, iterator: &Symbol, statements: &Scope) { + // TODO: Expression must be iterable + let (exp_type, _) = self.type_check_exp(iterable, env); + + if !exp_type.is_iterable() { + self.spanned_error(iterable, format!("Unable to iterate over \"{}\"", &exp_type)); + } + + let iterator_type = match &*exp_type { + // These are builtin iterables that have a clear/direct iterable type + Type::Array(t) => *t, + Type::Set(t) => *t, + Type::MutArray(t) => *t, + Type::MutSet(t) => *t, + Type::Anything => exp_type, + _t => self.types.error(), + }; + + let mut scope_env = self.types.add_symbol_env(SymbolEnv::new( + Some(env.get_ref()), + SymbolEnvKind::Scope, + env.phase, + self.ctx.current_stmt_idx(), + )); + match scope_env.define( + &iterator, + SymbolKind::make_free_variable(iterator.clone(), iterator_type, false, env.phase), + StatementIdx::Top, + ) { + Err(type_error) => { + self.type_error(type_error); } - StmtKind::SuperConstructor { arg_list } => { - self.type_check_arg_list(arg_list, env); + _ => {} + }; + self.types.set_scope_env(statements, scope_env); + + self.inner_scopes.push((statements, self.ctx.clone())); + } + + fn type_check_let( + &mut self, + type_: &Option, + env: &mut SymbolEnv, + initial_value: &Expr, + var_name: &Symbol, + reassignable: &bool, + ) { + let explicit_type = type_.as_ref().map(|t| self.resolve_type_annotation(t, env)); + let (mut inferred_type, _) = self.type_check_exp(initial_value, env); + if inferred_type.is_void() { + self.spanned_error( + var_name, + format!("Cannot assign expression of type \"{}\" to a variable", inferred_type), + ); + } + if explicit_type.is_none() && inferred_type.is_nil() { + self.spanned_error( + initial_value, + "Cannot assign nil value to variables without explicit optional type", + ); + } + if let Some(explicit_type) = explicit_type { + self.validate_type(inferred_type, explicit_type, initial_value); + let final_type = if !*reassignable && explicit_type.is_json() && inferred_type.is_json() { + // If both types are Json, use the inferred type in case it has more information + inferred_type + } else { + explicit_type + }; + match env.define( + var_name, + SymbolKind::make_free_variable(var_name.clone(), final_type, *reassignable, env.phase), + StatementIdx::Index(self.ctx.current_stmt_idx()), + ) { + Err(type_error) => { + self.type_error(type_error); + } + _ => {} + }; + } else { + if *reassignable && inferred_type.is_json() { + if let Type::Json(Some(_)) = *inferred_type { + // We do not have the required analysis to know the type of the Json data after reassignment + inferred_type = self.types.json(); + } } + match env.define( + var_name, + SymbolKind::make_free_variable(var_name.clone(), inferred_type, *reassignable, env.phase), + StatementIdx::Index(self.ctx.current_stmt_idx()), + ) { + Err(type_error) => { + self.type_error(type_error); + } + _ => {} + }; } } @@ -3698,7 +3827,6 @@ impl<'a> TypeChecker<'a> { statements: &Scope, reassignable: &bool, var_name: &Symbol, - stmt: &Stmt, env: &mut SymbolEnv, ) { let (mut cond_type, _) = self.type_check_exp(value, env); @@ -3728,11 +3856,9 @@ impl<'a> TypeChecker<'a> { let mut stmt_env = self.types.add_symbol_env(SymbolEnv::new( Some(env.get_ref()), - env.return_type, - false, - false, + SymbolEnvKind::Scope, env.phase, - stmt.idx, + self.ctx.current_stmt_idx(), )); // Add the variable to if block scope @@ -3748,92 +3874,80 @@ impl<'a> TypeChecker<'a> { } self.types.set_scope_env(statements, stmt_env); - self.inner_scopes.push(statements); + self.inner_scopes.push((statements, self.ctx.clone())); } - fn type_check_if_statement(&mut self, condition: &Expr, statements: &Scope, stmt: &Stmt, env: &mut SymbolEnv) { + fn type_check_if_statement(&mut self, condition: &Expr, statements: &Scope, env: &mut SymbolEnv) { let (cond_type, _) = self.type_check_exp(condition, env); self.validate_type(cond_type, self.types.bool(), condition); let if_scope_env = self.types.add_symbol_env(SymbolEnv::new( Some(env.get_ref()), - env.return_type, - false, - false, + SymbolEnvKind::Scope, env.phase, - stmt.idx, + self.ctx.current_stmt_idx(), )); self.types.set_scope_env(statements, if_scope_env); - self.inner_scopes.push(statements); + self.inner_scopes.push((statements, self.ctx.clone())); } fn type_check_super_constructor_against_parent_initializer( &mut self, - scope: &Scope, - class_type: TypeRef, - class_env: &mut SymbolEnv, - init_name: &str, + super_constructor_call: &Stmt, + arg_list: &ArgList, + env: &mut SymbolEnv, ) { - if &scope.statements.len() >= &1 { - match &scope.statements[0].kind { - StmtKind::SuperConstructor { arg_list } => { - if let Some(parent_class) = &class_type.as_class().unwrap().parent { - let parent_initializer = parent_class - .as_class() - .unwrap() - .methods(false) - .filter(|(name, _type)| name == init_name) - .collect_vec()[0] - .1; - - let class_initializer = &class_type - .as_class() - .unwrap() - .methods(true) - .filter(|(name, _type)| name == init_name) - .collect_vec()[0] - .1; - - // Create a temp init environment to use for typechecking args - let init_env = &mut SymbolEnv::new( - Some(class_env.get_ref()), - self.types.void(), - true, - true, - class_env.phase, - scope.statements[0].idx, - ); + let arg_list_types = self.type_check_arg_list(arg_list, env); - // add the initializer args to the init_env - for arg in class_initializer.as_function_sig().unwrap().parameters.iter() { - let sym = Symbol { - name: arg.name.clone(), - span: scope.statements[0].span.clone(), - }; - match init_env.define( - &sym, - SymbolKind::make_free_variable(sym.clone(), arg.typeref, false, init_env.phase), - StatementIdx::Top, - ) { - Err(type_error) => { - self.type_error(type_error); - } - _ => {} - }; - } + // Verify we're inside a class + let class_type = if let Some(current_clas) = self.ctx.current_class().cloned() { + self + .resolve_user_defined_type(¤t_clas, env, self.ctx.current_stmt_idx()) + .expect("current class type to be defined") + } else { + self.spanned_error( + super_constructor_call, + "Call to super constructor can only be done from within a class initializer", + ); + return; + }; - let arg_list_types = self.type_check_arg_list(&arg_list, init_env); - self.type_check_arg_list_against_function_sig( - &arg_list, - parent_initializer.as_function_sig().unwrap(), - &scope.statements[0], - arg_list_types, - ); - } - } - _ => {} // No super no problem - } + let init_name = if env.phase == Phase::Inflight { + CLASS_INFLIGHT_INIT_NAME + } else { + CLASS_INIT_NAME + }; + + // Verify we're inside the class's initializer + let (method_name, _) = self.ctx.current_method().expect("to be inside a method"); + if method_name.name != init_name { + self.spanned_error( + super_constructor_call, + "Call to super constructor can only be done from within a class initializer", + ); + return; + } + + // Verify the class has a parent class + let Some(parent_class) = &class_type.as_class().expect("class type to be a class").parent else { + self.spanned_error(super_constructor_call, format!("Class \"{class_type}\" does not have a parent class")); + return; }; + + let parent_initializer = parent_class + .as_class() + .unwrap() + .methods(false) + .filter(|(name, _type)| name == init_name) + .collect_vec()[0] + .1; + + self.type_check_arg_list_against_function_sig( + &arg_list, + parent_initializer.as_function_sig().unwrap(), + super_constructor_call, + arg_list_types, + ); } /// Validate if the fields of a class are initialized in the constructor (init) according to the given phase. @@ -3910,9 +4024,10 @@ impl<'a> TypeChecker<'a> { let is_init = method_name.name == CLASS_INIT_NAME || method_name.name == CLASS_INFLIGHT_INIT_NAME; let mut method_env = self.types.add_symbol_env(SymbolEnv::new( Some(parent_env.get_ref()), - method_sig.return_type, - is_init, - true, + SymbolEnvKind::Function { + is_init, + sig: method_type, + }, method_sig.phase, statement_idx, )); @@ -3931,19 +4046,21 @@ impl<'a> TypeChecker<'a> { } self.add_arguments_to_env(&method_def.signature.parameters, method_sig, &mut method_env); - if let FunctionBody::Statements(scope) = &method_def.body { - self.types.set_scope_env(scope, method_env); - self.inner_scopes.push(scope); - } + self.with_function_def(Some(method_name), &method_def.signature, method_env, |tc| { + if let FunctionBody::Statements(scope) = &method_def.body { + tc.types.set_scope_env(scope, method_env); + tc.inner_scopes.push((scope, tc.ctx.clone())); + } - if let FunctionBody::External(_) = &method_def.body { - if !method_def.is_static { - self.spanned_error( - method_name, - "Extern methods must be declared \"static\" (they cannot access instance members)", - ); + if let FunctionBody::External(_) = &method_def.body { + if !method_def.is_static { + tc.spanned_error( + method_name, + "Extern methods must be declared \"static\" (they cannot access instance members)", + ); + } } - } + }); } fn add_method_to_class_env( @@ -3951,6 +4068,7 @@ impl<'a> TypeChecker<'a> { method_sig: &ast::FunctionSignature, env: &mut SymbolEnv, instance_type: Option, + access_modifier: AccessModifier, class_env: &mut SymbolEnv, method_name: &Symbol, ) { @@ -3961,6 +4079,40 @@ impl<'a> TypeChecker<'a> { .expect("Expected method type to be a function") .this_type = instance_type; + // If this method is overriding a parent method, check access modifiers allow it, note this is only relevant for instance methods + if instance_type.is_some() { + if let Some(parent_type_env) = class_env.parent { + if let LookupResult::Found(SymbolKind::Variable(var), li) = parent_type_env.lookup_ext(method_name, None) { + let SymbolEnvKind::Type(method_defined_in) = li.env.kind else { + panic!("Expected env to be a type env"); + }; + // If parent method is private we don't allow overriding + if var.access_modifier == AccessModifier::Private { + self.spanned_error( + method_name, + format!("Cannot override private method \"{method_name}\" of \"{method_defined_in}\""), + ); + } else { + // For non private methods, we only allow overriding if the access modifier is the same or more permissive: + // - public can override public or protected + // - protected can only override protected + if !(access_modifier == AccessModifier::Public + && matches!(var.access_modifier, AccessModifier::Public | AccessModifier::Protected) + || access_modifier == AccessModifier::Protected && var.access_modifier == AccessModifier::Protected) + { + self.spanned_error( + method_name, + format!( + "Cannot override {} method \"{method_name}\" of \"{method_defined_in}\" with a {access_modifier} method", + var.access_modifier + ), + ); + } + } + } + } + } + match class_env.define( method_name, SymbolKind::make_member_variable( @@ -3969,6 +4121,7 @@ impl<'a> TypeChecker<'a> { false, instance_type.is_none(), method_sig.phase, + access_modifier, None, ), StatementIdx::Top, @@ -4144,17 +4297,10 @@ impl<'a> TypeChecker<'a> { types_map.insert(format!("{o}"), (*o, *n)); } - let new_env = SymbolEnv::new( - None, - original_type_class.env.return_type, - false, - false, - Phase::Independent, - 0, - ); + let dummy_env = SymbolEnv::new(None, SymbolEnvKind::Type(original_type), Phase::Independent, 0); let tt = Type::Class(Class { name: original_type_class.name.clone(), - env: new_env, + env: dummy_env, fqn: Some(original_fqn.to_string()), parent: original_type_class.parent, implements: original_type_class.implements.clone(), @@ -4168,8 +4314,17 @@ impl<'a> TypeChecker<'a> { // TODO: here we add a new type regardless whether we already "hydrated" `original_type` with these `type_params`. Cache! let mut new_type = self.types.add_type(tt); + let new_env = SymbolEnv::new(None, SymbolEnvKind::Type(new_type), Phase::Independent, 0); + let new_type_class = new_type.as_class_mut().unwrap(); + // Update the class's env to point to the new env + new_type_class.env = new_env; + + let SymbolEnvKind::Type(new_type) = new_type_class.env.kind else { // TODO: Ugly hack to get non mut ref of new_type so we can use it + panic!("Expected class env to be a type"); + }; + // Add symbols from original type to new type // Note: this is currently limited to top-level function signatures and fields for (name, symbol, _) in original_type_class.env.iter(true) { @@ -4180,6 +4335,7 @@ impl<'a> TypeChecker<'a> { reassignable, phase: flight, kind, + access_modifier, docs: _, }) => { // Replace type params in function signatures @@ -4221,6 +4377,7 @@ impl<'a> TypeChecker<'a> { *reassignable, matches!(kind, VariableKind::StaticMember), *flight, + *access_modifier, None, ), StatementIdx::Top, @@ -4242,6 +4399,7 @@ impl<'a> TypeChecker<'a> { *reassignable, matches!(kind, VariableKind::StaticMember), *flight, + *access_modifier, None, ), StatementIdx::Top, @@ -4411,7 +4569,7 @@ impl<'a> TypeChecker<'a> { // then resolve a class named "Util" within it. This will basically be equivalent to the // `foo.Bar.baz()` case (where `baz()`) is a static method of class `Bar`. if base_udt.fields.is_empty() { - let result = env.lookup_nested_str(&base_udt.full_path_str(), Some(self.statement_idx)); + let result = env.lookup_nested_str(&base_udt.full_path_str(), Some(self.ctx.current_stmt_idx())); if let LookupResult::Found(symbol_kind, _) = result { if let SymbolKind::Namespace(_) = symbol_kind { let mut new_udt = base_udt.clone(); @@ -4421,7 +4579,7 @@ impl<'a> TypeChecker<'a> { }); return self - .resolve_user_defined_type(&new_udt, env, self.statement_idx) + .resolve_user_defined_type(&new_udt, env, self.ctx.current_stmt_idx()) .ok() .map(|_| new_udt); } @@ -4429,7 +4587,7 @@ impl<'a> TypeChecker<'a> { } self - .resolve_user_defined_type(&base_udt, env, self.statement_idx) + .resolve_user_defined_type(&base_udt, env, self.ctx.current_stmt_idx()) .ok() .map(|_| base_udt) } @@ -4437,7 +4595,7 @@ impl<'a> TypeChecker<'a> { fn resolve_reference(&mut self, reference: &Reference, env: &mut SymbolEnv) -> (VariableInfo, Phase) { match reference { Reference::Identifier(symbol) => { - let lookup_res = env.lookup_ext_mut(symbol, Some(self.statement_idx)); + let lookup_res = env.lookup_ext_mut(symbol, Some(self.ctx.current_stmt_idx())); if let LookupResultMut::Found(var, _) = lookup_res { if let Some(var) = var.as_variable_mut() { let phase = var.phase; @@ -4490,7 +4648,7 @@ impl<'a> TypeChecker<'a> { let mut force_reassignable = false; if let ExprKind::Reference(Reference::Identifier(symb)) = &object.kind { if symb.name == "this" { - if let LookupResult::Found(kind, info) = env.lookup_ext(&symb, Some(self.statement_idx)) { + if let LookupResult::Found(kind, info) = env.lookup_ext(&symb, Some(self.ctx.current_stmt_idx())) { // `this` reserved symbol should always be a variable assert!(matches!(kind, SymbolKind::Variable(_))); force_reassignable = info.init; @@ -4540,7 +4698,7 @@ impl<'a> TypeChecker<'a> { } Reference::TypeMember { type_name, property } => { let type_ = self - .resolve_user_defined_type(type_name, env, self.statement_idx) + .resolve_user_defined_type(type_name, env, self.ctx.current_stmt_idx()) .unwrap_or_else(|e| self.type_error(e)); match *type_ { Type::Enum(ref e) => { @@ -4552,6 +4710,7 @@ impl<'a> TypeChecker<'a> { type_, reassignable: false, phase: Phase::Independent, + access_modifier: AccessModifier::Public, docs: None, }, Phase::Independent, @@ -4584,34 +4743,27 @@ impl<'a> TypeChecker<'a> { } let new_class = self.hydrate_class_type_arguments(env, WINGSDK_STRUCT, vec![type_]); - let v = self.get_property_from_class_like(new_class.as_class().unwrap(), property, true); + let v = self.get_property_from_class_like(new_class.as_class().unwrap(), property, true, env); (v, Phase::Independent) } - Type::Class(ref c) => match c.env.lookup(&property, None) { - Some(SymbolKind::Variable(v)) => { - if let VariableKind::StaticMember = v.kind { - (v.clone(), v.phase) - } else { - self.spanned_error_with_var( - property, - format!( - "Class \"{}\" contains a member \"{}\" but it is not static", - type_, property.name - ), - ) - } + Type::Class(ref c) => { + let v = self.get_property_from_class_like(c, property, true, env); + if matches!(v.kind, VariableKind::InstanceMember) { + return self.spanned_error_with_var( + property, + format!("Class \"{c}\" contains a member \"{property}\" but it is not static"), + ); } - _ => self.spanned_error_with_var( - property, - format!("No member \"{}\" in class \"{}\"", property.name, type_), - ), - }, + (v.clone(), v.phase) + } _ => self.spanned_error_with_var(property, format!("\"{}\" not a valid reference", reference)), } } } } + /// Check if the given property on the given type with the given access modifier can be accessed from the current context + fn resolve_variable_from_instance_type( &mut self, instance_type: TypeRef, @@ -4622,41 +4774,42 @@ impl<'a> TypeChecker<'a> { ) -> VariableInfo { match *instance_type { Type::Optional(t) => self.resolve_variable_from_instance_type(t, property, env, _object), - Type::Class(ref class) => self.get_property_from_class_like(class, property, false), - Type::Interface(ref interface) => self.get_property_from_class_like(interface, property, false), + Type::Class(ref class) => self.get_property_from_class_like(class, property, false, env), + Type::Interface(ref interface) => self.get_property_from_class_like(interface, property, false, env), Type::Anything => VariableInfo { name: property.clone(), type_: instance_type, reassignable: false, phase: env.phase, kind: VariableKind::InstanceMember, + access_modifier: AccessModifier::Public, docs: None, }, // Lookup wingsdk std types, hydrating generics if necessary Type::Array(t) => { let new_class = self.hydrate_class_type_arguments(env, WINGSDK_ARRAY, vec![t]); - self.get_property_from_class_like(new_class.as_class().unwrap(), property, false) + self.get_property_from_class_like(new_class.as_class().unwrap(), property, false, env) } Type::MutArray(t) => { let new_class = self.hydrate_class_type_arguments(env, WINGSDK_MUT_ARRAY, vec![t]); - self.get_property_from_class_like(new_class.as_class().unwrap(), property, false) + self.get_property_from_class_like(new_class.as_class().unwrap(), property, false, env) } Type::Set(t) => { let new_class = self.hydrate_class_type_arguments(env, WINGSDK_SET, vec![t]); - self.get_property_from_class_like(new_class.as_class().unwrap(), property, false) + self.get_property_from_class_like(new_class.as_class().unwrap(), property, false, env) } Type::MutSet(t) => { let new_class = self.hydrate_class_type_arguments(env, WINGSDK_MUT_SET, vec![t]); - self.get_property_from_class_like(new_class.as_class().unwrap(), property, false) + self.get_property_from_class_like(new_class.as_class().unwrap(), property, false, env) } Type::Map(t) => { let new_class = self.hydrate_class_type_arguments(env, WINGSDK_MAP, vec![t]); - self.get_property_from_class_like(new_class.as_class().unwrap(), property, false) + self.get_property_from_class_like(new_class.as_class().unwrap(), property, false, env) } Type::MutMap(t) => { let new_class = self.hydrate_class_type_arguments(env, WINGSDK_MUT_MAP, vec![t]); - self.get_property_from_class_like(new_class.as_class().unwrap(), property, false) + self.get_property_from_class_like(new_class.as_class().unwrap(), property, false, env) } Type::Json(_) => self.get_property_from_class_like( env @@ -4669,6 +4822,7 @@ impl<'a> TypeChecker<'a> { .unwrap(), property, false, + env, ), Type::MutJson => self.get_property_from_class_like( env @@ -4681,6 +4835,7 @@ impl<'a> TypeChecker<'a> { .unwrap(), property, false, + env, ), Type::String => self.get_property_from_class_like( env @@ -4693,6 +4848,7 @@ impl<'a> TypeChecker<'a> { .unwrap(), property, false, + env, ), Type::Duration => self.get_property_from_class_like( env @@ -4705,8 +4861,9 @@ impl<'a> TypeChecker<'a> { .unwrap(), property, false, + env, ), - Type::Struct(ref s) => self.get_property_from_class_like(s, property, true), + Type::Struct(ref s) => self.get_property_from_class_like(s, property, true, env), _ => { self .spanned_error_with_var(property, "Property not found".to_string()) @@ -4715,20 +4872,63 @@ impl<'a> TypeChecker<'a> { } } - /// Get's the type of an instance variable in a class + /// Get's the type of an instance variable in a class and the type in which it's defined fn get_property_from_class_like( &mut self, class: &impl ClassLike, property: &Symbol, allow_static: bool, + env: &SymbolEnv, ) -> VariableInfo { let lookup_res = class.get_env().lookup_ext(property, None); - if let LookupResult::Found(field, _) = lookup_res { + if let LookupResult::Found(field, lookup_info) = lookup_res { let var = field.as_variable().expect("Expected property to be a variable"); + + // Determine the access type of the property + // Lookup the property in the class env to find out in which class (perhaps an ancestor) it was defined + let SymbolEnvKind::Type(property_defined_in) = lookup_info.env.kind else { + panic!("Expected env to be a type env"); + }; + // Check if the class in which the property is defined is one of the classes we're currently nested in + let mut private_access = false; + let mut protected_access = false; + for current_class in self.ctx.current_class_nesting() { + let current_class_type = self + .resolve_user_defined_type(¤t_class, env, self.ctx.current_stmt_idx()) + .unwrap(); + private_access = current_class_type.is_same_type_as(&property_defined_in); + protected_access = private_access || current_class_type.is_strict_subtype_of(&property_defined_in); + if private_access { + break; + } + } + + // Compare the access type with what's allowed + match var.access_modifier { + AccessModifier::Private => { + if !private_access { + self.spanned_error( + property, + format!("Cannot access private member \"{property}\" of \"{class}\""), + ); + } + } + AccessModifier::Protected => { + if !protected_access { + self.spanned_error( + property, + format!("Cannot access protected member \"{property}\" of \"{class}\""), + ); + } + } + AccessModifier::Public => {} // keep this here to make sure we don't add a new access modifier without handling it here + } + if let VariableKind::StaticMember = var.kind { if allow_static { return var.clone(); } + self .spanned_error_with_var( property, @@ -4786,7 +4986,7 @@ impl<'a> TypeChecker<'a> { }; let parent_type = self - .resolve_user_defined_type(parent, env, self.statement_idx) + .resolve_user_defined_type(parent, env, self.ctx.current_stmt_idx()) .unwrap_or_else(|e| { self.type_error(e); self.types.error() @@ -4825,10 +5025,16 @@ impl<'a> TypeChecker<'a> { } } +impl VisitorWithContext for TypeChecker<'_> { + fn ctx(&mut self) -> &mut VisitContext { + &mut self.ctx + } +} + fn add_parent_members_to_struct_env( extends_types: &Vec, name: &Symbol, - struct_env: &mut SymbolEnv, + struct_env: &mut SymbolEnv, // TODO: pass the struct_type here and we'll extract the env ) -> Result<(), TypeError> { // Add members of all parents to the struct's environment for parent_type in extends_types.iter() { @@ -4870,7 +5076,15 @@ fn add_parent_members_to_struct_env( }; struct_env.define( &sym, - SymbolKind::make_member_variable(sym.clone(), member_type, false, false, struct_env.phase, None), + SymbolKind::make_member_variable( + sym.clone(), + member_type, + false, + false, + struct_env.phase, + AccessModifier::Public, + None, + ), StatementIdx::Top, )?; } @@ -4883,7 +5097,7 @@ fn add_parent_members_to_struct_env( fn add_parent_members_to_iface_env( extends_types: &Vec, name: &Symbol, - iface_env: &mut SymbolEnv, + iface_env: &mut SymbolEnv, // TODO: pass the iface_type here and we'll extract the env ) -> Result<(), TypeError> { // Add members of all parents to the interface's environment for parent_type in extends_types.iter() { @@ -4925,7 +5139,15 @@ fn add_parent_members_to_iface_env( }; iface_env.define( &sym, - SymbolKind::make_member_variable(sym.clone(), member_type, false, true, iface_env.phase, None), + SymbolKind::make_member_variable( + sym.clone(), + member_type, + false, + true, + iface_env.phase, + AccessModifier::Public, + None, + ), StatementIdx::Top, )?; } @@ -5035,7 +5257,7 @@ pub fn resolve_super_method(method: &Symbol, env: &SymbolEnv, types: &Types) -> } } else { Err(TypeError { - message: (if env.is_function { + message: (if matches!(env.kind, SymbolEnvKind::Function { .. }) { "Cannot call super method inside of a static method" } else { "\"super\" can only be used inside of classes" @@ -5102,7 +5324,9 @@ pub fn fully_qualify_std_type(type_: &str) -> String { match type_name { "Json" | "MutJson" | "MutArray" | "MutMap" | "MutSet" | "Array" | "Map" | "Set" | "String" | "Duration" - | "Boolean" | "Number" => format!("{WINGSDK_STD_MODULE}.{type_name}"), + | "Boolean" | "Number" => { + format!("{WINGSDK_STD_MODULE}.{type_name}") + } _ => type_name.to_string(), } } diff --git a/libs/wingc/src/type_check/jsii_importer.rs b/libs/wingc/src/type_check/jsii_importer.rs index bf4c801a96b..2672b62a5d2 100644 --- a/libs/wingc/src/type_check/jsii_importer.rs +++ b/libs/wingc/src/type_check/jsii_importer.rs @@ -1,13 +1,14 @@ use std::collections::BTreeMap; use crate::{ - ast::{Phase, Symbol}, + ast::{AccessModifier, Phase, Symbol}, debug, diagnostic::{WingLocation, WingSpan}, docs::Docs, type_check::{ - self, symbol_env::StatementIdx, Class, FunctionParameter, FunctionSignature, Interface, Struct, SymbolKind, Type, - TypeRef, Types, CLASS_INIT_NAME, + self, + symbol_env::{StatementIdx, SymbolEnvKind}, + Class, FunctionParameter, FunctionSignature, Interface, Struct, SymbolKind, Type, TypeRef, Types, CLASS_INIT_NAME, }, CONSTRUCT_BASE_CLASS, WINGSDK_ASSEMBLY_NAME, WINGSDK_DURATION, WINGSDK_JSON, WINGSDK_MUT_JSON, WINGSDK_RESOURCE, }; @@ -202,7 +203,7 @@ impl<'a> JsiiImporter<'a> { } else { let ns = self.wing_types.add_namespace(Namespace { name: type_name.assembly().to_string(), - env: SymbolEnv::new(None, self.wing_types.void(), false, false, Phase::Preflight, 0), + env: SymbolEnv::new(None, SymbolEnvKind::Scope, Phase::Preflight, 0), loaded: false, }); self @@ -244,7 +245,7 @@ impl<'a> JsiiImporter<'a> { } else { let ns = self.wing_types.add_namespace(Namespace { name: namespace_name.to_string(), - env: SymbolEnv::new(None, self.wing_types.void(), false, false, Phase::Preflight, 0), + env: SymbolEnv::new(None, SymbolEnvKind::Scope, Phase::Preflight, 0), loaded: false, }); parent_ns @@ -301,18 +302,12 @@ impl<'a> JsiiImporter<'a> { _ => false, }; - let mut iface_env = SymbolEnv::new( - None, - self.wing_types.void(), - false, - false, - if is_struct { - Phase::Independent - } else { - Phase::Preflight - }, - self.jsii_spec.import_statement_idx, - ); + let phase = if is_struct { + Phase::Independent + } else { + Phase::Preflight + }; + let new_type_symbol = Self::jsii_name_to_symbol(&type_name, &jsii_interface.location_in_module); let mut wing_type = match is_struct { true => self.wing_types.add_type(Type::Struct(Struct { @@ -323,9 +318,7 @@ impl<'a> JsiiImporter<'a> { // Will be replaced below env: SymbolEnv::new( None, - self.wing_types.void(), - false, - false, + SymbolEnvKind::Type(self.wing_types.void()), Phase::Independent, // structs are phase-independent self.jsii_spec.import_statement_idx, ), @@ -338,10 +331,8 @@ impl<'a> JsiiImporter<'a> { // Will be replaced below env: SymbolEnv::new( None, - self.wing_types.void(), - false, - false, - iface_env.phase, + SymbolEnvKind::Type(self.wing_types.void()), + phase, self.jsii_spec.import_statement_idx, ), })), @@ -360,6 +351,13 @@ impl<'a> JsiiImporter<'a> { } }; + let mut iface_env = SymbolEnv::new( + None, + SymbolEnvKind::Type(wing_type), + phase, + self.jsii_spec.import_statement_idx, + ); + self.add_members_to_class_env( jsii_interface, Phase::Inflight, @@ -495,6 +493,11 @@ impl<'a> JsiiImporter<'a> { .flatten(), })); let sym = Self::jsii_name_to_symbol(&m.name, &m.location_in_module); + let access_modifier = if matches!(m.protected, Some(true)) { + AccessModifier::Protected + } else { + AccessModifier::Public + }; class_env .define( &sym, @@ -504,6 +507,7 @@ impl<'a> JsiiImporter<'a> { false, is_static, member_phase, + access_modifier, Some(Docs::from(&m.docs)), ), StatementIdx::Top, @@ -533,6 +537,11 @@ impl<'a> JsiiImporter<'a> { }; let sym = Self::jsii_name_to_symbol(&p.name, &p.location_in_module); + let access_modifier = if matches!(p.protected, Some(true)) { + AccessModifier::Protected + } else { + AccessModifier::Public + }; class_env .define( &sym, @@ -542,6 +551,7 @@ impl<'a> JsiiImporter<'a> { !matches!(p.immutable, Some(true)), is_static, member_phase, + access_modifier, Some(Docs::from(&p.docs)), ), StatementIdx::Top, @@ -643,7 +653,7 @@ impl<'a> JsiiImporter<'a> { }; // Create environment representing this class, for now it'll be empty just so we can support referencing ourselves from the class definition. - let dummy_env = SymbolEnv::new(None, self.wing_types.void(), false, false, class_phase, 0); + let dummy_env = SymbolEnv::new(None, SymbolEnvKind::Type(self.wing_types.void()), class_phase, 0); let new_type_symbol = Self::jsii_name_to_symbol(type_name, &jsii_class.location_in_module); // Create the new resource/class type and add it to the current environment. // When adding the class methods below we'll be able to reference this type. @@ -697,7 +707,7 @@ impl<'a> JsiiImporter<'a> { }; // Create class's actual environment before we add properties and methods to it - let mut class_env = SymbolEnv::new(base_class_env, self.wing_types.void(), false, false, class_phase, 0); + let mut class_env = SymbolEnv::new(base_class_env, SymbolEnvKind::Type(new_type), class_phase, 0); // Add constructor to the class environment let jsii_initializer = jsii_class.initializer.as_ref(); @@ -751,6 +761,11 @@ impl<'a> JsiiImporter<'a> { docs: Docs::from(&initializer.docs), })); let sym = Self::jsii_name_to_symbol(CLASS_INIT_NAME, &initializer.location_in_module); + let access_modifier = if matches!(initializer.protected, Some(true)) { + AccessModifier::Protected + } else { + AccessModifier::Public + }; if let Err(e) = class_env.define( &sym, SymbolKind::make_member_variable( @@ -759,6 +774,7 @@ impl<'a> JsiiImporter<'a> { false, true, member_phase, + access_modifier, Some(Docs::from(&initializer.docs)), ), StatementIdx::Top, @@ -934,7 +950,7 @@ impl<'a> JsiiImporter<'a> { { let ns = self.wing_types.add_namespace(Namespace { name: assembly.name.clone(), - env: SymbolEnv::new(None, self.wing_types.void(), false, false, Phase::Preflight, 0), + env: SymbolEnv::new(None, SymbolEnvKind::Scope, Phase::Preflight, 0), loaded: false, }); self diff --git a/libs/wingc/src/type_check/symbol_env.rs b/libs/wingc/src/type_check/symbol_env.rs index ccdeb9aeb45..c23c54f5c62 100644 --- a/libs/wingc/src/type_check/symbol_env.rs +++ b/libs/wingc/src/type_check/symbol_env.rs @@ -22,17 +22,18 @@ pub struct SymbolEnv { pub(crate) symbol_map: BTreeMap, pub(crate) parent: Option, - // TODO: This doesn't make much sense in the context of the "environment" but I needed a way to propagate the return type of a function - // down the scopes. Think of a nicer way to do this. - pub return_type: TypeRef, + pub kind: SymbolEnvKind, - pub is_init: bool, - // Whether this scope is inside of a function - pub is_function: bool, pub phase: Phase, statement_idx: usize, } +pub enum SymbolEnvKind { + Scope, + Function { is_init: bool, sig: TypeRef }, + Type(TypeRef), +} + impl Display for SymbolEnv { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut level = 0; @@ -153,23 +154,18 @@ pub struct SymbolLookupInfo { } impl SymbolEnv { - pub fn new( - parent: Option, - return_type: TypeRef, - is_init: bool, - is_function: bool, - phase: Phase, - statement_idx: usize, - ) -> Self { - // assert that if the return type isn't void, then there is a parent environment - assert!(matches!(*return_type, Type::Void) || parent.is_some()); + pub fn new(parent: Option, kind: SymbolEnvKind, phase: Phase, statement_idx: usize) -> Self { + // Some sanity checks + // If parent is a type-environent this must be one too + assert!( + parent.is_none() + || matches!(parent, Some(parent) if matches!(parent.kind, SymbolEnvKind::Type(_)) == matches!(kind, SymbolEnvKind::Type(_))) + ); Self { symbol_map: BTreeMap::new(), parent, - return_type, - is_init, - is_function, + kind, phase, statement_idx, } @@ -270,7 +266,7 @@ impl SymbolEnv { kind, SymbolLookupInfo { phase: self.phase, - init: self.is_init, + init: matches!(self.kind, SymbolEnvKind::Function { is_init: true, .. }), env: get_ref, }, ); @@ -356,11 +352,7 @@ impl SymbolEnv { } pub fn is_in_function(&self) -> bool { - let mut curr_env = self.get_ref(); - while !curr_env.is_function && !curr_env.is_root() { - curr_env = curr_env.parent.unwrap(); - } - curr_env.is_function + matches!(self.kind, SymbolEnvKind::Function { .. }) } } @@ -396,7 +388,7 @@ impl<'a> Iterator for SymbolEnvIter<'a> { kind, SymbolLookupInfo { phase: self.curr_env.phase, - init: self.curr_env.is_init, + init: matches!(self.curr_env.kind, SymbolEnvKind::Function { is_init: true, .. }), env: self.curr_env.get_ref(), }, )) @@ -419,7 +411,10 @@ impl<'a> Iterator for SymbolEnvIter<'a> { mod tests { use crate::{ ast::{Phase, Symbol}, - type_check::{symbol_env::LookupResult, Namespace, SymbolKind, Types}, + type_check::{ + symbol_env::{LookupResult, SymbolEnvKind}, + Namespace, SymbolKind, Types, + }, }; use super::{StatementIdx, SymbolEnv}; @@ -431,13 +426,11 @@ mod tests { #[test] fn test_statement_idx_lookups() { let types = setup_types(); - let mut parent_env = SymbolEnv::new(None, types.void(), false, false, Phase::Independent, 0); + let mut parent_env = SymbolEnv::new(None, SymbolEnvKind::Scope, Phase::Independent, 0); let child_scope_idx = 10; let mut child_env = SymbolEnv::new( Some(parent_env.get_ref()), - types.void(), - false, - false, + SymbolEnvKind::Scope, crate::ast::Phase::Independent, child_scope_idx, ); @@ -551,12 +544,10 @@ mod tests { #[test] fn test_nested_lookups() { let mut types = setup_types(); - let mut parent_env = SymbolEnv::new(None, types.void(), false, false, Phase::Independent, 0); + let mut parent_env = SymbolEnv::new(None, SymbolEnvKind::Scope, Phase::Independent, 0); let child_env = SymbolEnv::new( Some(parent_env.get_ref()), - types.void(), - false, - false, + SymbolEnvKind::Scope, crate::ast::Phase::Independent, 0, ); @@ -564,19 +555,12 @@ mod tests { // Create namespaces let ns1 = types.add_namespace(Namespace { name: "ns1".to_string(), - env: SymbolEnv::new(None, types.void(), false, false, Phase::Independent, 0), + env: SymbolEnv::new(None, SymbolEnvKind::Scope, Phase::Independent, 0), loaded: false, }); let ns2 = types.add_namespace(Namespace { name: "ns2".to_string(), - env: SymbolEnv::new( - Some(ns1.env.get_ref()), - types.void(), - false, - false, - Phase::Independent, - 0, - ), + env: SymbolEnv::new(Some(ns1.env.get_ref()), SymbolEnvKind::Scope, Phase::Independent, 0), loaded: false, }); diff --git a/libs/wingc/src/visit.rs b/libs/wingc/src/visit.rs index c27a6fc6ba5..1837c063d10 100644 --- a/libs/wingc/src/visit.rs +++ b/libs/wingc/src/visit.rs @@ -1,8 +1,8 @@ use crate::{ ast::{ ArgList, BringSource, CalleeKind, Class, Expr, ExprKind, FunctionBody, FunctionDefinition, FunctionParameter, - FunctionSignature, Interface, InterpolatedStringPart, Literal, NewExpr, Reference, Scope, Stmt, StmtKind, Symbol, - TypeAnnotation, TypeAnnotationKind, UserDefinedType, + FunctionSignature, IfLet, Interface, InterpolatedStringPart, Literal, NewExpr, Reference, Scope, Stmt, StmtKind, + Symbol, TypeAnnotation, TypeAnnotationKind, UserDefinedType, }, dbg_panic, }; @@ -131,14 +131,14 @@ where v.visit_scope(statements); } StmtKind::Break | StmtKind::Continue => {} - StmtKind::IfLet { + StmtKind::IfLet(IfLet { value, statements, reassignable: _, var_name, elif_statements, else_statements, - } => { + }) => { v.visit_symbol(var_name); v.visit_expr(value); v.visit_scope(statements); diff --git a/libs/wingc/src/visit_context.rs b/libs/wingc/src/visit_context.rs index 2ea815724b5..2c36ed3ae96 100644 --- a/libs/wingc/src/visit_context.rs +++ b/libs/wingc/src/visit_context.rs @@ -1,14 +1,17 @@ +use itertools::Itertools; + use crate::{ - ast::{ExprId, Phase, Symbol, UserDefinedType}, + ast::{ExprId, FunctionSignature, Phase, Symbol, UserDefinedType}, type_check::symbol_env::SymbolEnvRef, }; +#[derive(Clone)] pub struct VisitContext { phase: Vec, env: Vec, - method_env: Vec>, + function_env: Vec, property: Vec, - method: Vec>, + function: Vec<(Option, FunctionSignature)>, class: Vec, statement: Vec, in_json: Vec, @@ -21,11 +24,11 @@ impl VisitContext { VisitContext { phase: vec![], env: vec![], - method_env: vec![], + function_env: vec![], property: vec![], class: vec![], statement: vec![], - method: vec![], + function: vec![], in_json: vec![], in_type_annotation: vec![], expression: vec![], @@ -76,43 +79,55 @@ impl VisitContext { // -- - pub fn push_class(&mut self, class: UserDefinedType, phase: &Phase, initializer_env: Option) { + pub fn push_class(&mut self, class: UserDefinedType, phase: &Phase) { self.class.push(class); self.push_phase(*phase); - self.method_env.push(initializer_env); } pub fn pop_class(&mut self) { self.class.pop(); self.pop_phase(); - self.method_env.pop(); } pub fn current_class(&self) -> Option<&UserDefinedType> { self.class.last() } - // -- + pub fn current_class_nesting(&self) -> Vec { + self.class.iter().rev().map(|udt| udt.clone()).collect_vec() + } - pub fn push_function_definition(&mut self, function_name: &Option, phase: &Phase, env: SymbolEnvRef) { - self.push_phase(*phase); - self.method.push(function_name.clone()); + // -- - // if the function definition doesn't have a name (i.e. it's a closure), don't push its env - // because it's not a method dude! - let maybe_env = function_name.as_ref().map(|_| env); - self.method_env.push(maybe_env); + pub fn push_function_definition( + &mut self, + function_name: Option<&Symbol>, + sig: &FunctionSignature, + env: SymbolEnvRef, + ) { + self.push_phase(sig.phase); + self.function.push((function_name.cloned(), sig.clone())); + self.function_env.push(env); } pub fn pop_function_definition(&mut self) { self.pop_phase(); - self.method.pop(); - self.method_env.pop(); + self.function.pop(); + self.function_env.pop(); } - pub fn current_method(&self) -> Option { + pub fn current_method(&self) -> Option<(Symbol, FunctionSignature)> { // return the first none-None method in the stack (from the end) - self.method.iter().rev().find_map(|m| m.clone()) + self + .function + .iter() + .rev() + .find(|(m, _)| m.is_some()) + .map(|(m, sig)| (m.clone().unwrap(), sig.clone())) + } + + pub fn current_function(&self) -> Option<(Option, FunctionSignature)> { + self.function.last().cloned() } pub fn current_phase(&self) -> Phase { @@ -120,7 +135,20 @@ impl VisitContext { } pub fn current_method_env(&self) -> Option<&SymbolEnvRef> { - self.method_env.iter().rev().find_map(|m| m.as_ref()) + // Get the env of the first named function in the stack (non named functions are closures) + self.function.iter().zip(self.function_env.iter()).rev().find_map( + |((m, _), e)| { + if m.is_some() { + Some(e) + } else { + None + } + }, + ) + } + + pub fn current_function_env(&self) -> Option<&SymbolEnvRef> { + self.function_env.last() } // -- @@ -179,9 +207,28 @@ impl VisitContext { pub trait VisitorWithContext { fn ctx(&mut self) -> &mut VisitContext; - fn with_expr(&mut self, expr: usize, f: impl FnOnce(&mut Self)) { + fn with_expr(&mut self, expr: ExprId, f: impl FnOnce(&mut Self)) { self.ctx().push_expr(expr); f(self); self.ctx().pop_expr(); } + + fn with_stmt(&mut self, stmt: usize, f: impl FnOnce(&mut Self)) { + self.ctx().push_stmt(stmt); + f(self); + self.ctx().pop_stmt(); + } + + fn with_function_def( + &mut self, + function_name: Option<&Symbol>, + sig: &FunctionSignature, + env: SymbolEnvRef, + f: impl FnOnce(&mut Self) -> T, + ) -> T { + self.ctx().push_function_definition(function_name, sig, env); + let res = f(self); + self.ctx().pop_function_definition(); + res + } } diff --git a/tools/hangar/__snapshots__/invalid.ts.snap b/tools/hangar/__snapshots__/invalid.ts.snap index 924438e2035..8d15193e76f 100644 --- a/tools/hangar/__snapshots__/invalid.ts.snap +++ b/tools/hangar/__snapshots__/invalid.ts.snap @@ -10,6 +10,203 @@ exports[`access_hidden_namespace.main.w 1`] = ` +Tests 1 failed (1) +Test Files 1 failed (1) +Duration " +`; + +exports[`access_modifiers.main.w 1`] = ` +"error: Method \\"private_method\\" is private in \\"Foo\\" but it's an implementation of \\"SomeInterface\\". Interface members must be public. + --> ../../../examples/tests/invalid/access_modifiers.main.w:40:3 + | +40 | private_method() {} + | ^^^^^^^^^^^^^^ Method \\"private_method\\" is private in \\"Foo\\" but it's an implementation of \\"SomeInterface\\". Interface members must be public. + + +error: Method \\"protected_method\\" is protected in \\"Foo\\" but it's an implementation of \\"SomeInterface\\". Interface members must be public. + --> ../../../examples/tests/invalid/access_modifiers.main.w:38:13 + | +38 | protected protected_method() {} + | ^^^^^^^^^^^^^^^^ Method \\"protected_method\\" is protected in \\"Foo\\" but it's an implementation of \\"SomeInterface\\". Interface members must be public. + + +error: Cannot override private method \\"method\\" of \\"Foo\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:49:3 + | +49 | method() { + | ^^^^^^ Cannot override private method \\"method\\" of \\"Foo\\" + + +error: Cannot override private method \\"method\\" of \\"Bar\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:72:3 + | +72 | method() { + | ^^^^^^ Cannot override private method \\"method\\" of \\"Bar\\" + + +error: Cannot access protected member \\"protected_field\\" of \\"Foo\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:100:9 + | +100 | log(foo.protected_field); + | ^^^^^^^^^^^^^^^ Cannot access protected member \\"protected_field\\" of \\"Foo\\" + + +error: Cannot access private member \\"private_field\\" of \\"Foo\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:102:9 + | +102 | log(foo.private_field); + | ^^^^^^^^^^^^^ Cannot access private member \\"private_field\\" of \\"Foo\\" + + +error: Cannot access protected member \\"protected_static_method\\" of \\"Foo\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:106:5 + | +106 | Foo.protected_static_method(); + | ^^^^^^^^^^^^^^^^^^^^^^^ Cannot access protected member \\"protected_static_method\\" of \\"Foo\\" + + +error: Cannot access private member \\"private_static_method\\" of \\"Foo\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:108:5 + | +108 | Foo.private_static_method(); + | ^^^^^^^^^^^^^^^^^^^^^ Cannot access private member \\"private_static_method\\" of \\"Foo\\" + + +error: Cannot override public method \\"public_method\\" of \\"Foo\\" with a private method + --> ../../../examples/tests/invalid/access_modifiers.main.w:148:3 + | +148 | public_method() {} + | ^^^^^^^^^^^^^ Cannot override public method \\"public_method\\" of \\"Foo\\" with a private method + + +error: Cannot override protected method \\"protected_method\\" of \\"Foo\\" with a private method + --> ../../../examples/tests/invalid/access_modifiers.main.w:150:3 + | +150 | protected_method() {} + | ^^^^^^^^^^^^^^^^ Cannot override protected method \\"protected_method\\" of \\"Foo\\" with a private method + + +error: Cannot override private method \\"private_method\\" of \\"Foo\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:152:3 + | +152 | private_method() {} + | ^^^^^^^^^^^^^^ Cannot override private method \\"private_method\\" of \\"Foo\\" + + +error: Cannot override public method \\"public_method\\" of \\"Foo\\" with a protected method + --> ../../../examples/tests/invalid/access_modifiers.main.w:157:13 + | +157 | protected public_method() {} + | ^^^^^^^^^^^^^ Cannot override public method \\"public_method\\" of \\"Foo\\" with a protected method + + +error: Cannot override private method \\"private_method\\" of \\"Foo\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:160:13 + | +160 | protected private_method() {} + | ^^^^^^^^^^^^^^ Cannot override private method \\"private_method\\" of \\"Foo\\" + + +error: Cannot override private method \\"private_method\\" of \\"Foo\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:167:7 + | +167 | pub private_method() {} + | ^^^^^^^^^^^^^^ Cannot override private method \\"private_method\\" of \\"Foo\\" + + +error: Cannot access protected member \\"protected_static_method\\" of \\"Foo\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:128:9 + | +128 | Foo.protected_static_method(); + | ^^^^^^^^^^^^^^^^^^^^^^^ Cannot access protected member \\"protected_static_method\\" of \\"Foo\\" + + +error: Cannot access private member \\"private_static_method\\" of \\"Foo\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:130:9 + | +130 | Foo.private_static_method(); + | ^^^^^^^^^^^^^^^^^^^^^ Cannot access private member \\"private_static_method\\" of \\"Foo\\" + + +error: Cannot access protected member \\"protected_field\\" of \\"Foo\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:114:13 + | +114 | log(foo.protected_field); + | ^^^^^^^^^^^^^^^ Cannot access protected member \\"protected_field\\" of \\"Foo\\" + + +error: Cannot access private member \\"private_field\\" of \\"Foo\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:116:13 + | +116 | log(foo.private_field); + | ^^^^^^^^^^^^^ Cannot access private member \\"private_field\\" of \\"Foo\\" + + +error: Cannot access protected member \\"protected_method\\" of \\"Foo\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:120:9 + | +120 | foo.protected_method(); + | ^^^^^^^^^^^^^^^^ Cannot access protected member \\"protected_method\\" of \\"Foo\\" + + +error: Cannot access private member \\"private_method\\" of \\"Foo\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:122:9 + | +122 | foo.private_method(); + | ^^^^^^^^^^^^^^ Cannot access private member \\"private_method\\" of \\"Foo\\" + + +error: Cannot access private member \\"private_static_method\\" of \\"Foo\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:88:9 + | +88 | Foo.private_static_method(); + | ^^^^^^^^^^^^^^^^^^^^^ Cannot access private member \\"private_static_method\\" of \\"Foo\\" + + +error: Cannot access private member \\"private_static_method\\" of \\"Bar\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:93:9 + | +93 | Bar.private_static_method(); + | ^^^^^^^^^^^^^^^^^^^^^ Cannot access private member \\"private_static_method\\" of \\"Bar\\" + + +error: Cannot access private member \\"private_field\\" of \\"Baz\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:75:14 + | +75 | log(this.private_field); + | ^^^^^^^^^^^^^ Cannot access private member \\"private_field\\" of \\"Baz\\" + + +error: Cannot access private member \\"private_method\\" of \\"Baz\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:80:10 + | +80 | this.private_method(); + | ^^^^^^^^^^^^^^ Cannot access private member \\"private_method\\" of \\"Baz\\" + + +error: Cannot access private member \\"private_static_method\\" of \\"Foo\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:65:9 + | +65 | Foo.private_static_method(); + | ^^^^^^^^^^^^^^^^^^^^^ Cannot access private member \\"private_static_method\\" of \\"Foo\\" + + +error: Cannot access private member \\"private_field\\" of \\"Bar\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:52:14 + | +52 | log(this.private_field); + | ^^^^^^^^^^^^^ Cannot access private member \\"private_field\\" of \\"Bar\\" + + +error: Cannot access private member \\"private_method\\" of \\"Bar\\" + --> ../../../examples/tests/invalid/access_modifiers.main.w:57:10 + | +57 | this.private_method(); + | ^^^^^^^^^^^^^^ Cannot access private member \\"private_method\\" of \\"Bar\\" + + + + Tests 1 failed (1) Test Files 1 failed (1) Duration " @@ -19,8 +216,8 @@ exports[`access_static_from_instance.main.w 1`] = ` "error: Static class fields not supported yet, see https://github.com/winglang/wing/issues/1668 --> ../../../examples/tests/invalid/access_static_from_instance.main.w:4:3 | -4 | static f: num; - | ^^^^^^^^^^^^^^ Static class fields not supported yet, see https://github.com/winglang/wing/issues/1668 +4 | pub static f: num; + | ^^^^^^^^^^^^^^^^^^ Static class fields not supported yet, see https://github.com/winglang/wing/issues/1668 error: Cannot access static property \\"f\\" from instance @@ -402,17 +599,10 @@ error: Unknown symbol \\"C11\\" | ^^^ Unknown symbol \\"C11\\" -error: Expected type to be \\"num\\", but got \\"str\\" instead - --> ../../../examples/tests/invalid/class.main.w:123:11 - | -123 | super(someStr); - | ^^^^^^^ Expected type to be \\"num\\", but got \\"str\\" instead - - error: Expected 1 positional argument(s) but got 0 - --> ../../../examples/tests/invalid/class.main.w:132:5 + --> ../../../examples/tests/invalid/class.main.w:157:5 | -132 | super(); +157 | super(); | ^^^^^^^^ Expected 1 positional argument(s) but got 0 @@ -424,17 +614,24 @@ error: Expected 1 arguments but got 2 error: Expected 1 positional argument(s) but got 0 - --> ../../../examples/tests/invalid/class.main.w:157:5 + --> ../../../examples/tests/invalid/class.main.w:132:5 | -157 | super(); +132 | super(); | ^^^^^^^^ Expected 1 positional argument(s) but got 0 +error: Expected type to be \\"num\\", but got \\"str\\" instead + --> ../../../examples/tests/invalid/class.main.w:123:11 + | +123 | super(someStr); + | ^^^^^^^ Expected type to be \\"num\\", but got \\"str\\" instead + + error: Variable cannot be reassigned from inflight --> ../../../examples/tests/invalid/class.main.w:61:5 | 61 | this.y = 1; - | ^^^^^^^^^^^ Variable cannot be reassigned from inflight + | ^^^^^^ Variable cannot be reassigned from inflight error: Expected type to be \\"num\\", but got \\"str\\" instead @@ -1235,7 +1432,7 @@ exports[`inflight_reassign.main.w 1`] = ` --> ../../../examples/tests/invalid/inflight_reassign.main.w:5:3 | 5 | xvar = \\"hi\\"; - | ^^^^^^^^^^^^ Variable cannot be reassigned from inflight + | ^^^^ Variable cannot be reassigned from inflight error: Variable is not reassignable @@ -1385,6 +1582,21 @@ exports[`issue_2767.main.w 1`] = ` +Tests 1 failed (1) +Test Files 1 failed (1) +Duration " +`; + +exports[`jsii_access_modifiers.main.w 1`] = ` +"error: Cannot access protected member \\"createTopic\\" of \\"Bucket\\" + --> ../../../examples/tests/invalid/jsii_access_modifiers.main.w:13:3 + | +13 | b.createTopic(cloud.BucketEventType.CREATE); + | ^^^^^^^^^^^ Cannot access protected member \\"createTopic\\" of \\"Bucket\\" + + + + Tests 1 failed (1) Test Files 1 failed (1) Duration " @@ -2387,6 +2599,13 @@ exports[`super_call.main.w 1`] = ` | ^^ \\"super\\" can only be used inside of classes +error: Cannot override private method \\"m1\\" of \\"BaseClass\\" + --> ../../../examples/tests/invalid/super_call.main.w:47:12 + | +47 | inflight m1(): str { + | ^^ Cannot override private method \\"m1\\" of \\"BaseClass\\" + + error: \`super\` calls inside inflight closures not supported yet, see: https://github.com/winglang/wing/issues/3474 --> ../../../examples/tests/invalid/super_call.main.w:52:50 | @@ -2534,11 +2753,11 @@ Duration " `; exports[`unknown_field.main.w 1`] = ` -"error: No member \\"a\\" in class \\"String\\" +"error: Unknown symbol \\"a\\" --> ../../../examples/tests/invalid/unknown_field.main.w:1:12 | 1 | std.String.a.b.c.fromJson(); - | ^ No member \\"a\\" in class \\"String\\" + | ^ Unknown symbol \\"a\\"