Skip to content

Commit

Permalink
Merge branch '4.x' into upstream-master
Browse files Browse the repository at this point in the history
  • Loading branch information
weirdan committed Feb 11, 2022
2 parents 6d05766 + fb1fd84 commit 11e60fa
Show file tree
Hide file tree
Showing 20 changed files with 206 additions and 46 deletions.
2 changes: 1 addition & 1 deletion dictionaries/CallMap.php
Expand Up @@ -14855,7 +14855,7 @@
'trader_wclprice' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_willr' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'],
'trader_wma' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trait_exists' => ['?bool', 'trait'=>'string', 'autoload='=>'bool'],
'trait_exists' => ['bool', 'trait'=>'string', 'autoload='=>'bool'],
'Transliterator::create' => ['?Transliterator', 'id'=>'string', 'direction='=>'int'],
'Transliterator::createFromRules' => ['?Transliterator', 'rules'=>'string', 'direction='=>'int'],
'Transliterator::createInverse' => ['Transliterator'],
Expand Down
2 changes: 1 addition & 1 deletion dictionaries/CallMap_historical.php
Expand Up @@ -15941,7 +15941,7 @@
'trader_wclprice' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_willr' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'],
'trader_wma' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trait_exists' => ['?bool', 'trait'=>'string', 'autoload='=>'bool'],
'trait_exists' => ['bool', 'trait'=>'string', 'autoload='=>'bool'],
'transliterator_create' => ['?Transliterator', 'id'=>'string', 'direction='=>'int'],
'transliterator_create_from_rules' => ['?Transliterator', 'rules'=>'string', 'direction='=>'int'],
'transliterator_create_inverse' => ['Transliterator', 'transliterator'=>'Transliterator'],
Expand Down
Expand Up @@ -8,7 +8,6 @@ This issue is emitted when a method overriding a native method is defined withou

