Skip to content

Commit

Permalink
Psalm level 2 + type coersion bug fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
tuqqu committed Aug 31, 2024
1 parent e268f7b commit 51c715d
Show file tree
Hide file tree
Showing 24 changed files with 228 additions and 134 deletions.
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
"tuqqu/go-parser": "^0.5.1"
},
"require-dev": {
"symfony/var-dumper": "^7",
"friendsofphp/php-cs-fixer": "^3.48",
"symfony/var-dumper": "^7.1",
"friendsofphp/php-cs-fixer": "^3.63",
"phpunit/phpunit": "^10.5",
"vimeo/psalm": "^5.20"
"vimeo/psalm": "5.20"
},
"bin": [
"bin/go-php"
Expand Down
1 change: 1 addition & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
colors="true"
failOnRisky="true"
failOnWarning="true"
displayDetailsOnTestsThatTriggerWarnings="true"
>
<php>
<ini name="error_reporting" value="-1" />
Expand Down
6 changes: 4 additions & 2 deletions psalm.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<psalm
errorLevel="3"
errorLevel="2"
resolveFromConfigFile="true"
findUnusedBaselineEntry="false"
findUnusedCode="false"
Expand All @@ -16,6 +16,8 @@
</projectFiles>

<issueHandlers>
<ImplicitToStringCast errorLevel="suppress" />
<DocblockTypeContradiction errorLevel="suppress" />
<RedundantConditionGivenDocblockType errorLevel="suppress" />
<UnsafeInstantiation errorLevel="suppress" />
</issueHandlers>
</psalm>
2 changes: 1 addition & 1 deletion src/Arg.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use GoPhp\GoValue\AddressableValue;

/**
* @template V
* @template V of AddressableValue
*/
final class Arg
{
Expand Down
3 changes: 3 additions & 0 deletions src/ArgvBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public function markUnpacked(Invokable $func): void
};
}

