From 2287695b444feb510ceab20bb44df7663fb79199 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Wed, 22 Jul 2026 20:39:18 +0800 Subject: [PATCH] refactor(constructor): enhance constructor property validation with existing method checks - Add detection of pre-existing __construct methods in class - Throw CompileTimeAttributeError when constructor property conflicts with existing method - Update test to use proper exception expectation pattern - Add new test case for parameter order independence - Create helper method to find declared constructors in class statements - Improve error messaging with specific class and method information --- .../code/constructor-existing-reordered.php | 16 ++++++++++++++++ phpunit/src/ClassTest.php | 15 ++++++++++++++- src/Transform/ConstructorLowering.php | 19 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 phpunit/code/constructor-existing-reordered.php diff --git a/phpunit/code/constructor-existing-reordered.php b/phpunit/code/constructor-existing-reordered.php new file mode 100644 index 00000000..59e7fadd --- /dev/null +++ b/phpunit/code/constructor-existing-reordered.php @@ -0,0 +1,16 @@ +id = $id; + $this->name = $name; + } +} diff --git a/phpunit/src/ClassTest.php b/phpunit/src/ClassTest.php index 2d4712c4..d36ad068 100644 --- a/phpunit/src/ClassTest.php +++ b/phpunit/src/ClassTest.php @@ -523,7 +523,20 @@ class ClassTest extends \BaseTest public function testConstructorRejectsExistingConstructor(): void { - $this->exec('Duplicate method `__construct`', 'constructor-existing.php'); + $this->expectException(\TypePhp\Exception\SyntaxError::class); + $this->expectExceptionMessage( + 'Constructor cannot generate InvalidConstructor::__construct(): method is already declared', + ); + $this->compile('constructor-existing.php'); + } + + public function testConstructorRejectsExistingConstructorRegardlessOfParameterOrder(): void + { + $this->expectException(\TypePhp\Exception\SyntaxError::class); + $this->expectExceptionMessage( + 'Constructor cannot generate ReorderedConstructor::__construct(): method is already declared', + ); + $this->compile('constructor-existing-reordered.php'); } public function testConstructorRejectsStaticProperties(): void diff --git a/src/Transform/ConstructorLowering.php b/src/Transform/ConstructorLowering.php index e6160679..48085493 100644 --- a/src/Transform/ConstructorLowering.php +++ b/src/Transform/ConstructorLowering.php @@ -33,6 +33,14 @@ final class ConstructorLowering public static function lowerClassLike(Stmt\Class_|Stmt\Trait_|Stmt\Enum_ $class): void { + $declaredConstructor = null; + foreach ($class->stmts as $stmt) { + if ($stmt instanceof Stmt\ClassMethod && $stmt->name->toLowerString() === '__construct') { + $declaredConstructor = $stmt; + break; + } + } + $properties = []; $target = null; foreach ($class->stmts as $stmt) { @@ -43,6 +51,17 @@ final class ConstructorLowering throw new SyntaxError('Constructor properties can only be declared in classes'); } $attribute = CompileTimeAttribute::find($stmt, 'Constructor'); + if ($declaredConstructor !== null) { + $className = $class->name?->toString() ?? 'anonymous class'; + throw new CompileTimeAttributeError( + "Constructor cannot generate {$className}::__construct(): method is already declared", + $stmt, + 'Constructor', + $attribute ?? $stmt, + null, + $declaredConstructor, + ); + } CompileTimeAttribute::consume($stmt, 'Constructor'); $target ??= $stmt; foreach ($stmt->props as $property) {