diff --git a/phpunit/code/class-exists-class-and-enum.php b/phpunit/code/class-exists-class-and-enum.php new file mode 100644 index 00000000..234deb17 --- /dev/null +++ b/phpunit/code/class-exists-class-and-enum.php @@ -0,0 +1,22 @@ +compileToCpp('class-exists-trait.php'); + + // The trait is known at compile time, so the call is still folded - + // just to the answer PHP gives, which is false for a trait. + self::assertStringNotContainsString('php::fn::class_exists(', $cpp); + self::assertStringContainsString('= false;', $cpp); + } + + public function testClassAndEnumNamesStillFoldToTrue(): void + { + $cpp = $this->compileToCpp('class-exists-class-and-enum.php'); + + self::assertStringNotContainsString('php::fn::class_exists(', $cpp); + self::assertStringNotContainsString('= false;', $cpp); + } + + private function compileToCpp(string $file): string + { + global $translator; + + $compiler = CompilerTest::create(TYPEPHP_ROOT_PATH); + $translator = $compiler; + $source = TYPEPHP_ROOT_PATH . '/phpunit/code/' . $file; + $compiler->addFiles([$source]); + $compiler->prepareFile($source); + + return file_get_contents($compiler->convertFile($source)); + } +} diff --git a/src/Optimizer/FuncCallOptimizer.php b/src/Optimizer/FuncCallOptimizer.php index fe2ae690..423f0270 100644 --- a/src/Optimizer/FuncCallOptimizer.php +++ b/src/Optimizer/FuncCallOptimizer.php @@ -667,6 +667,11 @@ trait FuncCallOptimizer if (!$this->isScalarString($cn) || !$this->hasClass($cn->value)) { return false; } + // The class table also carries traits, but a trait is not a class to + // class_exists(): PHP answers false for it and true for an enum. + if ($this->getClassDef($cn->value)?->trait !== null) { + return 'false'; + } return $this->isNativeObjectClass($cn->value) ? 'false' : 'true'; } diff --git a/tests/compiler/stdlib/class_exists.phpt b/tests/compiler/stdlib/class_exists.phpt index bfab60a7..039466d1 100644 --- a/tests/compiler/stdlib/class_exists.phpt +++ b/tests/compiler/stdlib/class_exists.phpt @@ -16,6 +16,12 @@ function main() { var_dump(class_exists("NonexistentClass")); var_dump(class_exists("NonexistentClass", false)); + // A trait is not a class. The answer must not depend on whether the name + // is a literal the compiler can resolve at compile time. + var_dump(class_exists("MyTrait")); + $traitName = "MyTrait"; + var_dump(class_exists($traitName)); + // interface_exists var_dump(interface_exists("MyInterface")); var_dump(interface_exists("NonexistentInterface")); @@ -38,6 +44,8 @@ bool(true) bool(true) bool(false) bool(false) +bool(false) +bool(false) bool(true) bool(false) bool(false)