Skip to content

Commit ca6d5be

Browse files
authored
fix(compiler)!: non-static extern methods are not supported (#4027)
It's not currently possible for extern method implementations to access class members (like fields, or other instance methods), yet it has still been possible to declare them both ways. This PR introduces an error message if you attempt to add a non-static extern method. In the future, this restriction can be lifted if the capability is supported through a new calling convention with extern JavaScript files or something like that. ## 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) - [ ] 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)*.
1 parent 8cad1fa commit ca6d5be

File tree

9 files changed

+44
-15
lines changed

9 files changed

+44
-15
lines changed

Diff for: docs/docs/03-language-reference.md

+2
Original file line numberDiff line numberDiff line change
@@ -1762,6 +1762,8 @@ exports.makeId = function () {
17621762
Given a method of name X, the compiler will map the method to the JavaScript export with the
17631763
matching name (without any case conversion).
17641764
1765+
Extern methods do not support access to class's members through `this`, so they must be declared `static`.
1766+
17651767
### 5.2.1 TypeScript
17661768
17671769
It is possible to use TypeScript to write helpers, but at the moment this is not

Diff for: examples/tests/invalid/extern.w

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@ class Foo {
22
extern "./sad.js" static getNum(): num;
33
//^ "./sad.js" not found
44
extern "not-installed" static tooBad(): bool;
5-
//^ "not-installed" not found
6-
}
5+
}

Diff for: examples/tests/invalid/extern_static.w

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class Foo {
2+
extern "../external_js.js" inflight getGreeting(name: str): str;
3+
//^ Error: extern methods must be declared "static"
4+
}

Diff for: examples/tests/valid/dynamo.w

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ class DynamoTable {
5454
}
5555
}
5656

57-
extern "./dynamo.js" inflight _putItem(tableName: str, item: Json): void;
57+
extern "./dynamo.js" static inflight _putItem(tableName: str, item: Json): void;
5858

5959
inflight putItem(item: Map<Attribute>) {
6060
let json = this._itemToJson(item);
61-
this._putItem(this.tableName, json);
61+
DynamoTable._putItem(this.tableName, json);
6262
}
6363

6464
inflight _itemToJson(item: Map<Attribute>): Json {

Diff for: examples/tests/valid/dynamo_awscdk.w

+4-4
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,16 @@ class DynamoTable {
6060
}
6161
}
6262

63-
extern "./dynamo.js" inflight _putItem(tableName: str, item: Json): void;
63+
extern "./dynamo.js" static inflight _putItem(tableName: str, item: Json): void;
6464
inflight putItem(item: Map<Attribute>) {
6565
let json = this._itemToJson(item);
66-
this._putItem(this.tableName, json);
66+
DynamoTable._putItem(this.tableName, json);
6767
}
6868

69-
extern "./dynamo.js" inflight _getItem(tableName: str, key: Json): Json;
69+
extern "./dynamo.js" static inflight _getItem(tableName: str, key: Json): Json;
7070
inflight getItem(key: Map<Attribute>): Json {
7171
let json = this._itemToJson(key);
72-
return this._getItem(this.tableName, json);
72+
return DynamoTable._getItem(this.tableName, json);
7373
}
7474

7575
inflight _itemToJson(item: Map<Attribute>): Json {

Diff for: examples/tests/valid/extern_implementation.w

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class Foo {
55
extern "./external_js.js" static inflight regexInflight(pattern: str, text: str): bool;
66
extern "./external_js.js" static inflight getUuid(): str;
77
extern "./external_js.js" static inflight getData(): str;
8-
extern "./external_js.js" inflight print(msg: str);
8+
extern "./external_js.js" static inflight print(msg: str);
99
extern "uuid" static v4(): str;
1010

1111
inflight call() {
@@ -27,5 +27,5 @@ test "call" {
2727
}
2828

2929
test "console" {
30-
f.print("hey there");
30+
Foo.print("hey there");
3131
}

Diff for: libs/wingc/src/type_check.rs

+9
Original file line numberDiff line numberDiff line change
@@ -3941,6 +3941,15 @@ impl<'a> TypeChecker<'a> {
39413941
self.types.set_scope_env(scope, method_env);
39423942
self.inner_scopes.push(scope);
39433943
}
3944+
3945+
if let FunctionBody::External(_) = &method_def.body {
3946+
if !method_def.is_static {
3947+
self.spanned_error(
3948+
method_name,
3949+
"Extern methods must be declared \"static\" (they cannot access instance members)",
3950+
);
3951+
}
3952+
}
39443953
}
39453954

39463955
fn add_method_to_class_env(

Diff for: tools/hangar/__snapshots__/invalid.ts.snap

+15
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,21 @@ error: Failed to resolve extern \\"not-installed\\": Not Found
752752
753753
754754
755+
Tests 1 failed (1)
756+
Test Files 1 failed (1)
757+
Duration <DURATION>"
758+
`;
759+
760+
exports[`extern_static.w 1`] = `
761+
"error: Extern methods must be declared \\"static\\" (they cannot access instance members)
762+
--> ../../../examples/tests/invalid/extern_static.w:2:39
763+
|
764+
2 | extern \\"../external_js.js\\" inflight getGreeting(name: str): str;
765+
| ^^^^^^^^^^^ Extern methods must be declared \\"static\\" (they cannot access instance members)
766+
767+
768+
769+
755770
Tests 1 failed (1)
756771
Test Files 1 failed (1)
757772
Duration <DURATION>"

Diff for: tools/hangar/__snapshots__/test_corpus/valid/extern_implementation.w_compile_tf-aws.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ module.exports = function({ $f }) {
2020

2121
## inflight.$Closure2-1.js
2222
```js
23-
module.exports = function({ $f }) {
23+
module.exports = function({ $Foo }) {
2424
class $Closure2 {
2525
constructor({ }) {
2626
const $obj = (...args) => this.handle(...args);
2727
Object.setPrototypeOf($obj, this);
2828
return $obj;
2929
}
3030
async handle() {
31-
(await $f.print("hey there"));
31+
(await $Foo.print("hey there"));
3232
}
3333
}
3434
return $Closure2;
@@ -51,7 +51,7 @@ module.exports = function({ }) {
5151
static async getData() {
5252
return (require("<ABSOLUTE_PATH>/external_js.js")["getData"])()
5353
}
54-
async print(msg) {
54+
static async print(msg) {
5555
return (require("<ABSOLUTE_PATH>/external_js.js")["print"])(msg)
5656
}
5757
async call() {
@@ -348,7 +348,7 @@ class $Root extends $stdlib.std.Resource {
348348
static _toInflightType(context) {
349349
return `
350350
require("./inflight.$Closure2-1.js")({
351-
$f: ${context._lift(f)},
351+
$Foo: ${context._lift(Foo)},
352352
})
353353
`;
354354
}
@@ -368,7 +368,7 @@ class $Root extends $stdlib.std.Resource {
368368
}
369369
_registerBind(host, ops) {
370370
if (ops.includes("handle")) {
371-
$Closure2._registerBindObject(f, host, ["print"]);
371+
$Closure2._registerBindObject(Foo, host, ["print"]);
372372
}
373373
super._registerBind(host, ops);
374374
}

0 commit comments

Comments
 (0)