Skip to content

Fix documentation inheritance for static method overrides on mixin intersection types#3161

Open
Copilot wants to merge 3 commits intomainfrom
copilot/fix-mixins-overrides-docs
Open

Fix documentation inheritance for static method overrides on mixin intersection types#3161
Copilot wants to merge 3 commits intomainfrom
copilot/fix-mixins-overrides-docs

Conversation

Copy link
Contributor

Copilot AI commented Mar 19, 2026

When a class extends a mixin returning an intersection type (e.g., typeof MixinClass & T), overridden static methods lose inherited documentation. This happens because GetBaseTypes resolves the intersection constructor type to the return type of the first construct signature, which may not contain all static members from the full intersection.

declare class BaseClass {
    /** some documentation */
    static method(): number;
}

class MixinClass {}
declare function Mix<T extends AnyConstructor>(BaseClass: T): typeof MixinClass & T;

declare class Mixed extends Mix(BaseClass) {
    static method(): number;
}

Mixed.method;
//    ^ No documentation shown

Changes

  • internal/ls/hover.go: When the primary GetBaseTypes loop fails to find a static property in any base type, fall back to resolving the extends clause expression directly via GetTypeAtLocation(expr). This yields the full constructor-side intersection type, on which GetPropertyOfType can find static properties across all constituents. The fallback is scoped to only the extends clause (not implements) since static members are inherited from the base class, and is guarded by a foundInBase check to avoid unnecessary work in common cases.
  • internal/fourslash/tests/hoverMixinOverrideDocumentation_test.go: Test verifying documentation inheritance through mixin overrides.
Original prompt

This section details on the original issue you should resolve

<issue_title>Mixins Overrides Drop Documentation</issue_title>
<issue_description>Note: This is a refiling of microsoft/TypeScript#60871 in accordance to the instructions to follow after being closed in microsoft/TypeScript#62827.

Search Terms

mixins overrides, documentation dropped

Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about documentation, mixins, etc.

Playground

https://www.typescriptlang.org/play/?ts=5.7.2#code/CYUwxgNghgTiAEkoGdnwEIpAYWq+A3gFDynwD0AVJfMgPYC2CwdYArkwHYAuU3AlnU7xK5EmWS8BYeE24ALOsAAUASgBc8ThwBGIGAG4iAXyJFuATwAOCAIKcL2IZJhsw3OjHgBeeFB0uUO5aIADu8MoAdNGwAObImlAOANoAuqo+AHzwdDoAVuDcZgBmbJzugsIAsvwAHgA8ACrwILXcIJzAaPaOztyu7p6ZypjIOHgJ8I0ZxKT+gcFI+DW1-Jy4KGit7Z1oo+ObhOKkYH0DHjBRMTDxiSnpR2QSbDaX0ZFxyKpGT6akpuI4Nw2DBqnU1htUEYAeRyPAAPJeBieBDIfgMKwQCzwCx0NiIJLwUIwfjtdREWGICbwFYQ6kEUyU0BIODwUrlARCGl1JotNodLrwHpOTguNwXYb7SGTaaaSw2OjFbmrdbUgBkUyMRGZ0FZSzQKxAwD5O0FKxGWGlM3Ekj4-BkckUKg0Wl0+mhZkNwEijqURkpZAAevAAHJ0eAsdhcKSVSJAA

Code

declare class BaseClass {
    /** some documentation */
    static method(): number;
}

type AnyConstructor = abstract new (...args: any[]) => object

function Mix<T extends AnyConstructor>(BaseClass: T) {
  abstract class MixinClass extends BaseClass {
    constructor(...args: any[]) {
      super(...args);
    }
  }

  return MixinClass;
}

// Or more simply you can write:
class MixinClass {}
declare function Mix<T extends AnyConstructor>(BaseClass: T): typeof MixinClass & T;

declare class Mixed extends Mix(BaseClass) {
  static method(): number;
}

Mixed.method;
//    ^ No documentation.

Actual behavior

Overrides of a mixin class does not have documentation.

Notably if you don't override the method the documentation does show up. This shows it's possible to get it.

Expected behavior

It should inherit documentation.</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

When a class extends a mixin that returns an intersection type (e.g.,
typeof MixinClass & T), overridden static methods lose their inherited
documentation because baseType.Symbol() returns nil for intersection
types, preventing access to the constructor/static side.

Add a fallback that uses heritage clause expressions to get the
constructor type directly via GetTypeAtLocation, allowing
GetPropertyOfType to find static properties across intersection
type constituents.

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Copilot AI changed the title [WIP] [TS-60871] Fix mixins overrides documentation Fix documentation inheritance for static method overrides on mixin intersection types Mar 19, 2026
Copilot AI requested a review from jakebailey March 19, 2026 06:57
@jakebailey jakebailey marked this pull request as ready for review March 19, 2026 18:37
Copilot AI review requested due to automatic review settings March 19, 2026 18:37
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes missing inherited hover documentation for overridden static methods when a class extends a mixin whose return type is an intersection (e.g., typeof MixinClass & T), where the base type’s symbol can be nil and prevents static-side doc lookup.

Changes:

  • Add a fallback path in hover JSDoc resolution for static members to resolve constructor-side types via heritage clause expressions.
  • Add a fourslash test covering hover documentation inheritance for mixin intersection override scenarios.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
internal/ls/hover.go Adds fallback static-member doc lookup via heritage clause expression type resolution when base type symbols are missing (intersection types).
internal/fourslash/tests/hoverMixinOverrideDocumentation_test.go Adds regression test ensuring hover/QuickInfo shows inherited docs for overridden static methods from mixin intersection bases.

Comment on lines +580 to +593
if isStatic {
for _, superTypeNode := range getAllSuperTypeNodes(node.Parent) {
if superTypeNode == nil {
continue
}
if expr := superTypeNode.Expression(); expr != nil {
if prop := c.GetPropertyOfType(c.GetTypeAtLocation(expr), symbol.Name); prop != nil && prop.ValueDeclaration != nil {
if jsDoc := getJSDocOrTag(c, prop.ValueDeclaration); jsDoc != nil {
return jsDoc
}
}
}
}
}
Comment on lines +577 to +580
// For static members on intersection base types (e.g., from mixins),
// baseType.Symbol() may be nil, preventing access to the constructor type.
// Fall back to using heritage clause expressions to get the constructor type directly.
if isStatic {
@jakebailey
Copy link
Member

@copilot address feedback

… with foundInBase

- Use GetClassExtendsHeritageElement instead of getAllSuperTypeNodes to only
  look at the extends clause (not implements) for static member doc inheritance.
- Track whether the property was found in any base type and only run the
  fallback when it wasn't, avoiding unnecessary GetTypeAtLocation work in
  common cases.
- Update comment to accurately describe the root cause: GetBaseTypes resolves
  to the return type of the first construct signature, which may not contain
  all static members from an intersection constructor type.

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 19, 2026

@copilot address feedback

Addressed both review comments in 9fa5170:

  1. Only extends, not implements: Replaced getAllSuperTypeNodes with ast.GetClassExtendsHeritageElement so the fallback only looks at the extends clause.

  2. Avoid unnecessary work: Added foundInBase tracking so the fallback only runs when the property wasn't found in any base type via the primary GetBaseTypes loop. Note: the original suggestion to check for nil symbols wasn't the right guard here — debugging showed GetBaseTypes resolves the intersection constructor to the return type of the first construct signature (e.g., MixinClass), which has a non-nil symbol but simply doesn't contain all static members from the intersection. !foundInBase correctly captures this.

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.

Mixins Overrides Drop Documentation

3 participants