Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/4.x' into upstream-master6
Browse files Browse the repository at this point in the history
  • Loading branch information
orklah committed Jan 26, 2022
2 parents ac29afc + 5cd5255 commit 9168cef
Show file tree
Hide file tree
Showing 15 changed files with 295 additions and 65 deletions.
1 change: 1 addition & 0 deletions config.xsd
Expand Up @@ -81,6 +81,7 @@
<xs:attribute name="reportInfo" type="xs:boolean" default="true" />
<xs:attribute name="restrictReturnTypes" type="xs:boolean" default="false" />
<xs:attribute name="limitMethodComplexity" type="xs:boolean" default="false" />
<xs:attribute name="disableSuppressAll" type="xs:boolean" default="false" />
<xs:attribute name="triggerErrorExits" type="TriggerErrorExitsType" default="default" />
</xs:complexType>

Expand Down
4 changes: 2 additions & 2 deletions dictionaries/CallMap.php
Expand Up @@ -10054,7 +10054,7 @@
'Phar::delMetadata' => ['bool'],
'Phar::extractTo' => ['bool', 'pathto'=>'string', 'files='=>'string|array|null', 'overwrite='=>'bool'],
'Phar::getAlias' => ['string'],
'Phar::getMetadata' => ['mixed'],
'Phar::getMetadata' => ['mixed', 'unserializeOptions='=>'array'],
'Phar::getModified' => ['bool'],
'Phar::getPath' => ['string'],
'Phar::getSignature' => ['array{hash:string, hash_type:string}'],
Expand Down Expand Up @@ -10122,7 +10122,7 @@
'PharFileInfo::getCompressedSize' => ['int'],
'PharFileInfo::getContent' => ['string'],
'PharFileInfo::getCRC32' => ['int'],
'PharFileInfo::getMetadata' => ['mixed'],
'PharFileInfo::getMetadata' => ['mixed', 'unserializeOptions='=>'array'],
'PharFileInfo::getPharFlags' => ['int'],
'PharFileInfo::hasMetadata' => ['bool'],
'PharFileInfo::isCompressed' => ['bool', 'compression_type='=>'int'],
Expand Down
8 changes: 8 additions & 0 deletions dictionaries/CallMap_80_delta.php
Expand Up @@ -93,6 +93,14 @@
'old' => ['bool', 'mode'=>'int'],
'new' => ['bool', 'mode'=>'int', '...args='=>'mixed'],
],
'Phar::getMetadata' => [
'old' => ['mixed'],
'new' => ['mixed', 'unserializeOptions='=>'array'],
],
'PharFileInfo::getMetadata' => [
'old' => ['mixed'],
'new' => ['mixed', 'unserializeOptions='=>'array'],
],
'ReflectionClass::getConstants' => [
'old' => ['array<string,mixed>'],
'new' => ['array<string,mixed>', 'filter='=>'?int'],
Expand Down
9 changes: 9 additions & 0 deletions docs/running_psalm/configuration.md
Expand Up @@ -143,6 +143,15 @@ Setting this to `false` means that any function calls will cause Psalm to forget
```
When `true`, strings can be used as classes, meaning `$some_string::someMethod()` is allowed. If `false`, only class constant strings (of the form `Foo\Bar::class`) can stand in for classes, otherwise an `InvalidStringClass` issue is emitted. Defaults to `false`.

#### disableSuppressAll

```xml
<psalm
disableSuppressAll="[bool]"
>
```
When `true`, disables wildcard suppression of all issues with `@psalm-suppress all`. Defaults to `false`.

#### memoizeMethodCallResults

```xml
Expand Down
10 changes: 9 additions & 1 deletion psalm-baseline.xml
Expand Up @@ -41,6 +41,10 @@
</PossiblyUndefinedIntArrayOffset>
</file>
<file src="src/Psalm/Internal/Analyzer/ClassAnalyzer.php">
<DeprecatedProperty occurrences="2">
<code>$storage-&gt;template_extended_count</code>
<code>$storage-&gt;template_extended_count</code>
</DeprecatedProperty>
<PossiblyUndefinedIntArrayOffset occurrences="3">
<code>$comments[0]</code>
<code>$stmt-&gt;props[0]</code>
Expand Down Expand Up @@ -279,7 +283,11 @@
</PossiblyUndefinedIntArrayOffset>
</file>
<file src="src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php">
<PossiblyUndefinedIntArrayOffset occurrences="3">
<DeprecatedProperty occurrences="1">
<code>$storage-&gt;template_extended_count</code>
</DeprecatedProperty>
<PossiblyUndefinedIntArrayOffset occurrences="4">
<code>$imported_type_data[3]</code>
<code>$l[4]</code>
<code>$r[4]</code>
<code>$var_line_parts[0]</code>
Expand Down
6 changes: 6 additions & 0 deletions src/Psalm/Config.php
Expand Up @@ -327,6 +327,11 @@ class Config
*/
public $allow_string_standin_for_class = false;

/**
* @var bool
*/
public $disable_suppress_all = false;

/**
* @var bool
*/
Expand Down Expand Up @@ -913,6 +918,7 @@ private static function fromXmlAndPaths(
'strictBinaryOperands' => 'strict_binary_operands',
'rememberPropertyAssignmentsAfterCall' => 'remember_property_assignments_after_call',
'allowStringToStandInForClass' => 'allow_string_standin_for_class',
'disableSuppressAll' => 'disable_suppress_all',
'usePhpDocMethodsWithoutMagicCall' => 'use_phpdoc_method_without_magic_or_parent',
'usePhpDocPropertiesWithoutMagicCall' => 'use_phpdoc_property_without_magic_or_parent',
'memoizeMethodCallResults' => 'memoize_method_calls',
Expand Down
Expand Up @@ -2,6 +2,8 @@

namespace Psalm\Internal\Analyzer\Statements\Expression\Call\Method;

use Exception;
use PDOException;
use PhpParser;
use Psalm\CodeLocation;
use Psalm\Codebase;
Expand All @@ -27,6 +29,7 @@
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TTemplateParam;
use Psalm\Type\Union;
use Throwable;
use UnexpectedValueException;

use function array_filter;
Expand Down Expand Up @@ -95,6 +98,16 @@ public static function fetch(
}
}

if ($premixin_method_id->method_name === 'getcode'
&& $premixin_method_id->fq_class_name !== Exception::class
&& in_array(Throwable::class, $class_storage->class_implements)) {
if ($premixin_method_id->fq_class_name === PDOException::class) {
return Type::getString();
} else {
return Type::getInt(true); // TODO: Remove the flag in Psalm 5
}
}

if ($declaring_method_id && $declaring_method_id !== $method_id) {
$declaring_fq_class_name = $declaring_method_id->fq_class_name;
$declaring_method_name = $declaring_method_id->method_name;
Expand Down
Expand Up @@ -489,37 +489,16 @@ private static function handleNamedCall(
$class_storage->final
);

$old_data_provider = $statements_analyzer->node_data;

$statements_analyzer->node_data = clone $statements_analyzer->node_data;

$context->vars_in_scope['$tmp_mixin_var'] = $new_lhs_type;

$fake_method_call_expr = new VirtualMethodCall(
new VirtualVariable(
'tmp_mixin_var',
$stmt->class->getAttributes()
),
return self::forwardCallToInstanceMethod(
$statements_analyzer,
$stmt,
$stmt_name,
$stmt->getArgs(),
$stmt->getAttributes()
$context,
'tmp_mixin_var',
true
);

if (MethodCallAnalyzer::analyze(
$statements_analyzer,
$fake_method_call_expr,
$context
) === false) {
return false;
}

$fake_method_call_type = $statements_analyzer->node_data->getType($fake_method_call_expr);

$statements_analyzer->node_data = $old_data_provider;

$statements_analyzer->node_data->setType($stmt, $fake_method_call_type ?? Type::getMixed());

return true;
}
}
}
Expand Down Expand Up @@ -713,6 +692,44 @@ private static function handleNamedCall(
if ($pseudo_method_storage->return_type) {
return true;
}
} elseif ($stmt->class instanceof PhpParser\Node\Name && $stmt->class->parts[0] === 'parent'
&& !$codebase->methodExists($method_id)
&& !$statements_analyzer->isStatic()
) {
// In case of parent::xxx() call on instance method context (i.e. not static context)
// with nonexistent method, we try to forward to instance method call for resolve pseudo method.

// Use parent type as static type for the method call
$context->vars_in_scope['$tmp_parent_var'] = new Union([$lhs_type_part]);

if (self::forwardCallToInstanceMethod(
$statements_analyzer,
$stmt,
$stmt_name,
$context,
'tmp_parent_var'
) === false) {
return false;
}

// Resolve actual static return type according to caller (i.e. $this) static type
if (isset($context->vars_in_scope['$this'])
&& $method_call_type = $statements_analyzer->node_data->getType($stmt)
) {
$method_call_type = clone $method_call_type;

foreach ($method_call_type->getAtomicTypes() as $name => $type) {
if ($type instanceof TNamedObject && $type->is_static && $type->value === $fq_class_name) {
// Replace parent&static type to actual static type
$method_call_type->removeType($name);
$method_call_type->addType($context->vars_in_scope['$this']->getSingleAtomic());
}
}

$statements_analyzer->node_data->setType($stmt, $method_call_type);
}

return true;
}

