diff --git a/phpunit/src/ClassTest.php b/phpunit/src/ClassTest.php index b74146c2..9332bf01 100644 --- a/phpunit/src/ClassTest.php +++ b/phpunit/src/ClassTest.php @@ -26,4 +26,43 @@ class ClassTest extends \BaseTest { $this->exec('Cannot override private method `Base::doWork()`', 'override-private-method.php'); } + + public function testSelfCanBePartOfUnionType() + { + global $translator; + $compiler = \PhpAot\Php\CompilerTest::create(ROOT_PATH); + $translator = $compiler; + $testFile = __DIR__ . '/../code/union_type_self_allowed.php'; + $compiler->addFiles([$testFile]); + $compiler->prepareFile($testFile); + $compiler->convertFile($testFile); + $this->addToAssertionCount(1); + } + + public function testParentCanBePartOfUnionType() + { + global $translator; + $compiler = \PhpAot\Php\CompilerTest::create(ROOT_PATH); + $translator = $compiler; + $testFile = __DIR__ . '/../code/union_type_parent_allowed.php'; + $compiler->addFiles([$testFile]); + $compiler->prepareFile($testFile); + $compiler->convertFile($testFile); + $this->addToAssertionCount(1); + } + + public function testSelfCannotBePartOfIntersectionType() + { + $this->exec("Type 'self' cannot be part of an intersection type", 'intersection_type_self_not_allowed.php'); + } + + public function testParentCannotBePartOfIntersectionType() + { + $this->exec("Type 'parent' cannot be part of an intersection type", 'intersection_type_parent_not_allowed.php'); + } + + public function testStaticCannotBePartOfIntersectionType() + { + $this->exec("Type 'static' cannot be part of an intersection type", 'intersection_type_static_not_allowed.php'); + } } diff --git a/src/Php/Generator/TypeCheckGenerator.php b/src/Php/Generator/TypeCheckGenerator.php index c8bb9d09..eb706d49 100644 --- a/src/Php/Generator/TypeCheckGenerator.php +++ b/src/Php/Generator/TypeCheckGenerator.php @@ -37,6 +37,12 @@ trait TypeCheckGenerator $check[] = count($clause) === 1 ? $clause[0] : ['kind' => 'allOf', 'types' => $clause]; } } elseif ($typeNode instanceof IntersectionType) { + foreach ($typeNode->types as $subType) { + $nameLower = strtolower($this->parseIdentifier($subType)); + if ($nameLower === 'self' || $nameLower === 'parent' || $nameLower === 'static') { + $this->fatalError($subType, "Type '{$nameLower}' cannot be part of an intersection type"); + } + } $clause = $this->buildTypeCheckClause($typeNode); if (!empty($clause)) { $check[] = count($clause) === 1 ? $clause[0] : ['kind' => 'allOf', 'types' => $clause]; @@ -180,6 +186,15 @@ trait TypeCheckGenerator return '(' . implode(' && ', $conditions) . ')'; } + protected function getTypeCheckCallableName(): string + { + if ($this->classDef) { + return $this->classDef->getNamespacedName(false) . '::' . $this->functionDef->name; + } + + return $this->functionDef->getNamespacedName(); + } + protected function genUnionParamCheck(ArgInfo $argInfo, int $argIndex): string { if (empty($argInfo->typeCheck)) { @@ -251,7 +266,7 @@ trait TypeCheckGenerator protected function genUnionParamTypeErrorExpr(ArgInfo $argInfo, string $valueExpr, string $argNoExpr): string { - $fnName = $this->functionDef->getNamespacedName(); + $fnName = $this->getTypeCheckCallableName(); return 'php::concat({' . 'php::Str(' . $this->genCharPtr($fnName . '(): Argument #', true) . '), ' . 'php::toString(' . $argNoExpr . '), ' @@ -282,7 +297,7 @@ trait TypeCheckGenerator } $orExpr = implode(' || ', $conditions); - $fnName = $this->functionDef->getNamespacedName(); + $fnName = $this->getTypeCheckCallableName(); $typeStr = $this->functionDef->returnTypeStr; $msgExpr = 'php::concat(php::concat(php::Str(' . $this->genCharPtr($fnName, true) . ' "(): Return value must be of type " '