Skip to content

fix(64058): fix reparse jsdoc @extends type arguments for call expressions - #64072

Open
Oleksandr Tarasiuk (a-tarasyuk) wants to merge 3 commits into
microsoft:mainfrom
a-tarasyuk:fix/64058
Open

fix(64058): fix reparse jsdoc @extends type arguments for call expressions#64072
Oleksandr Tarasiuk (a-tarasyuk) wants to merge 3 commits into
microsoft:mainfrom
a-tarasyuk:fix/64058

Conversation

@a-tarasyuk

@a-tarasyuk Oleksandr Tarasiuk (a-tarasyuk) commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Fixes #64058

Copilot AI balanced review requested due to automatic review settings August 28, 2026 08:01
@github-project-automation github-project-automation Bot moved this to Not started in PR Backlog Aug 28, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes JSDoc @extends type arguments for call-expression heritage clauses.

Changes:

  • Copies JSDoc type arguments onto the parsed heritage node.
  • Adds parser and compiler regression coverage.
  • Removes the previous property-access matching helper.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tsc/internal/parser/reparser.go Reparses @extends type arguments.
tsc/internal/ast/utilities.go Removes the old name-matching helper.
tsc/internal/parser/parser_test.go Tests reparsed AST linkage.
tsc/testdata/tests/cases/conformance/jsdoc/extendsTag7.ts Adds the compiler regression case.
tsc/testdata/baselines/reference/conformance/extendsTag7.types Records inferred types.
tsc/testdata/baselines/reference/conformance/extendsTag7.symbols Records resolved symbols.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

}
target.TypeArguments = p.newNodeList(source.TypeArguments.Loc, newArguments)
p.finishMutatedNode(target.AsNode())
if target.TypeArguments == nil && source.TypeArguments != nil {

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.

I think this problem has now been restored; does it need to still check that the call target is the right name?

(I haven't double checked Strada behavior here)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a bit of a tricky case :), because Strada mostly relies on the Effective resolution, and for call-expression heritage, it doesn't compare the @extends name with the call receiver.

It treats them separately, Playground:

  • A.extend() is checked as the runtime expression.
  • Other<string> from @extends is used as the effective base type.

So class B extends A.extend() with @extends {Other<string>} inherits from Other<string>, not A<string>, and no mismatch diagnostic is produced.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Comment thread tsc/internal/ast/utilities.go Outdated
func HasSamePropertyAccessName(target, source *Node) bool {
target = SkipParentheses(target)
source = SkipParentheses(source)
if IsCallExpression(target) {

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.

This is kind of a heuristic, since you could write something as goofy as Foo<string> and have extends Foo.Bar() and then it'd parameterize Foo?

Not sure how I feel about it. I actually didn't know the original code was so syntactic...

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.

Maybe the actual fix needs to be in the reparser more specifically?

target.TypeArguments = p.newNodeList(source.TypeArguments.Loc, newArguments)
p.finishMutatedNode(target.AsNode())
if target.TypeArguments == nil && source.TypeArguments != nil {
if ast.IsCallExpression(ast.SkipParentheses(target.Expression)) || ast.HasSamePropertyAccessName(target.Expression, source.Expression) {

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.

Strada handles this by using the @extends type as the effective base and checking the call expression separately; it doesn’t apply the JSDoc arguments to the call’s returned constructor.

function mixin(Base) {
    return class extends Base {};
}

/** @extends {A<string>} */
class B extends mixin(A) {}

This change appears to apply <string> to the non-generic returned class. Could we resolve the call first and only attach the arguments when its returned constructor is compatible with the @extends target?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Just to make sure I understand correctly: for @extends {A<string>} with extends mixin(A), should the JSDoc reparser synthesize the AST equivalent of:

class B extends (mixin(A) as typeof A<string>) {}

so that normal heritage checking treats A<string> as the base? Or should it preserve mixin(A) in the heritage AST and let the checker resolve the call, consult the @extends tag, and apply <string> only if the resolved constructor is compatible with A, otherwise keeping the anonymous constructor as the base?

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

Labels

For Backlog Bug PRs that fix a backlog bug

Projects

Status: Not started

Development

Successfully merging this pull request may close these issues.

TS7: @extends is ignored when the heritage is a call expression (Base.extend())

3 participants