Skip to content

Commit

Permalink
feature: universal object crates
Browse files Browse the repository at this point in the history
  • Loading branch information
mr-feek committed Aug 5, 2020
1 parent e0f5595 commit f1d7195
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 2 deletions.
7 changes: 7 additions & 0 deletions config.xsd
Expand Up @@ -21,6 +21,7 @@
<xs:element name="issueHandlers" type="IssueHandlersType" minOccurs="0" maxOccurs="1" />
<xs:element name="ignoreExceptions" type="ExceptionsType" minOccurs="0" maxOccurs="1" />
<xs:element name="globals" type="GlobalsType" minOccurs="0" maxOccurs="1" />
<xs:element name="universalObjectCrates" type="UniversalObjectCratesType" minOccurs="0" maxOccurs="1" />
</xs:choice>

<xs:attribute name="autoloader" type="xs:string" />
Expand Down Expand Up @@ -127,6 +128,12 @@
</xs:sequence>
</xs:complexType>

<xs:complexType name="UniversalObjectCratesType">
<xs:sequence>
<xs:element name="class" maxOccurs="unbounded" type="NameAttributeType" />
</xs:sequence>
</xs:complexType>

<xs:complexType name="ExceptionsType">
<xs:sequence>
<xs:element name="class" minOccurs="0" maxOccurs="unbounded" type="ExceptionType" />
Expand Down
35 changes: 35 additions & 0 deletions src/Psalm/Config.php
Expand Up @@ -80,6 +80,7 @@
use const LIBXML_NONET;
use function is_a;
use const SCANDIR_SORT_NONE;
use function array_map;

/**
* @psalm-suppress PropertyNotSetInConstructor
Expand Down Expand Up @@ -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<int, class-string>
*/
protected $universal_object_crates = [
\stdClass::class,
SimpleXMLElement::class,
];

/**
* @var static|null
*/
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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<int, lowercase-string>
*/
public function getUniversalObjectCrates(): array
{
return array_map('strtolower', $this->universal_object_crates);
}
}
Expand Up @@ -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;
Expand Down Expand Up @@ -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
)
)
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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());

Expand Down
26 changes: 26 additions & 0 deletions tests/PropertyTypeTest.php
Expand Up @@ -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',
'<?php
class Foo { }
$f = new Foo();
// reads are fine
$f->bar;
// sets are fine
$f->buzz = false;
'
);

$this->analyzeFile('somefile.php', new Context());
}

/**
* @return void
*/
Expand Down

0 comments on commit f1d7195

Please sign in to comment.