if (!$context->check_methods) {
Expand Down Expand Up @@ -823,37 +840,12 @@ private static function handleNamedCall(
}

if ($is_dynamic_this_method) {
$old_data_provider = $statements_analyzer->node_data;

$statements_analyzer->node_data = clone $statements_analyzer->node_data;

$fake_method_call_expr = new VirtualMethodCall(
new VirtualVariable(
'this',
$stmt->class->getAttributes()
),
$stmt_name,
$stmt->getArgs(),
$stmt->getAttributes()
);

if (MethodCallAnalyzer::analyze(
return self::forwardCallToInstanceMethod(
$statements_analyzer,
$fake_method_call_expr,
$stmt,
$stmt_name,
$context
) === false) {
return false;
}

$fake_method_call_type = $statements_analyzer->node_data->getType($fake_method_call_expr);

$statements_analyzer->node_data = $old_data_provider;

if ($fake_method_call_type) {
$statements_analyzer->node_data->setType($stmt, $fake_method_call_type);
}

return true;
);
}
}

Expand Down Expand Up @@ -1089,4 +1081,59 @@ private static function findPseudoMethodAndClassStorages(

return null;
}

/**
* Forward static call to instance call, using `VirtualMethodCall` and `MethodCallAnalyzer::analyze()`
* The resolved method return type will be set as type of the $stmt node.
*
* @param StatementsAnalyzer $statements_analyzer
* @param PhpParser\Node\Expr\StaticCall $stmt
* @param PhpParser\Node\Identifier $stmt_name
* @param Context $context
* @param string $virtual_var_name Temporary var name to use for create the fake MethodCall statement.
* @param bool $always_set_node_type If true, when the method has no declared typed, mixed will be set on node.
*
* @return bool Result of analysis. False if the call is invalid.
*
* @see MethodCallAnalyzer::analyze()
*/
private static function forwardCallToInstanceMethod(
StatementsAnalyzer $statements_analyzer,
PhpParser\Node\Expr\StaticCall $stmt,
PhpParser\Node\Identifier $stmt_name,
Context $context,
string $virtual_var_name = 'this',
bool $always_set_node_type = false
): bool {
$old_data_provider = $statements_analyzer->node_data;

$statements_analyzer->node_data = clone $statements_analyzer->node_data;

$fake_method_call_expr = new VirtualMethodCall(
new VirtualVariable($virtual_var_name, $stmt->class->getAttributes()),
$stmt_name,
$stmt->getArgs(),
$stmt->getAttributes()
);

if (MethodCallAnalyzer::analyze(
$statements_analyzer,
$fake_method_call_expr,
$context
) === false) {
return false;
}

$fake_method_call_type = $statements_analyzer->node_data->getType($fake_method_call_expr);

$statements_analyzer->node_data = $old_data_provider;

if ($fake_method_call_type) {
$statements_analyzer->node_data->setType($stmt, $fake_method_call_type);
} elseif ($always_set_node_type) {
$statements_analyzer->node_data->setType($stmt, Type::getMixed());
}

return true;
}
}
Expand Up @@ -23,6 +23,7 @@
use function count;
use function is_string;
use function max;
use function mb_strcut;

