Skip to content

Fix new-expression emit parentheses after type erasure (#64981) - #65015

Open
timurzvural wants to merge 2 commits into
microsoft:mainfrom
timurzvural:fix/64981-new-expression-parens
Open

timurzvural wants to merge 2 commits into
microsoft:mainfrom
timurzvural:fix/64981-new-expression-parens

Conversation

@timurzvural

Copy link
Copy Markdown

Summary

Fixes #64981 — new (f() as T).C() drops required parentheses on emit.

new (getPlugins() as any).Linker() was emitted as new getPlugins().Linker() (i.e. (new getPlugins()).Linker()), causing a runtime TypeError.

Change:

  • In emitNewExpression, parenthesize when the leftmost expression (stopping at calls) is a CallExpression.
  • Walk KindParenthesizedExpression in GetLeftmostExpression so parentheses / assertion wrappers are handled consistently with classic TS6 emit: new (getPlugins().Linker)().

Test plan

  • Added Go unit test TestParenthesizeNew4 in printer_test.go
  • Added type-eraser cases NewExpression3–NewExpression6 covering as / satisfies / non-null
  • CI / existing test suite

Relates to

This focused PR replaces the emit portion of the combined #65013 (which is being closed in favor of two single-issue PRs).

AI assistance disclosure

This change was authored with Cursor Agent (an LLM-based coding agent). A human operator directed the investigation at this specific open issue, reviewed the resulting patch, and is submitting it for review. Per CONTRIBUTING.md, AI tool use is disclosed here.

Parenthesize new-expression operands whose leftmost expression is a
call, including after as/satisfies/non-null erasure and through
parentheses, so `new (f() as T).C()` emits as `new (f().C)()`.

AI assistance disclosure: this change was authored with Cursor Agent.
Copilot AI balanced review requested due to automatic review settings October 29, 2026 19:23
@github-project-automation github-project-automation Bot moved this to Not started in PR Backlog Oct 29, 2026
@typescript-automation typescript-automation Bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Oct 29, 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.

🟡 Changes recommended

Traversing retained parentheses causes emitConciseBody to double-wrap already-parenthesized object literals, breaking two unchanged arrow-function output baselines.

1 open issue
What changed in this PR

This PR addresses #64981 in the native TypeScript compiler by preserving constructor grouping when type erasure exposes a call expression.

Changes:

  • Detects calls along the leftmost constructor-expression chain and adds parentheses during emit.
  • Extends shared expression traversal to look through parentheses.
  • Adds printer and type-erasure regression tests for property-access constructors.
File Description
tsc/​internal/​transformers/​tstransforms/​typeeraser_test.go Adds constructor cases covering assertions, satisfies, and non-null expressions.
tsc/​internal/​printer/​printer.go Parenthesizes constructor expressions with leftmost calls.
tsc/​internal/​printer/​printer_test.go Tests printing a constructor accessed from a call result.
tsc/​internal/​ast/​precedence.go Extends leftmost-expression traversal through parentheses.

Review effort: Balanced

Comment thread tsc/internal/ast/precedence.go Outdated
KindPropertyAccessExpression,
KindNonNullExpression,
KindPartiallyEmittedExpression,
KindParenthesizedExpression,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks — addressed in 9391b36.

  • ast.GetLeftmostExpression no longer walks KindParenthesizedExpression (shared helper keeps its parentheses boundary so emitConciseBody does not double-wrap () => ({...})).
  • New-expression call detection is printer-local via newExpressionOperandNeedsParens, which walks parentheses / property-element access / as / satisfies / non-null / partially-emitted only for emitNewExpression.
  • NewExpression6 still expects new ((f()).C)() because non-null does not elide the original parentheses the way as/satisfies do.
  • Arrow object-literal baselines remain correct with this approach.

…s in GetLeftmostExpression

Address Copilot review on microsoft#65015: do not teach GetLeftmostExpression to
traverse ParenthesizedExpression (that double-wrapped arrow concise bodies
like `() => ({...})`). Detect leftmost calls for `new` in a printer-local
helper that may walk parentheses, preserving emitConciseBody behavior while
still emitting `new (f().C)()` after type erasure.

AI assistance disclosure: authored with Cursor Agent.

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

For Uncommitted Bug PR for untriaged, rejected, closed or missing bug

Projects

Status: Not started

Development

Successfully merging this pull request may close these issues.

TS7+ drops required parentheses in new (f() as T).C(), emitting incorrectly as new f().C()

3 participants