Skip to content

Add decorators tests#4103

Open
pzuraq wants to merge 2 commits intotc39:mainfrom
pzuraq:add-decorators-tests
Open

Add decorators tests#4103
pzuraq wants to merge 2 commits intotc39:mainfrom
pzuraq:add-decorators-tests

Conversation

@pzuraq
Copy link

@pzuraq pzuraq commented May 30, 2024

This PR is still a WIP, I will be trying to wrap it up tomorrow. Took me a while to get best way to combine templates and cases to have as wide coverage as possible without having to write out every permutation manually.

@pzuraq pzuraq requested a review from a team as a code owner May 30, 2024 23:19
@pzuraq pzuraq marked this pull request as draft May 30, 2024 23:44
@pzuraq
Copy link
Author

pzuraq commented Jun 2, 2024

I've completed writing the tests, now I just need to actually get them to run properly and debug any issues (could be typos, etc).

@nicolo-ribaudo
Copy link
Member

nicolo-ribaudo commented Jun 2, 2024

https://github.com/babel/babel-test262-runner/ could be useful to run the tests and check if they have any problem.

@pzuraq
Copy link
Author

pzuraq commented Jun 2, 2024

Yes, I've been using that repo for testing in general. The previous commit for fields (a3b4282) was passing, at that point I kinda had all the details figured out for the overall structure so I scaffolded out all of the tests themselves and made sure I had written all of the ones we needed.

Next is just running them again to see if they work and fixing issues. I'm sure there are minor issues, definitely, but could also be some larger ones.

@nicolo-ribaudo one quick question, the generators in make throw an error if the folders don't exist for the output of a template/case file, so I've just been making them manually. I couldn't figure out the command to run to not throw, is there a way to do that easily?

@pzuraq pzuraq marked this pull request as ready for review June 6, 2024 13:43
@pzuraq pzuraq force-pushed the add-decorators-tests branch from f72b125 to aa99427 Compare June 6, 2024 14:26
@pzuraq
Copy link
Author

pzuraq commented Jun 6, 2024

This PR is now ready for review. There are some tests that are failing against the Babel test runner that I can't figure out:

  1. Some class expression tests are failing due to a Babel transpilation error, var C is already declared for some reason. The code is valid and should work, and it transpiles fine in the Babel playground, so I'm not sure what the issue is there.
  2. Some tests that rely on Proxy are failing because Proxy is undefined. I did add the feature flag for it, so I'm not sure what else I need to do to make Proxy available.
  3. The tests for a class decorator returning a non-[[Construct]] arrow function are failing, this is something that the Babel transform does not check for (not sure if it realistically can check for this).

I believe that's it though.

The tests should cover the bulk of decorator behavior, there may be some small gaps but we can address those in followups, this is already a massive PR and I don't want to continue adding to it.

@ptomato
Copy link
Contributor

ptomato commented Aug 15, 2024

@pzuraq We discussed this in yesterday's test262 meeting. We plan to start reviewing it after tackling one other large PR that's been waiting longer.

In the meantime, would you mind splitting the commit on this branch into two commits, one with any hand-written changes and one with autogenerated changes? We'll focus on the former.

@pzuraq
Copy link
Author

pzuraq commented Aug 16, 2024

Ok, sounds good! I can get that done likely by the end of the month or early next month.

@ptomato ptomato force-pushed the add-decorators-tests branch from aa99427 to 15f73b9 Compare October 15, 2024 19:27
@ptomato
Copy link
Contributor

ptomato commented Oct 15, 2024

@pzuraq I went ahead and investigated which tests were autogenerated — it looks like they all are! 😄

A few of them don't have corresponding templates in src/, maybe you forgot to add some files?

  • src/decorator/field-context-kind.case
  • src/decorator/field-initializers-interleave.case
  • src/decorator/field-instance-initializer-order.case
  • src/decorator/field-received-value.case
  • src/decorator/field-static-initializer-order.case
  • src/decorator/decorator-test-123.case

@pzuraq
Copy link
Author

pzuraq commented Feb 18, 2025

@ptomato ah those files were mistakenly added actually, they were work in progress files as I was working out the way the generator system worked. Removing them and rebasing now.

@fisker
Copy link

fisker commented May 14, 2025

The diff is quite big, I didn't read all, but it will good to add this case to tests if missing.

class A {
  @decorators[0]
  method() {}
}