/**
* @psalm-suppress ArgumentTypeCoercion
*/
public function build(): Argv
{
/** @var list<Arg> $argv */
Expand Down
4 changes: 2 additions & 2 deletions src/Builtin/BuiltinFunc/Append.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use GoPhp\GoValue\Slice\SliceValue;

use function array_slice;
use function GoPhp\assert_arg_type_for;
use function GoPhp\assert_arg_type;
use function GoPhp\assert_arg_value;
use function GoPhp\assert_argc;

Expand Down Expand Up @@ -43,7 +43,7 @@ public function __invoke(Argv $argv): SliceValue
$elems = array_slice($argv->values, 1);

foreach ($elems as $elem) {
assert_arg_type_for($elem->value, $slice->type->elemType, $this->name);
assert_arg_type($elem, $slice->type->elemType, $this->name);

$slice->append($elem->value);
}
Expand Down
12 changes: 6 additions & 6 deletions src/Builtin/BuiltinFunc/Cap.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,17 @@ public function __invoke(Argv $argv): IntValue
{
assert_argc($this, $argv, 1);

$capable = $argv[0];
$v = $argv[0];

if ($capable->value instanceof ArrayValue) {
return new IntValue($capable->value->len());
if ($v->value instanceof ArrayValue) {
return new IntValue($v->value->len());
}

if ($capable->value instanceof SliceValue) {
return new IntValue($capable->value->cap());
if ($v->value instanceof SliceValue) {
return new IntValue($v->value->cap());
}

throw RuntimeError::wrongArgumentType($capable, 'slice, array');
throw RuntimeError::wrongArgumentTypeForBuiltin($v->value, $this->name);
}

public function name(): string
Expand Down
12 changes: 5 additions & 7 deletions src/Builtin/BuiltinFunc/Complex.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@
namespace GoPhp\Builtin\BuiltinFunc;

use GoPhp\Argv;
use GoPhp\GoValue\Complex\ComplexNumber;
use GoPhp\GoValue\Complex\Complex128Value;
use GoPhp\GoValue\Complex\Complex64Value;
use GoPhp\GoValue\Complex\ComplexNumber;
use GoPhp\GoValue\Complex\UntypedComplexValue;
use GoPhp\GoValue\Float\Float32Value;
use GoPhp\GoValue\Float\Float64Value;

use function GoPhp\assert_arg_float;
use function GoPhp\assert_arg_type;
use function GoPhp\assert_argc;
use function GoPhp\assert_float_type;

/**
* @see https://pkg.go.dev/builtin#complex
Expand All @@ -30,12 +29,11 @@ public function __invoke(Argv $argv): ComplexNumber
assert_argc($this, $argv, 2);

[$real, $imag] = $argv;

assert_arg_float($real);
assert_arg_type($real, $real->value->type());

[$real, $imag] = [$real->value, $imag->value];

assert_float_type($real);
assert_float_type($imag);

if ($real instanceof Float32Value || $imag instanceof Float32Value) {
return new Complex64Value($real->unwrap(), $imag->unwrap());
}
Expand Down
5 changes: 3 additions & 2 deletions src/Builtin/BuiltinFunc/Copy.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ public function __invoke(Argv $argv): IntValue
$srcType = $src->value->type();

if ($srcType instanceof BasicType && $srcType->isString()) {
assert_arg_type($dst, new SliceType(NamedType::Byte));
/** @psalm-suppress InvalidArgument */
assert_arg_type($dst, new SliceType(NamedType::Byte), $this->name);
} else {
assert_arg_type($src, $dst->value->type());
assert_arg_type($src, $dst->value->type(), $this->name);
}

$dst = $dst->value;
Expand Down
2 changes: 2 additions & 0 deletions src/Builtin/BuiltinFunc/Delete.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace GoPhp\Builtin\BuiltinFunc;

use GoPhp\Argv;
use GoPhp\GoValue\Hashable;
use GoPhp\GoValue\Map\MapValue;
use GoPhp\GoValue\VoidValue;

Expand All @@ -28,6 +29,7 @@ public function __invoke(Argv $argv): VoidValue
$m = $argv[0]->value;
$key = $argv[1]->value;

/** @psalm-var Hashable $key */
$m->delete($key);

return VoidValue::get();
Expand Down
7 changes: 5 additions & 2 deletions src/Builtin/BuiltinFunc/Len.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
namespace GoPhp\Builtin\BuiltinFunc;

use GoPhp\Argv;
use GoPhp\Error\RuntimeError;
use GoPhp\GoValue\Int\IntValue;
use GoPhp\GoValue\Sequence;

use function GoPhp\assert_arg_value;
use function GoPhp\assert_argc;

/**
Expand All @@ -23,10 +23,13 @@ public function __construct(
public function __invoke(Argv $argv): IntValue
{
assert_argc($this, $argv, 1);
assert_arg_value($argv[0], Sequence::class, 'slice, array, string, map');

$v = $argv[0]->value;

if (!$v instanceof Sequence) {
throw RuntimeError::wrongArgumentTypeForBuiltin($v, $this->name);
}

return new IntValue($v->len());
}

Expand Down
9 changes: 5 additions & 4 deletions src/Builtin/BuiltinFunc/Make.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
use GoPhp\GoValue\Slice\SliceValue;
use GoPhp\GoValue\TypeValue;

use function GoPhp\assert_arg_int;
use function GoPhp\assert_arg_int_for_builtin;
use function GoPhp\assert_arg_value;
use function GoPhp\assert_argc;
use function GoPhp\assert_index_positive;
Expand Down Expand Up @@ -46,7 +46,7 @@ public function __invoke(Argv $argv): SliceValue|MapValue
$builder = SliceBuilder::fromType($type->type);

if (isset($argv[1])) {
assert_arg_int($argv[1]);
assert_arg_int_for_builtin($argv[1]);

$len = (int) $argv[1]->value->unwrap();

Expand All @@ -58,7 +58,7 @@ public function __invoke(Argv $argv): SliceValue|MapValue
}

if (isset($argv[2])) {
assert_arg_int($argv[2]);
assert_arg_int_for_builtin($argv[2]);

$cap = (int) $argv[2]->value->unwrap();

Expand Down Expand Up @@ -88,7 +88,8 @@ public function __invoke(Argv $argv): SliceValue|MapValue
return MapBuilder::fromType($type->type)->build();
}

throw RuntimeError::wrongArgumentType($argv[0], 'slice, map or channel');
/** @psalm-suppress InvalidArgument */
throw RuntimeError::wrongArgumentTypeForMake($argv[0]);
}

public function name(): string
Expand Down
69 changes: 60 additions & 9 deletions src/Error/RuntimeError.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ public static function expectedSliceInArgumentUnpacking(GoValue $value, Invokabl
return self::cannotUseArgumentAsType($value, $type, $name);
}

public static function cannotUseArgumentAsType(GoValue $value, GoType|string $type, string $func): self
public static function cannotUseArgumentAsType(GoValue $value, GoType|string $type, ?string $func): self
{
return new self(
sprintf(
'cannot use %s as type %s in argument to %s',
self::valueToString($value),
is_string($type) ? $type : $type->name(),
$func,
$func ?? 'function',
),
);
}
Expand Down Expand Up @@ -310,14 +310,56 @@ public static function wrongArgumentType(Arg $arg, string|GoType $expectedType):
{
return new self(
sprintf(
'invalid argument %d (%s), expected %s',
'invalid argument: %d (%s), expected %s',
$arg->pos,
$arg->value->type()->name(),
self::valueToString($arg->value),
is_string($expectedType) ? $expectedType : $expectedType->name(),
),
);
}

public static function wrongArgumentTypeForBuiltin(GoValue $value, string $func): self
{
return new self(
sprintf(
'invalid argument: %s for built-in %s',
self::valueToString($value),
$func,
),
);
}

public static function wrongArgumentTypeForMake(Arg $arg): self
{
return new self(
sprintf(
'invalid argument: cannot make %s; type must be slice, map, or channel',
$arg->value->type()->name(),
),
);
}

public static function nonFloatingPointArgument(GoValue $value): self
{
return new self(
sprintf(
'invalid argument: arguments have type %s, expected floating-point',
self::valueToString($value),
),
);
}

//todo
public static function wrongFirstArgumentTypeForAppend(Arg $arg): self
{
return new self(
sprintf(
'first argument to append must be a slice; have %s',
self::valueToString($arg->value),
),
);
}

public static function indexNegative(GoValue|int $value): self
{
return new self(
Expand Down Expand Up @@ -580,6 +622,9 @@ public static function mixedReturnParams(): self
return new self('mixed named and unnamed parameters');
}

/**
* @param list<GoValue> $returnValues
*/
public static function wrongReturnValueNumber(array $returnValues, Params $params): self
{
return self::wrongFuncArity($returnValues, $params, 'return values');
Expand Down Expand Up @@ -625,18 +670,24 @@ final protected static function fullName(string $name, string $selector): string
return sprintf('%s.%s', $name, $selector);
}

/**
* @param Argv|list<GoValue> $values
*/
protected static function wrongFuncArity(
Argv|array $values,
Params $params,
string $type,
): self {
[$len, $values] = is_array($values)
? [count($values), $values]
: [$values->argc, $values->values];

$msg = $params->len > $len ?
'not enough ' :
'too many ';
: [$values->argc, array_map(
static fn(Arg $arg): GoValue => $arg->value,
$values->values,
)];

$msg = $params->len > $len
? 'not enough '
: 'too many ';

$msg .= sprintf(
"%s\nhave (%s)\nwant (%s)",
Expand Down
1 change: 1 addition & 0 deletions src/GoType/NamedType.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ public function isCompatible(GoType $other): bool
},
self::Float64,
self::Float32 => match ($other) {
UntypedType::UntypedInt,
UntypedType::UntypedFloat,
UntypedType::UntypedRoundFloat => true,
default => $this->equals($other),
Expand Down
6 changes: 3 additions & 3 deletions src/GoValue/BoolValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
use function GoPhp\assert_values_compatible;

/**
* @template-implements Hashable<bool>
* @template-implements Hashable<int>
* @template-implements AddressableValue<bool>
*/
final class BoolValue implements Hashable, Castable, Sealable, AddressableValue
Expand Down Expand Up @@ -125,9 +125,9 @@ private function logicAnd(self $other): self
return new self($this->value && $other->value);
}

public function hash(): bool
public function hash(): int
{
return $this->unwrap();
return (int) $this->unwrap();
}

public function cast(NamedType $to): self
Expand Down
6 changes: 3 additions & 3 deletions src/GoValue/Complex/ComplexNumber.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

/**
* @psalm-type ComplexTuple = array{float, float}
* @template-implements Hashable<float>
* @template-implements Hashable<int>
* @template-implements AddressableValue<ComplexTuple>
*/
abstract class ComplexNumber implements Hashable, Castable, Sealable, AddressableValue
Expand Down Expand Up @@ -67,10 +67,10 @@ public function toString(): string
);
}

final public function hash(): float
final public function hash(): int
{
// Cantor Pairing Hash
return 0.5 * ($this->real + $this->imag) * ($this->real + $this->imag + 1) + $this->imag;
return (int) (100 * ($this->real + $this->imag) * ($this->real + $this->imag + 1) + $this->imag);
}

final public function cast(NamedType $to): self
Expand Down
Loading

0 comments on commit 51c715d

Please sign in to comment.