Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Properties defined with JsProperty are not enumerable, do not work with spread operator #9913

Open
mofojed opened this issue Jan 31, 2024 · 1 comment

Comments

@mofojed
Copy link

mofojed commented Jan 31, 2024

GWT version: 2.9.0
Browser (with version): Chromium Version 120.0.6099.216
Operating System: Ubuntu 22.04


Description

You cannot use the spread operator to copy properties defined with @JsProperty as would be expected from a native JS object.

Steps to reproduce

We're experiencing the issue in our project: deephaven/web-client-ui#1753 (comment)
I believe a minimally reproducible example would be:

  1. GWT compile a class Foo defined with a @JsProperty public String getBar() { return "bar" }
  2. Use the compiled library and copy the object using a spread operator, checking that the copied object is the same as the e.g.:
var a = new Foo()
var b = {...a}
console.log(b, a.bar === b.bar)
> Outputs {}, false
Known workarounds

It looks like the enumerable: true flag should be added when defining properties on an object:

propertyDefinition[key]['configurable'] = true;

See MDN docs for details: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#enumerable_attribute

Links to further discussions
@niloc132
Copy link
Contributor

It looks like this is the expected behavior with types that define get/set methods (both by Object.defineProperties and es6 class definitions):

class Foo {
  get label() {
    return 1;
  }
}
a = {...new Foo()} 
a.label

yield undefined. If the get/set method is on an object rather than a type, the value is returned as expected:

var a = {
    get b() { return 'b' },
};
var b = { ...a }
b.b

returns the string "b".

Typescript understands this, but only to a point - classes are understood to have get methods that won't be enumerable, but once you add an interface into the picture, it no longer works:

interface IFoo {
  get label(): string;
}

class Foo implements IFoo {
  get label() {
    return 'foo';
  }
}

const foo = new Foo();
const ifoo: IFoo = new Foo();

const a = { ...foo };
const b = { ...ifoo };

In the above, a is recognized to have no properties, but as of 5.4.5, TS believes that b has a label string, whereas in reality it is undefined.


The https://github.com/Vertispan/jsinterop-ts-defs/ project is already emitting as correct of TS as we can, with the equivelent of the interface IFoo above having get/set methods on it. In theory something like microsoft/TypeScript#9726 could provide a way to explicitly indicate that these property accessors aren't enumerable, but from the TS example above, I'm inclined to think this might just be a bug in TS rather than a missing feature.


We could make a feature request at https://github.com/google/jsinterop-annotations/ to specify a new JsProperty#enumerable, but it might need to be tri-state, or specific to decorating methods rather than properties (since Java fields would be enumerable by default, as they are properties on the underlying js object, while java methods would not be enumerable, since they are on the class).

I'll leave this open for later discussion, but at this time we're not considering this to be a bug.

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

No branches or pull requests

2 participants