Skip to content

Add getters and setters (#235) - #701

Open
bvisness wants to merge 10 commits into
WebAssembly:mainfrom
bvisness:get-set
Open

Add getters and setters (#235)#701
bvisness wants to merge 10 commits into
WebAssembly:mainfrom
bvisness:get-set

Conversation

@bvisness

Copy link
Copy Markdown
Collaborator

Adds get and set to WIT and adds [get] and [set] annotations to function names, along with validation conditions.

Right now my strongly-unique rules forbid [get]foo.prop and [static]foo.prop from existing on the same resource. This seems like a reasonable and conservative choice to me. It does not forbid [get]foo.prop from existing alongside [method]foo.get-prop or [static]foo.get-prop, since we need to keep that WASI migration path open. This could be pretty easily added in the future but will make me sad because it will make the strongly-unique rules even more bonkers to implement than they already are >:(

Resolves #235.

@bvisness
bvisness requested a review from lukewagner August 17, 2026 23:04
@badeend

badeend commented Aug 18, 2026

Copy link
Copy Markdown
Member

Should this be emoji-gated?


With resource instance getters/setters spec'ed & implemented, how big do you expect the jump will be to supporting static resource getters/setters and interface-level getters/setters as well?. E.g.

interface a {
    foo: get() -> u64;
    foo: set(value: u64);

    resource r {
        bar: get() -> u64;
        bar: set(value: u64);

        baz: static get() -> u64;
        baz: static set(value: u64);
    }
}

If not too much, it might be nice to include these at the same time. Especially interface-level getters/setters already have a couple of use cases in WASI:

  • environment.get-environment
  • environment.get-arguments
  • environment.get-initial-cwd
  • monotonic-clock.get-resolution
  • system-clock.get-resolution
  • insecure-seed.get-insecure-seed
  • preopens.get-directories
  • stdin.get-stdin
  • stdout.get-stdout
  • stderr.get-stderr
  • terminal-stdin.get-terminal-stdin
  • terminal-stdout.get-terminal-stdout
  • terminal-stderr.get-terminal-stderr

@bvisness

Copy link
Copy Markdown
Collaborator Author

Should this be emoji-gated?

That seems overkill to me, since getters and setters are basically sugar with a couple extra validation rules.

how big do you expect the jump will be to supporting static resource getters/setters and interface-level getters/setters as well?

I was initially surprised by this question, but on further review, it seems like this happens in WebIDL often enough that we should probably go ahead and spec it right away. For example, Notification.permission is a static read-only property. My C-programmer sensibilities are offended, but what else is new on the web?

The same goes for interface getters with properties like CSS.highlights. This is just a thing we do, I guess.

My main concern is codegen; I would expect that many languages don't have any kind of native syntax for interface-level getters and setters in particular. A cursory LLM exploration indicates that newer versions of Python, for example, forbid you to combine @classmethod and @property, forcing you to either do something with metaclasses (🤮) or just generate a normal function with get in the name. I also instinctively dislike having too many ways to define accessors; I don't relish the idea of forever bikeshedding whether something ought to be a getter or a plain function, nor the idea of evolving getters into plain functions later when it turns out they needed a parameter.

But, if some languages have to fall back to plain old functions with get and set in the name, that's probably fine, and my other complaints are not really confined to static/interface getters in particular. Probably we can just do it, and bindings generators can just suck it up and fill in more of the matrix.

how big do you expect the jump will be to supporting static resource getters/setters and interface-level getters/setters as well?

To actually answer the question...probably not that hard. As far as validation, it's just dropping (param "self" (borrow $R)). My main question is how to compose multiple annotations together, e.g. [static][get] vs. [static,get], and also for interface getters, is it just [get]foo with no resource type?

@bvisness

Copy link
Copy Markdown
Collaborator Author

Another fun thing to consider: WebIDL's [PutForwards] allows the types of getters and setters to disagree. For example, the completely ubiquitous HTMLElement.style property is defined like so:

interface mixin ElementCSSInlineStyle {
  [SameObject, PutForwards=cssText] readonly attribute CSSStyleProperties style;
};

In other words, the setter for style forwards to style.cssText, meaning a get of style returns CSSStyleProperties, while a set of style takes string. A similar situation applies to window.location. Should we relax the restriction that the setter's value type and the getter's return type must agree? Do we have any other choice?

@lukewagner

Copy link
Copy Markdown
Member

Per our new WASI 0.3.* CM feature release process, even small additions (e.g., recently, implements) are emoji-gated and released only after a WASI SG vote (so that we are explicit about when and what can change without breaking people), so I think @badeend is right we should have an emoji-gate here.

For "static getters/setters": could we instead consider cases like Notifications.permission as being a (non-static) getter on a notifications resource whose handle is returned by a plain function on a global interface (that contains everything installed on the JS global)?

Should we relax the restriction that the setter's value type and the getter's return type must agree? Do we have any other choice?

I think this makes sense.

@bvisness

Copy link
Copy Markdown
Collaborator Author

For "static getters/setters": could we instead consider cases like Notifications.permission as being a (non-static) getter on a notifications resource whose handle is returned by a plain function on a global interface (that contains everything installed on the JS global)?

Ryan dug into this a bit and these properties don't even require a this in order to be accessed. It seems superfluous to require a handle to a resource that won't even be accessed. So honestly it does seem like I should just get over it and support static getters, although this does have name-mangling sorts of concerns.

As for the getter/setter agreement rule, I'll just go ahead and drop that. Bindings generators can just check the type agreement themselves when deciding what code to generate, and the majority of the time they should agree.

@lukewagner

Copy link
Copy Markdown
Member

Ryan dug into this a bit and these properties don't even require a this in order to be accessed.

Ah, ok. So I guess this is unlike performance.now() where, strangely, iiuc, you can't write let n = performance.now; n() b/c the receiver must actually be the Performance object.

Having to fall back to explicit get-foo/set-foo seems fine for languages that don't (and will be needed in various other cases anyways) but, just to confirm: do at least some other languages have a concept of "static" getters/setters?

@badeend

badeend commented Aug 18, 2026

Copy link
Copy Markdown
Member

do at least some other languages have a concept of "static" getters/setters?

From what I could see in #235 (comment) (see bottom of comment), the answer seems to be:
Yes, all of them.

@bvisness

Copy link
Copy Markdown
Collaborator Author

Yes, all of them.

It seems like Python might be an exception - per this page, @classmethod can no longer wrap @property as of Python 3.13. But there are probably workarounds, and it probably isn't a deal-breaker anyway.

@bvisness

Copy link
Copy Markdown
Collaborator Author

PR is updated with emoji-gating, new canonicalization rules to match #702, a rule requiring getters to precede setters for the same property, and without the rule requiring getter and setter types to agree.

I would appreciate guidance on how to specify names for static getters. How do you combine [static] and [get] into a single name?

@lukewagner

Copy link
Copy Markdown
Member

I would appreciate guidance on how to specify names for static getters. How do you combine [static] and [get] into a single name?

[static][get]/[static][set] seem reasonable to me . Although we may want to relax it later, I think we could start by just adding those two exact strings as two new cases to plainname alongside the others (and not worrying about [get][static] etc).

(As a bit of background for folks who saw the last iteration on this design question with static async functions back when we had async as a name-mangled hint (before it became an effect that is declared as part of func types), we had a space-separated [static async] for no particular reason, but revisiting this choice, spaces seem slightly more hazardous: maybe they get parsed as whitespace and so some impls accidentally accept N spaces instead of exactly 1; when printing the name in a browser, maybe you get a line-break mid-name unless the renderer thought to use a  . Comma-separated could address this, but if we anticipate some future [overload=some-suffix]foo (with meaning "use foo if you support overloading, use (your idiomatic casing of) foo-some-suffix if not), it seemed simpler to keep overload in its own cleanly-bracketed [...]. This is all total bikeshedding of course; most options seem workable, but fwiw that's how I got to the above.)

@bvisness

Copy link
Copy Markdown
Collaborator Author

I was hoping you'd say [static][get], so I'll go with that :)

@bvisness

Copy link
Copy Markdown
Collaborator Author

Updated to support static and interface-level getters and setters. I also opted to go with a slightly different name-mangling scheme that feels to me more consistent and makes it easier to express the matrix of validation constraints:

;; Interface-level
[get]foo
[set]foo

;; Non-static
[method][get]r.foo
[method][set]r.foo

;; Static
[static][get]r.foo
[static][set]r.foo

I also still have a rule requiring getters to precede setters, because it just feels better to me that way. I could be talked out of this for WIT but I see no reason to tolerate the order mismatch in components themselves. I have some additional thoughts about this in #235.

@bvisness

Copy link
Copy Markdown
Collaborator Author

Ok @lukewagner, I think this PR is now up to date with all the concerns raised both here and the original issue. So it should actually be ready for a real review now, whenever that works for you. I'm probably going to start implementing the current shape of things in SM but will not be upset if anything needs to change.

Comment thread design/mvp/WIT.md Outdated

@lukewagner lukewagner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One quick question:

Comment thread design/mvp/Binary.md Outdated
Comment thread design/mvp/Binary.md Outdated
Comment thread design/mvp/Explainer.md
Comment thread design/mvp/Binary.md
Comment on lines +451 to +458
* 📡 If a name with `[set]` is defined as an import or export within a
particular scope, the equivalent name with `[get]` must have already been
defined as an import or export respectively in that same scope—that is, all
labels must be equal (before canonicalization), and all annotations must be
the same except that `[set]` is replaced with `[get]`, and the `[get]`
import/export must precede the `[set]` import/export. For example,
`[set]prop` requires `[get]prop`, and `[method][set]foo.bar` requires
`[method][get]foo.bar`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about this some more:
The rule on its own makes sense, though the tooling side of things prbably makes it impractical:

  • We can't assume that toolchains (like GCC or LLVM) emit their external symbols in any well-defined order.
  • When guest code imports a property but only invokes its setter, the getter import may end up being optimized away as partof dead-code elimination.

This makes me lean towards:

  • at the binary level; treat getters & setters as independent items, validated separately, just like their method equivalents are today
  • move the validation into WIT, or even further out into an external wit "linter" that steers authors away from BadIdeas™ such as: setters without getters, identifiers called class, etc. There have been ideas about such a linter tool before, but as of yet no such thing exists.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea why you think toolchains have no control over the order in which symbols are emitted; Luke already pointed out in this comment that we already have a rule today saying that r must precede [method]r.foo. What makes this any different?

As for dead code elimination, surely component tooling is generally not quick to delete "unused" functions, because the functions (used or unused) define the component / instance's type. And besides, if some component tooling is indeed performing some kind of whole-component optimization pass, it seems reasonable for it to be aware of constraints on getters and setters.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While at the core wasm level, I think @badeend is right that core symbols are emitted object-file-by-object-file in arbitrary order with no well-defined global order, what we're talking about here is component-level imports and exports that are emitted by wasm-tools component new which sees the target world and knows the validation rules and order things before generating the final component output.

Comment thread design/mvp/WIT.md Outdated
Comment thread design/mvp/Explainer.md

@lukewagner lukewagner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything makes sense to me, nice work! Let's leave this open for a bit for other comments and current discussion to finish. Since it's emoji-gated, I think it can land ahead of full implementation since we can open future issues to fix problems we find.

Comment thread design/mvp/WIT.md Outdated
Comment thread design/mvp/Binary.md
Comment on lines +451 to +458
* 📡 If a name with `[set]` is defined as an import or export within a
particular scope, the equivalent name with `[get]` must have already been
defined as an import or export respectively in that same scope—that is, all
labels must be equal (before canonicalization), and all annotations must be
the same except that `[set]` is replaced with `[get]`, and the `[get]`
import/export must precede the `[set]` import/export. For example,
`[set]prop` requires `[get]prop`, and `[method][set]foo.bar` requires
`[method][get]foo.bar`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While at the core wasm level, I think @badeend is right that core symbols are emitted object-file-by-object-file in arbitrary order with no well-defined global order, what we're talking about here is component-level imports and exports that are emitted by wasm-tools component new which sees the target world and knows the validation rules and order things before generating the final component output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Resource properties

3 participants