Skip to content

fix: keep parens around the class expression of new - #2554

Open
maoxor wants to merge 1 commit into
prettier:mainfrom
maoxor:fix-new-with-class-expression
Open

maoxor wants to merge 1 commit into
prettier:mainfrom
maoxor:fix-new-with-class-expression

Conversation

@maoxor

@maoxor maoxor commented Oct 29, 2026

Copy link
Copy Markdown

Since PHP 8.0, new accepts any expression in parentheses, but the printer only keeps the parentheses when that expression is a call or an array lookup. Laravel Prompts has

return new (static::$themes[static::$theme][$class] ?? static::$themes['default'][$class])($this);

which is printed as

return new static::$themes[static::$theme][$class] ??
    static::$themes["default"][$class]($this);

That still parses, but as (new static::$themes[static::$theme][$class]) ?? static::$themes["default"][$class]($this), so the fallback class is called as a function instead of being instantiated. Other forms change meaning the same way (new ($x . "Suffix")(), new ($cond ? A::class : B::class)(), new ($a->factory()->class)()) or stop parsing (new (Foo::BAR)(), new ("Foo")(), new ((string) $x)()).

Without parentheses, PHP only accepts a class name or a variable after new, optionally followed by property, nullsafe property or static property lookups. isNewClassReference checks for exactly that, and anything else keeps its parentheses. Calls and array lookups keep them as before, while new ($a->b)() and new (static::$class)() still lose them.

The new tests/new/class-expression.php covers all these cases; before the change, its snapshot had the outputs above. yarn test (including the standalone build), AST_COMPARE=1 yarn test:node and yarn lint pass, no other snapshot changes, and Laravel's Themes.php now formats to the same AST as the input (checked with nikic/php-parser) and is stable on a second run.

One case is still not handled: new (FOO)() with a constant FOO. php-parser gives the same name node as for new FOO(), so the printer can't tell them apart.

Without parentheses, `new` only takes a class name or a variable with property lookups, so `new ($a ?? $b)()` printed as `new $a ?? $b()` changed meaning, and `new (Foo::BAR)()` printed as `new Foo::BAR()` stopped parsing.

@Dextheking1 Dextheking1 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.

checked this one out. the paren drop is real: on the old printer $h formats to new static::$classes[$k] ?? Fallback::class($z), which calls the fallback as a function instead of instantiating it. with the fix, tests/new passes 34/34 and the new class-expression cases keep their parens, while plain cases like $i and $j stay bare. looks right.

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.

2 participants