/**
* @internal
Expand All @@ -47,6 +48,8 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev
return Type::getMixed();
}

$is_replace = mb_strcut($event->getFunctionId(), 6, 7) === 'replace';

$inner_value_types = [];
$inner_key_types = [];

Expand Down Expand Up @@ -107,7 +110,11 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev

foreach ($unpacked_type_part->properties as $key => $type) {
if (!is_string($key)) {
$generic_properties[] = $type;
if ($is_replace) {
$generic_properties[$key] = $type;
} else {
$generic_properties[] = $type;
}
continue;
}

Expand Down
4 changes: 3 additions & 1 deletion src/Psalm/IssueBuffer.php
Expand Up @@ -211,7 +211,9 @@ public static function isSuppressed(CodeIssue $e, array $suppressed_issues = [])
}
}

$suppress_all_position = array_search('all', $suppressed_issues);
$suppress_all_position = $config->disable_suppress_all
? false
: array_search('all', $suppressed_issues);

if ($suppress_all_position !== false) {
if (is_int($suppress_all_position)) {
Expand Down
1 change: 1 addition & 0 deletions src/Psalm/Storage/ClassLikeStorage.php
Expand Up @@ -361,6 +361,7 @@ class ClassLikeStorage
public $template_extended_params;

/**
* @deprecated Will be replaced with $template_type_extends_count in Psalm v5
* @var ?int
*/
public $template_extended_count;
Expand Down
8 changes: 4 additions & 4 deletions tests/ArrayFunctionCallTest.php
Expand Up @@ -210,9 +210,9 @@ function getInts(): array{ return []; }
],
'arrayMergeIntArrays' => [
'code' => '<?php
$d = array_merge(["a", "b", "c"], [1, 2, 3]);',
$d = array_merge(["a", "b", "c", "d"], [1, 2, 3]);',
'assertions' => [
'$d' => 'array{0: string, 1: string, 2: string, 3: int, 4: int, 5: int}',
'$d' => 'array{0: string, 1: string, 2: string, 3: string, 4: int, 5: int, 6: int}',
],
],
'arrayMergePossiblyUndefined' => [
Expand Down Expand Up @@ -266,9 +266,9 @@ public function merge($a, $b): array
],
'arrayReplaceIntArrays' => [
'code' => '<?php
$d = array_replace(["a", "b", "c"], [1, 2, 3]);',
$d = array_replace(["a", "b", "c", "d"], [1, 2, 3]);',
'assertions' => [
'$d' => 'array{0: string, 1: string, 2: string, 3: int, 4: int, 5: int}',
'$d' => 'array{0: int, 1: int, 2: int, 3: string}',
],
],
'arrayReplacePossiblyUndefined' => [
Expand Down

0 comments on commit 9168cef

Please sign in to comment.