```php
<?php

class A implements JsonSerializable {
public function jsonSerialize() {
return ['type' => 'A'];
Expand Down
8 changes: 8 additions & 0 deletions psalm-baseline.xml
Expand Up @@ -17,6 +17,9 @@
<code>$matches[0]</code>
<code>$symbol_parts[1]</code>
</PossiblyUndefinedIntArrayOffset>
<PossiblyUnusedProperty occurrences="1">
<code>$analysis_php_version_id</code>
</PossiblyUnusedProperty>
</file>
<file src="src/Psalm/Config/FileFilter.php">
<PossiblyUndefinedIntArrayOffset occurrences="1">
Expand Down Expand Up @@ -317,6 +320,11 @@
<code>array_keys($template_type_map[$template_param_name])[0]</code>
</PossiblyUndefinedIntArrayOffset>
</file>
<file src="src/Psalm/Issue/MethodSignatureMustProvideReturnType.php">
<UnusedClass occurrences="1">
<code>MethodSignatureMustProvideReturnType</code>
</UnusedClass>
</file>
<file src="src/Psalm/Node/Stmt/VirtualClass.php">
<PropertyNotSetInConstructor occurrences="1">
<code>VirtualClass</code>
Expand Down
3 changes: 1 addition & 2 deletions src/Psalm/Config/FileFilter.php
Expand Up @@ -177,8 +177,7 @@ public static function loadFromArray(
}

throw new ConfigException(
'Could not resolve config path to ' . $base_dir
. DIRECTORY_SEPARATOR . $directory_path
'Could not resolve config path to ' . $prospective_directory_path
);
}

Expand Down
9 changes: 1 addition & 8 deletions src/Psalm/Internal/Analyzer/MethodComparator.php
Expand Up @@ -886,14 +886,7 @@ private static function compareMethodSignatureReturnTypes(
$implementer_signature_return_type,
$guide_signature_return_type
)
: (!$implementer_signature_return_type
&& $guide_signature_return_type->isMixed()
? false
: UnionTypeComparator::isContainedByInPhp(
$implementer_signature_return_type,
$guide_signature_return_type
)
);
: UnionTypeComparator::isContainedByInPhp($implementer_signature_return_type, $guide_signature_return_type);

if (!$is_contained_by) {
if ($codebase->analysis_php_version_id >= 8_00_00
Expand Down
Expand Up @@ -38,6 +38,7 @@
use Psalm\Plugin\EventHandler\Event\AddRemoveTaintsEvent;
use Psalm\Plugin\EventHandler\Event\AfterEveryFunctionCallAnalysisEvent;
use Psalm\Storage\FunctionLikeParameter;
use Psalm\Storage\FunctionStorage;
use Psalm\Storage\Possibilities;
use Psalm\Type;
use Psalm\Type\Atomic;
Expand Down Expand Up @@ -487,6 +488,8 @@ private static function handleNamedFunction(
$is_maybe_root_function = !$function_name instanceof PhpParser\Node\Name\FullyQualified
&& count($function_name->parts) === 1;

$args = $stmt->isFirstClassCallable() ? [] : $stmt->getArgs();

if (!$function_call_info->in_call_map) {
$predefined_functions = $codebase->config->getPredefinedFunctions();
$is_predefined = isset($predefined_functions[strtolower($original_function_id)])
Expand All @@ -498,11 +501,10 @@ private static function handleNamedFunction(
$function_call_info->function_id,
$code_location,
$is_maybe_root_function
) === false
) {
if (ArgumentsAnalyzer::analyze(
) === false) {
if ($args && ArgumentsAnalyzer::analyze(
$statements_analyzer,
$stmt->getArgs(),
$args,
null,
null,
true,
Expand Down Expand Up @@ -662,14 +664,23 @@ private static function getAnalyzeNamedExpression(
}

if ($var_type_part instanceof TClosure || $var_type_part instanceof TCallable) {
if (!$var_type_part->is_pure && ($context->pure || $context->mutation_free)) {
IssueBuffer::maybeAdd(
new ImpureFunctionCall(
'Cannot call an impure function from a mutation-free context',
new CodeLocation($statements_analyzer->getSource(), $stmt)
),
$statements_analyzer->getSuppressedIssues()
);
if (!$var_type_part->is_pure) {
if ($context->pure || $context->mutation_free) {
IssueBuffer::maybeAdd(
new ImpureFunctionCall(
'Cannot call an impure function from a mutation-free context',
new CodeLocation($statements_analyzer->getSource(), $stmt)
),
$statements_analyzer->getSuppressedIssues()
);
}

if (!$function_call_info->function_storage) {
$function_call_info->function_storage = new FunctionStorage();
}

$function_call_info->function_storage->pure = false;
$function_call_info->function_storage->mutation_free = false;
}

$function_call_info->function_params = $var_type_part->params;
Expand Down
Expand Up @@ -489,14 +489,15 @@ private static function handleNamedCall(
$class_storage->final
);

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

return self::forwardCallToInstanceMethod(
$statements_analyzer,
$stmt,
$stmt_name,
$context,
'tmp_mixin_var',
$mixin_context,
'__tmp_mixin_var__',
true
);
}
Expand Down Expand Up @@ -700,18 +701,21 @@ private static function handleNamedCall(
// 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]);
$tmp_context = clone $context;
$tmp_context->vars_in_scope['$__tmp_parent_var__'] = new Union([$lhs_type_part]);

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

unset($tmp_context);

// 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)
Expand Down
Expand Up @@ -226,6 +226,7 @@ public static function analyze(
if ($type instanceof Scalar) {
$keyed_array = new TKeyedArray([new Union([$type])]);
$keyed_array->is_list = true;
$keyed_array->sealed = true;
$permissible_atomic_types[] = $keyed_array;
} elseif ($type instanceof TNull) {
$permissible_atomic_types[] = new TArray([Type::getNever(), Type::getNever()]);
Expand Down
Expand Up @@ -16,6 +16,8 @@
use Psalm\Type\Atomic\TNonEmptyString;
use Psalm\Type\Union;

use function dirname;

/**
* @internal
*/
Expand Down Expand Up @@ -87,10 +89,16 @@ public static function analyze(
} else {
$statements_analyzer->node_data->setType($stmt, new Union([new TCallableString]));
}
} elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\File
|| $stmt instanceof PhpParser\Node\Scalar\MagicConst\Dir
) {
$statements_analyzer->node_data->setType($stmt, new Union([new TNonEmptyString()]));
} elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Dir) {
$statements_analyzer->node_data->setType(
$stmt,
Type::getString(dirname($statements_analyzer->getSource()->getFilePath()))
);
} elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\File) {
$statements_analyzer->node_data->setType(
$stmt,
Type::getString($statements_analyzer->getSource()->getFilePath())
);
} elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Trait_) {
if ($statements_analyzer->getSource() instanceof TraitAnalyzer) {
$statements_analyzer->node_data->setType($stmt, new Union([new TNonEmptyString()]));
Expand Down
Expand Up @@ -433,6 +433,18 @@ public static function infer(
}
}

if ($stmt instanceof PhpParser\Node\Expr\New_) {
$resolved_class_name = $stmt->class->getAttribute('resolvedName');

if (!is_string($resolved_class_name)) {
return null;
}

return new Union([
new Type\Atomic\TNamedObject($resolved_class_name)
]);
}

return null;
}

Expand Down
20 changes: 15 additions & 5 deletions src/Psalm/IssueBuffer.php
Expand Up @@ -694,7 +694,7 @@ function (IssueData $d1, IssueData $d2): int {
: $error_count . ' errors'
) . ' found' . "\n";
} else {
self::printSuccessMessage();
self::printSuccessMessage($project_analyzer);
}

$show_info = $project_analyzer->stdout_report_options->show_info;
Expand Down Expand Up @@ -785,8 +785,12 @@ function (IssueData $d1, IssueData $d2): int {
}
}

public static function printSuccessMessage(): void
public static function printSuccessMessage(ProjectAnalyzer $project_analyzer): void
{
if (!$project_analyzer->stdout_report_options) {
throw new UnexpectedValueException('Cannot print success message without stdout report options');
}

// this message will be printed
$message = "No errors found!";

Expand All @@ -811,9 +815,15 @@ public static function printSuccessMessage(): void
// text style, 1 = bold
$style = "1";

echo "\e[{$background};{$style}m{$paddingTop}\e[0m" . "\n";
echo "\e[{$background};{$foreground};{$style}m{$messageWithPadding}\e[0m" . "\n";
echo "\e[{$background};{$style}m{$paddingBottom}\e[0m" . "\n";
if ($project_analyzer->stdout_report_options->use_color) {
echo "\e[{$background};{$style}m{$paddingTop}\e[0m" . "\n";
echo "\e[{$background};{$foreground};{$style}m{$messageWithPadding}\e[0m" . "\n";
echo "\e[{$background};{$style}m{$paddingBottom}\e[0m" . "\n";
} else {
echo "\n";
echo "$messageWithPadding\n";
echo "\n";
}
}

/**
Expand Down
7 changes: 7 additions & 0 deletions stubs/Php81.phpstub
Expand Up @@ -56,6 +56,13 @@ namespace {
*/
public function getBackingValue(): int|string;
}

class ReflectionIntersectionType extends ReflectionType {
/**
* @return non-empty-list<ReflectionType>
*/
public function getTypes() {}
}
}

namespace FTP {
Expand Down
5 changes: 5 additions & 0 deletions tests/ClosureTest.php
Expand Up @@ -798,6 +798,11 @@ public static function __callStatic(string $name, array $args): mixed {
'ignored_issues' => [],
'php_version' => '8.1'
],
'unknownFirstClassCallable' => [
'code' => '<?php
/** @psalm-suppress UndefinedFunction */
unknown(...);',
],
];
}

Expand Down
41 changes: 41 additions & 0 deletions tests/ConstantTest.php
Expand Up @@ -2,9 +2,14 @@

namespace Psalm\Tests;

use Psalm\Context;
use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait;
use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait;

use function getcwd;

use const DIRECTORY_SEPARATOR;

class ConstantTest extends TestCase
{
use InvalidCodeAnalysisTestTrait;
Expand Down Expand Up @@ -42,6 +47,42 @@ class ConstantTest extends TestCase
// $this->analyzeFile($file_path, new Context());
// }

public function testUseObjectConstant(): void
{
$file1 = getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'file1.php';
$file2 = getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'file2.php';

$this->addFile(
$file1,
'<?php
namespace Foo;
final class Bar {}
const bar = new Bar();
'
);

$this->addFile(
$file2,
'<?php
namespace Baz;
use Foo\Bar;
use const Foo\bar;
require("tests/file1.php");
function bar(): Bar
{
return bar;
}
'
);

$this->analyzeFile($file1, new Context());
$this->analyzeFile($file2, new Context());
}

/**
* @return iterable<string,array{code:string,assertions?:array<string,string>,ignored_issues?:list<string>, php_version?: string}>
*/
Expand Down
8 changes: 4 additions & 4 deletions tests/DocumentationTest.php
Expand Up @@ -218,10 +218,6 @@ public function testInvalidCode($code, $error_message, $error_levels = [], $chec
$this->markTestSkipped();
}

if (strpos($error_message, 'MethodSignatureMustProvideReturnType') !== false) {
$php_version = '8.1';
}

$this->project_analyzer->setPhpVersion($php_version, 'tests');

if ($check_references) {
Expand Down Expand Up @@ -290,6 +286,10 @@ public function providerInvalidCodeParse(): array
case 'TraitMethodSignatureMismatch':
continue 2;

/** @todo reinstate this test when the issue is restored */
case 'MethodSignatureMustProvideReturnType':
continue 2;

case 'InvalidFalsableReturnType':
$ignored_issues = ['FalsableReturnStatement'];
break;
Expand Down

0 comments on commit 11e60fa

Please sign in to comment.