Skip to content

[BUGFIX] Keep generic classes generic through extend() - #21727

Open
NullVoxPopuli wants to merge 1 commit into
mainfrom
nvp/extend-keep-generic-constructor
Open

NullVoxPopuli wants to merge 1 commit into
mainfrom
nvp/extend-keep-generic-constructor

Conversation

@NullVoxPopuli

@NullVoxPopuli NullVoxPopuli commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Note

Investigated with Claude (via Claude Code); corrections welcome.

Component.extend(Mixin) now keeps Component<S> generic, so a class that extends it can still take a signature. Today it can't. This matters most in JS, where TypeScript 7 reports TS2508 for this pattern and the component loses its signature.

/** @extends {Component<Signature>} */
export default class Foo extends Component.extend(Evented) {}

The change

  • Before: Readonly<Statics> & EmberClassConstructor<Instance> & MergeArray<M>. EmberClassConstructor<Instance> is new (owner?) => Instance, with Instance already fixed to Component<unknown>.
  • After: Statics & MergeArray<M>. Statics is the this type, so its generic construct signatures carry through.

Behavior change

Statics on the result are no longer Readonly. No type test relied on that. Adding Readonly<Statics> back to the intersection would not restore it: an intersection property is readonly only if every member is.

Why now

Testing

  • A new type test in type-tests/@ember/object-test/extend.ts covers class StringBox extends Box.extend({})<string> (through a const). It fails on main with the same TS2508 and passes with this change.
  • type-check:internals, type-check:internals-stable-decorators, build:types, type-check:types and type-check:types-stable-decorators all pass (TS 5.9.3).
  • type-check:types and type-check:types-stable-decorators also pass on TS 5.2.2 and 7.0.2, the two ends of the types-range CI matrix.
  • In ember-content-mapper's upstream-differences fixture, the classic Component.extend(Evented) component reports no errors on TS 7.1.0-dev.20261015.1 with these types. Today it reports TS2508 and TS2769.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

📊 Size report

Tarball size — 1.3 MB → 1.3 MB

dist/dev   No changes

File Before (Size / Brotli) After (Size / Brotli)
./packages/shared-chunks/api-{hash}.js 5.9 kB / 1.9 kB 358%↑27 kB / 222%↑6.1 kB
./packages/shared-chunks/index-{hash}.js 57.1 kB / 14.1 kB 10%↑62.6 kB / -10.9%↓12.5 kB
Total (Includes all files) 2.2 MB / 519.9 kB 2.2 MB / 519.9 kB

dist/prod   No changes

File Before (Size / Brotli) After (Size / Brotli)
Total (Includes all files) 2 MB / 476.6 kB 2 MB / 476.6 kB

smoke-tests/v2-app-template/dist   No changes

File Before (Size / Brotli) After (Size / Brotli)
Total (Includes all files) 359.3 kB / 97.1 kB 359.3 kB / 97.1 kB

smoke-tests/v2-app-hello-world-template/dist   No changes

File Before (Size / Brotli) After (Size / Brotli)
Total (Includes all files) 128.9 kB / 35.3 kB 128.9 kB / 35.3 kB

🤖 This report was automatically generated by wyvox/pkg-size

@NullVoxPopuli
NullVoxPopuli marked this pull request as ready for review October 15, 2026 15:27
`CoreObject.extend` rebuilt its return type from the instance type
(`new (owner?) => Instance`), which fixed a generic class's type
parameters. `Component.extend(Mixin)` then had a constructor that was not
generic, so `class Foo extends Component.extend(Mixin)` could not take
type arguments. In JS, TypeScript 7 reports TS2508 for
`/** @extends {Component<Signature>} */` over that heritage, and the
component loses its signature.

Return `Statics` (the `this` type) instead, which keeps the original
generic construct signatures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

1 participant