var C = class {
@pushOrd(1, 3, 6)
@pushOrd(0, 4, 5)
Copy link
Contributor

Choose a reason for hiding this comment

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

According to https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-initializefieldoraccessor the extraInitializers run in the order they were added by https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-createaddinitializerfunction to the context. So according to the spec this should be 0,1,4,3,5,6.

At least unless I am missing something. The polyfill claims that it should 1,2,3,4,5,6.

Copy link
Author

Choose a reason for hiding this comment

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

The ordering here is correct. There are 3 phases:

  1. Decorator application
  2. Field initialization
  3. Extra initializers

Phase two reverses the ordering of application from outermost -> innermost, because logically we are "setting" the field. The initial value is first created and then set, then the outer decorator gets the initial value and gets to mutate it, then the inner decorator gets the value, then it is set on the internal storage slot.

There is an issue on the decorators repo discussing this, and it was discussed at a Plenary because the change was made in stage 3 so needed consensus to merge.

Copy link
Contributor

Choose a reason for hiding this comment

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

Right, application is inside out. So we first apply @pushOrd(0, 4, 5), then @pushOrd(1, 3, 6). According to https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-applydecoratorsanddefinemethod the extraInitializers are shared between all decorators of the same method. So the first one to run pushes a () => ord.push(4) and the second one () => ord.push(3) to the same extraInitializers list. And that is also the final order we are supposed to run them. So according to the spec I think this example should create 0,1,4,3,5,6.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, I got confused. The 5,6 are the extra initializers.

So 3,4 are the normal initializers. And what you are saying is that they are supposed to run outside in. I guess that part I have not found in the spec yet.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, got it. 15.7.6 ApplyDecoratorsToElementDefinition [...] 8. If IsCallable(initializer) is true, prepend initializer to elementRecord.[[Initializers]].

Copy link
Contributor

Choose a reason for hiding this comment

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

However considering that the [[Initializers]] field comes from ClassFieldDefinitionEvaluation which already adds the actual initializer to that list, wouldn't the prepending cause the original initializer to run last?

I.e., I think according to the current spec text we are supposed to see 0,1,3,4,2,5,6.

Copy link
Author

@pzuraq pzuraq Dec 2, 2025

Choose a reason for hiding this comment

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

This prepends the initialzer to the [[Initializers]] list:

l. If kind is field, then
i. If IsCallable(newValue) is true, prepend newValue to elementRecord.[[Initializers]].

Then we have initialization

  1. Assert: elementRecord.[[Kind]] is field or accessor.
  2. If elementRecord has a [[BackingStorageKey]] field, let fieldName be elementRecord.[[BackingStorageKey]].
  3. Else, let fieldName be elementRecord.[[Key]].
  4. Let initValue be undefined.
  5. For each element initializer of elementRecord.[[Initializers]], do
    a. Set initValue to ? Call(initializer, receiver, « initValue »).
  6. If fieldName is a Private Name, then
    a. Perform ? PrivateFieldAdd(receiver, fieldName, initValue).
  7. Else,
    a. Assert: IsPropertyKey(fieldName) is true.
    b. Perform ? CreateDataPropertyOrThrow(receiver, fieldName, initValue).
  8. For each element initializer of elementRecord.[[ExtraInitializers]], do
    a. Perform ? Call(initializer, receiver).
  9. Return unused.

Note that [[Initializers]] is used before [[ExtraInitializers]], which holds actual initializers added by addInitializers to an element definition. Only Fields and Accessors have [[Initializers]].

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed. But my new worry is that when we call prepend newValue to elementRecord.[[Initializers]], that list already contains the initializer CreateFieldInitializerFunction(this, #element, ord.push(2)) from ClassFieldDefinitionEvaluation. This means prepending the initializers from the decorators will move the actual initializer to the end of the [[Initializers]] list. Resulting in 0,1,3,4,2,5,6.

Copy link
Author

Choose a reason for hiding this comment

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

hmm, yes ok, you are correct. The prepended decorators are in the correct order, but the original initializer does need to be the first item in the list. This was likely introduced when the order was reversed, when initializers we appended to the list this behavior would have made sense.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, I created an issue to track this tc39/proposal-decorators#569


assert(C !== originalClass, 'values are not the same');
assert(C instanceof originalClass, 'value is instance of class');
assert.sameValue(new C(), 123);
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this fail with a TypeError if the 'construct' trap returns a non-object?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, but a Proxy is an object?

Copy link
Author

Choose a reason for hiding this comment

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

ah I see, sorry I was confused (still waking up).

Yes, this should error because construct needs to return an object, that's Proxy's behavior. Test should be updated to do that.

I don't have bandwidth to work on this at the moment but happy to take PRs to the branch or give access.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't have time to work on the tests either. I just stumble upon these issues since I am evaluating the coherence of spec vs. proposed V8 patch vs. babel.

Copy link
Contributor

Choose a reason for hiding this comment

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

(btw. the line 32 fails too because of the proxy)

Copy link
Member

Choose a reason for hiding this comment

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

(it's still very helpful to get these comments, as we can probably find somebody else from the community to take over this PR)

}
}

assert.throws(TypeError, evaluate);
Copy link
Contributor

Choose a reason for hiding this comment

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

Something is wrong here as we are not actually decorating a class as the description suggests.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants