Skip to content

fix(optimizer): class_exists() must not fold a trait name to true - #26

Merged
matyhtf merged 1 commit into
swoole:masterfrom
Giandonn:fix/class-exists-trait-fold
Aug 30, 2026
Merged

fix(optimizer): class_exists() must not fold a trait name to true#26
matyhtf merged 1 commit into
swoole:masterfrom
Giandonn:fix/class-exists-trait-fold

Conversation

@Giandonn

Copy link
Copy Markdown

Fixes #25

doFoldKnownClass folds class_exists() whenever the symbol table knows the
literal name. Traits live in that table, so a trait folded to true:

trait Helper {}

class_exists('Helper');   // folded to true
$name = 'Helper';
class_exists($name);      // reaches php::fn::class_exists, answers false

PHP answers false for both. So does this compiler's runtime - traits are
compile-time AST templates here, which is why
tests/compiler/stdlib/class_exists.phpt already expects trait_exists() to be
false. Only the fold disagreed, and only for a literal argument.

The change

A trait name folds to false. Classes and enums are untouched and keep folding
to true, matching PHP, where an enum is a class and a trait is not. Interfaces
already fell through to the runtime call, so they are unaffected.

Tests

  • tests/compiler/stdlib/class_exists.phpt gains the two missing cases: the
    trait name as a literal and through a variable. The file already covered
    class_exists for a class and trait_exists for a trait, but never
    class_exists for a trait.
  • phpunit/src/ClassExistsTraitFoldTest.php pins the fold decision in the
    generated C++ and runs without a native toolchain.

The PHPUnit test fails on master and passes with the change. The full suite
reports the same results as master, and PHPStan reports no new findings for the
touched file.

doFoldKnownClass folds class_exists() whenever the name is a literal the
symbol table knows. That table also holds traits, so a trait name folded
to true while PHP answers false:

    trait Helper {}

    class_exists('Helper');   // folded to true
    $name = 'Helper';
    class_exists($name);      // reaches php::fn::class_exists, answers false

The same program therefore gives two different answers for the same
trait, decided only by whether the argument is a literal.

The runtime side is already right, and deliberately so: traits are
compile-time AST templates in TypePHP, which is why
tests/compiler/stdlib/class_exists.phpt expects trait_exists() to be
false. Only the constant fold disagreed - with PHP and with the
compiler's own runtime.

A trait name now folds to false. Classes and enums keep folding to true,
which matches PHP: an enum is a class, a trait is not.

class_exists.phpt gains the literal and non-literal trait cases, and
ClassExistsTraitFoldTest pins the fold decision in the generated C++.
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.

class_exists() folds a trait name to true, disagreeing with PHP and with the compiler's own runtime

2 participants