From f1d719565d7279f44cfda1c0bfed2920bbda6226 Mon Sep 17 00:00:00 2001 From: Feek Date: Wed, 5 Aug 2020 15:52:20 -0700 Subject: [PATCH] feature: universal object crates --- config.xsd | 7 ++++ src/Psalm/Config.php | 35 +++++++++++++++++++ .../InstancePropertyAssignmentAnalyzer.php | 7 +++- .../Fetch/InstancePropertyFetchAnalyzer.php | 3 +- tests/PropertyTypeTest.php | 26 ++++++++++++++ 5 files changed, 76 insertions(+), 2 deletions(-) diff --git a/config.xsd b/config.xsd index 167bd38dcb6..91f007d2b5d 100644 --- a/config.xsd +++ b/config.xsd @@ -21,6 +21,7 @@ + @@ -127,6 +128,12 @@ + + + + + + diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index a2cbc88a701..9a34cbb07df 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -80,6 +80,7 @@ use const LIBXML_NONET; use function is_a; use const SCANDIR_SORT_NONE; +use function array_map; /** * @psalm-suppress PropertyNotSetInConstructor @@ -124,6 +125,15 @@ class Config 'MixedReturnTypeCoercion', ]; + /** + * These are special object classes that allow any and all properties to be get/set on them + * @var array + */ + protected $universal_object_crates = [ + \stdClass::class, + SimpleXMLElement::class, + ]; + /** * @var static|null */ @@ -958,6 +968,15 @@ private static function fromXmlAndPaths(string $base_dir, string $file_contents, } } + if (isset($config_xml->universalObjectCrates) && isset($config_xml->universalObjectCrates->class)) { + /** @var \SimpleXMLElement $universal_object_crate */ + foreach ($config_xml->universalObjectCrates->class as $universal_object_crate) { + /** @var class-string $classString */ + $classString = $universal_object_crate['name']; + $config->addUniversalObjectCrate($classString); + } + } + if (isset($config_xml->ignoreExceptions)) { if (isset($config_xml->ignoreExceptions->class)) { /** @var \SimpleXMLElement $exception_class */ @@ -2109,4 +2128,20 @@ private function getPHPVersionFromComposerJson(): ?string } return null; } + + /** + * @param class-string $class + */ + public function addUniversalObjectCrate(string $class): void + { + $this->universal_object_crates[] = $class; + } + + /** + * @return array + */ + public function getUniversalObjectCrates(): array + { + return array_map('strtolower', $this->universal_object_crates); + } } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php index 971466fcec0..afc98392a01 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php @@ -4,6 +4,7 @@ use PhpParser; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Stmt\PropertyProperty; +use Psalm\Config; use Psalm\Internal\Analyzer\ClassAnalyzer; use Psalm\Internal\Analyzer\ClassLikeAnalyzer; use Psalm\Internal\Analyzer\NamespaceAnalyzer; @@ -268,7 +269,11 @@ public static function analyze( ( in_array( strtolower($lhs_type_part->value), - ['stdclass', 'simplexmlelement', 'dateinterval', 'domdocument', 'domnode'], + Config::getInstance()->getUniversalObjectCrates() + [ + 'dateinterval', + 'domdocument', + 'domnode' + ], true ) ) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php index ba5021908ee..a752092b223 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php @@ -2,6 +2,7 @@ namespace Psalm\Internal\Analyzer\Statements\Expression\Fetch; use PhpParser; +use Psalm\Config; use Psalm\Internal\Analyzer\ClassLikeAnalyzer; use Psalm\Internal\Analyzer\FunctionLikeAnalyzer; use Psalm\Internal\Analyzer\NamespaceAnalyzer; @@ -397,7 +398,7 @@ public static function analyze( // but we don't want to throw an error // Hack has a similar issue: https://github.com/facebook/hhvm/issues/5164 if ($lhs_type_part instanceof TObject - || in_array(strtolower($lhs_type_part->value), ['stdclass', 'simplexmlelement'], true) + || in_array(strtolower($lhs_type_part->value), Config::getInstance()->getUniversalObjectCrates(), true) ) { $statements_analyzer->node_data->setType($stmt, Type::getMixed()); diff --git a/tests/PropertyTypeTest.php b/tests/PropertyTypeTest.php index 4b457e13127..94d2203bfac 100644 --- a/tests/PropertyTypeTest.php +++ b/tests/PropertyTypeTest.php @@ -127,6 +127,32 @@ public function getX(bool $b): int { $this->analyzeFile('somefile.php', new Context()); } + /** + * @return void + */ + public function testUniversalObjectCrates(): void + { + /** @var class-string $classString */ + $classString = 'Foo'; + Config::getInstance()->addUniversalObjectCrate($classString); + + $this->addFile( + 'somefile.php', + 'bar; + + // sets are fine + $f->buzz = false; + ' + ); + + $this->analyzeFile('somefile.php', new Context()); + } + /** * @return void */