diff --git a/composer.json b/composer.json index 7f350e6b2..c68ea5c89 100644 --- a/composer.json +++ b/composer.json @@ -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" diff --git a/phpunit.xml b/phpunit.xml index 3292f695e..a8b417892 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,6 +6,7 @@ colors="true" failOnRisky="true" failOnWarning="true" + displayDetailsOnTestsThatTriggerWarnings="true" > diff --git a/psalm.xml b/psalm.xml index a4ef816b6..3aa6b3638 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,6 +1,6 @@ - + + + diff --git a/src/Arg.php b/src/Arg.php index 9a13e0f0a..5d003f96f 100644 --- a/src/Arg.php +++ b/src/Arg.php @@ -7,7 +7,7 @@ use GoPhp\GoValue\AddressableValue; /** - * @template V + * @template V of AddressableValue */ final class Arg { diff --git a/src/ArgvBuilder.php b/src/ArgvBuilder.php index e832511a2..c6ccdaee8 100644 --- a/src/ArgvBuilder.php +++ b/src/ArgvBuilder.php @@ -52,6 +52,9 @@ public function markUnpacked(Invokable $func): void }; } + /** + * @psalm-suppress ArgumentTypeCoercion + */ public function build(): Argv { /** @var list $argv */ diff --git a/src/Builtin/BuiltinFunc/Append.php b/src/Builtin/BuiltinFunc/Append.php index ebd3ce042..465f050bb 100644 --- a/src/Builtin/BuiltinFunc/Append.php +++ b/src/Builtin/BuiltinFunc/Append.php @@ -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; @@ -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); } diff --git a/src/Builtin/BuiltinFunc/Cap.php b/src/Builtin/BuiltinFunc/Cap.php index 88c03e661..abaddad63 100644 --- a/src/Builtin/BuiltinFunc/Cap.php +++ b/src/Builtin/BuiltinFunc/Cap.php @@ -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 diff --git a/src/Builtin/BuiltinFunc/Complex.php b/src/Builtin/BuiltinFunc/Complex.php index 8145886fe..35fd78364 100644 --- a/src/Builtin/BuiltinFunc/Complex.php +++ b/src/Builtin/BuiltinFunc/Complex.php @@ -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 @@ -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()); } diff --git a/src/Builtin/BuiltinFunc/Copy.php b/src/Builtin/BuiltinFunc/Copy.php index 4841af725..06de56425 100644 --- a/src/Builtin/BuiltinFunc/Copy.php +++ b/src/Builtin/BuiltinFunc/Copy.php @@ -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; diff --git a/src/Builtin/BuiltinFunc/Delete.php b/src/Builtin/BuiltinFunc/Delete.php index cf2bd662c..9b3b03e2a 100644 --- a/src/Builtin/BuiltinFunc/Delete.php +++ b/src/Builtin/BuiltinFunc/Delete.php @@ -5,6 +5,7 @@ namespace GoPhp\Builtin\BuiltinFunc; use GoPhp\Argv; +use GoPhp\GoValue\Hashable; use GoPhp\GoValue\Map\MapValue; use GoPhp\GoValue\VoidValue; @@ -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(); diff --git a/src/Builtin/BuiltinFunc/Len.php b/src/Builtin/BuiltinFunc/Len.php index 38bae748b..ae35bc3b7 100644 --- a/src/Builtin/BuiltinFunc/Len.php +++ b/src/Builtin/BuiltinFunc/Len.php @@ -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; /** @@ -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()); } diff --git a/src/Builtin/BuiltinFunc/Make.php b/src/Builtin/BuiltinFunc/Make.php index 3b46a7861..a2ed844e2 100644 --- a/src/Builtin/BuiltinFunc/Make.php +++ b/src/Builtin/BuiltinFunc/Make.php @@ -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; @@ -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(); @@ -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(); @@ -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 diff --git a/src/Error/RuntimeError.php b/src/Error/RuntimeError.php index 680e9b6d9..04568f9f9 100644 --- a/src/Error/RuntimeError.php +++ b/src/Error/RuntimeError.php @@ -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', ), ); } @@ -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( @@ -580,6 +622,9 @@ public static function mixedReturnParams(): self return new self('mixed named and unnamed parameters'); } + /** + * @param list $returnValues + */ public static function wrongReturnValueNumber(array $returnValues, Params $params): self { return self::wrongFuncArity($returnValues, $params, 'return values'); @@ -625,6 +670,9 @@ final protected static function fullName(string $name, string $selector): string return sprintf('%s.%s', $name, $selector); } + /** + * @param Argv|list $values + */ protected static function wrongFuncArity( Argv|array $values, Params $params, @@ -632,11 +680,14 @@ protected static function wrongFuncArity( ): 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)", diff --git a/src/GoType/NamedType.php b/src/GoType/NamedType.php index 4479e244c..a25d65c81 100644 --- a/src/GoType/NamedType.php +++ b/src/GoType/NamedType.php @@ -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), diff --git a/src/GoValue/BoolValue.php b/src/GoValue/BoolValue.php index c236ca73d..a4297b5db 100644 --- a/src/GoValue/BoolValue.php +++ b/src/GoValue/BoolValue.php @@ -11,7 +11,7 @@ use function GoPhp\assert_values_compatible; /** - * @template-implements Hashable + * @template-implements Hashable * @template-implements AddressableValue */ final class BoolValue implements Hashable, Castable, Sealable, AddressableValue @@ -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 diff --git a/src/GoValue/Complex/ComplexNumber.php b/src/GoValue/Complex/ComplexNumber.php index 8dcc89d35..16167ef6a 100644 --- a/src/GoValue/Complex/ComplexNumber.php +++ b/src/GoValue/Complex/ComplexNumber.php @@ -28,7 +28,7 @@ /** * @psalm-type ComplexTuple = array{float, float} - * @template-implements Hashable + * @template-implements Hashable * @template-implements AddressableValue */ abstract class ComplexNumber implements Hashable, Castable, Sealable, AddressableValue @@ -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 diff --git a/src/GoValue/Func/Func.php b/src/GoValue/Func/Func.php index caa411862..f41bce6f0 100644 --- a/src/GoValue/Func/Func.php +++ b/src/GoValue/Func/Func.php @@ -75,7 +75,7 @@ public function __invoke(Argv $argv): GoValue $defaultValue = $param->type->zeroValue(); $namedReturns[] = $defaultValue; - /** @var string $param->name */ + /** @psalm-assert string $param->name */ $env->defineVar( $param->name, $defaultValue, diff --git a/src/GoValue/Hashable.php b/src/GoValue/Hashable.php index 0e946af40..bdeea4585 100644 --- a/src/GoValue/Hashable.php +++ b/src/GoValue/Hashable.php @@ -5,7 +5,7 @@ namespace GoPhp\GoValue; /** - * @psalm-type Hash = string|int|float|bool + * @psalm-type Hash = string|int * @template H = Hash */ interface Hashable @@ -13,5 +13,5 @@ interface Hashable /** * @return H */ - public function hash(): string|int|float|bool; + public function hash(): string|int; } diff --git a/src/GoValue/Map/Map.php b/src/GoValue/Map/Map.php index 1e8548459..364e91627 100644 --- a/src/GoValue/Map/Map.php +++ b/src/GoValue/Map/Map.php @@ -9,7 +9,7 @@ use GoPhp\GoValue\Sequence; /** - * @template K of Hashable&GoValue + * @template K of GoValue&Hashable * @template V of GoValue * * @template-extends Sequence @@ -19,16 +19,16 @@ interface Map extends Sequence /** * @param K $at */ - public function has(Hashable&GoValue $at): bool; + public function has(GoValue&Hashable $at): bool; /** * @param K $at */ - public function delete(Hashable&GoValue $at): void; + public function delete(GoValue&Hashable $at): void; /** * @param V $value * @param K $at */ - public function set(GoValue $value, Hashable&GoValue $at): void; + public function set(GoValue $value, GoValue&Hashable $at): void; } diff --git a/src/GoValue/SimpleNumber.php b/src/GoValue/SimpleNumber.php index 076b5f1cd..081509401 100644 --- a/src/GoValue/SimpleNumber.php +++ b/src/GoValue/SimpleNumber.php @@ -31,7 +31,7 @@ /** * @template N = int|float * - * @template-implements Hashable + * @template-implements Hashable * @template-implements AddressableValue */ abstract class SimpleNumber implements Hashable, Castable, Sealable, Typeable, AddressableValue @@ -124,9 +124,9 @@ final public function mutate(Operator $op, GoValue $rhs): void }; } - final public function hash(): int|float + final public function hash(): int { - return $this->unwrap(); + return (int) $this->unwrap(); } public function cast(NamedType $to): self|ComplexNumber diff --git a/src/Interpreter.php b/src/Interpreter.php index d125af965..b9fb788bd 100644 --- a/src/Interpreter.php +++ b/src/Interpreter.php @@ -134,7 +134,7 @@ final class Interpreter private bool $constContext = false; private int $switchContext = 0; private ?FuncValue $entryPoint = null; - private Ast $ast; + private ?Ast $ast = null; public function __construct( private readonly string $source, @@ -293,6 +293,8 @@ private function checkEntryPoint(): void private function evalDeclsInOrder(): void { + $this->assertAstIsSet(); + foreach ($this->ast->imports as $import) { $this->evalImportDeclStmt($import); } @@ -338,6 +340,7 @@ private function evalDeclsInOrder(): void private function evalImportDeclStmt(ImportDecl $decl): void { + $this->assertAstIsSet(); $mainAst = $this->ast; foreach (iter_spec($decl->spec) as $spec) { @@ -426,7 +429,7 @@ private function evalConstDeclStmt(ConstDecl $decl): None throw RuntimeError::invalidConstantType($type); } - if (!empty($spec->initList->exprs)) { + if ($spec->initList !== null && !empty($spec->initList->exprs)) { $initExprs = $spec->initList->exprs; } @@ -866,7 +869,7 @@ private function evalWithEnvWrap(?Environment $env, callable $code): StmtJump private function evalReturnStmt(ReturnStmt $stmt): ReturnJump { - if (empty($stmt->exprList->exprs)) { + if ($stmt->exprList === null || empty($stmt->exprList->exprs)) { return ReturnJump::fromVoid(); } @@ -1605,4 +1608,16 @@ private static function init(): void $init = true; self::$noneJump = new None(); } + + /** + * @psalm-assert !null $this->ast + */ + private function assertAstIsSet(): void + { + if ($this->ast !== null) { + return; + } + + throw new InternalError('AST is not set'); + } } diff --git a/src/StmtJump/ReturnJump.php b/src/StmtJump/ReturnJump.php index bf61108d8..d18dd1c67 100644 --- a/src/StmtJump/ReturnJump.php +++ b/src/StmtJump/ReturnJump.php @@ -60,7 +60,7 @@ public static function fromMultiple(array $values): self } /** - * @return V[] + * @return list */ public function values(): array { diff --git a/src/TypeResolver.php b/src/TypeResolver.php index ac1b8fda7..843d48e5b 100644 --- a/src/TypeResolver.php +++ b/src/TypeResolver.php @@ -195,16 +195,18 @@ private function resolveInterfaceType(AstInterfaceType $interfaceType, bool $com // fixme use composite param $methods = []; foreach ($interfaceType->items as $item) { - if ($item instanceof TypeTerm) { - throw InternalError::unimplemented(); - } - - if ($item instanceof MethodElem) { - if (isset($methods[$item->methodName->name])) { - throw RuntimeError::duplicateMethod($item->methodName->name); - } - - $methods[$item->methodName->name] = $this->resolveTypeFromAstSignature($item->signature); + switch (true) { + case $item instanceof TypeTerm: + throw InternalError::unimplemented(); + case $item instanceof MethodElem: + if (isset($methods[$item->methodName->name])) { + throw RuntimeError::duplicateMethod($item->methodName->name); + } + + $methods[$item->methodName->name] = $this->resolveTypeFromAstSignature($item->signature); + break; + default: + throw InternalError::unreachable($item); } } diff --git a/src/asserts.php b/src/asserts.php index 3aeb916ca..e90c932e8 100644 --- a/src/asserts.php +++ b/src/asserts.php @@ -14,16 +14,16 @@ use GoPhp\GoType\UntypedType; use GoPhp\GoType\WrappedType; use GoPhp\GoValue\AddressableValue; +use GoPhp\GoValue\Castable; use GoPhp\GoValue\Float\FloatNumber; +use GoPhp\GoValue\Float\UntypedFloatValue; use GoPhp\GoValue\Func\Func; use GoPhp\GoValue\GoValue; use GoPhp\GoValue\Hashable; use GoPhp\GoValue\Int\IntNumber; -use GoPhp\GoValue\Castable; +use GoPhp\GoValue\Int\UntypedIntValue; use GoPhp\GoValue\UntypedNilValue; -use function is_a; - /** * Asserts that two types are compatible with each other, * i.e. values of those types can be used in an operation. @@ -36,13 +36,15 @@ */ function assert_types_compatible(GoType $a, GoType $b): void { - if (!$a->isCompatible($b)) { - if ($a instanceof InterfaceType && $b instanceof WrappedType) { - throw InterfaceTypeError::cannotUseAsInterfaceType($b, $a); - } + if ($a->isCompatible($b)) { + return; + } - throw RuntimeError::mismatchedTypes($a, $b); + if ($a instanceof InterfaceType && $b instanceof WrappedType) { + throw InterfaceTypeError::cannotUseAsInterfaceType($b, $a); } + + throw RuntimeError::mismatchedTypes($a, $b); } /** @@ -56,13 +58,15 @@ function assert_types_compatible(GoType $a, GoType $b): void */ function assert_types_equal(GoType $a, GoType $b): void { - if (!$a->equals($b)) { - if ($a instanceof InterfaceType && $b instanceof WrappedType) { - throw InterfaceTypeError::cannotUseAsInterfaceType($b, $a); - } + if ($a->equals($b)) { + return; + } - throw RuntimeError::mismatchedTypes($a, $b); + if ($a instanceof InterfaceType && $b instanceof WrappedType) { + throw InterfaceTypeError::cannotUseAsInterfaceType($b, $a); } + + throw RuntimeError::mismatchedTypes($a, $b); } /** @@ -81,15 +85,17 @@ function assert_values_compatible(GoValue $a, GoValue $b): void } /** - * Asserts that the value is of valid type for a certain function. + * Asserts that the argument is of valid type for a function. * * @internal */ -function assert_arg_type_for(AddressableValue $a, GoType $b, string $funcName): void +function assert_arg_type(Arg $arg, GoType $type, ?string $funcName = null): void { - if (!$a->type()->isCompatible($b)) { - throw RuntimeError::cannotUseArgumentAsType($a, $b->name(), $funcName); + if ($type->isCompatible($arg->value->type())) { + return; } + + throw RuntimeError::cannotUseArgumentAsType($arg->value, $type->name(), $funcName); } /** @@ -99,13 +105,15 @@ function assert_arg_type_for(AddressableValue $a, GoType $b, string $funcName): * * @psalm-assert !UntypedNilValue $b */ -function assert_nil_comparison(GoValue $a, GoValue $b, string $name = ''): void +function assert_nil_comparison(GoValue $a, GoValue $b, string $name): void { assert_values_compatible($a, $b); - if (!$b instanceof UntypedNilValue) { - throw RuntimeError::onlyComparableToNil($name); + if ($b instanceof UntypedNilValue) { + return; } + + throw RuntimeError::onlyComparableToNil($name); } /** @@ -121,9 +129,11 @@ function assert_types_compatible_with_cast(GoType $a, GoValue &$b): void $b = $b->cast($a); } - if (!$b instanceof AddressableValue) { - throw InternalError::unexpectedValue($b); + if ($b instanceof AddressableValue) { + return; } + + throw InternalError::unexpectedValue($b); } /** @@ -149,32 +159,18 @@ function assert_argc(Func|BuiltinFunc $context, Argv $argv, int $expectedArgc, b /** * @internal * - * @template C + * @template C of AddressableValue + * * @psalm-param class-string $value * @psalm-assert Arg $arg */ -function assert_arg_value(Arg $arg, string $value, ?string $name = null): void +function assert_arg_value(Arg $arg, string $value): void { - if (!$arg->value instanceof $value) { - $name ??= is_a($value, AddressableValue::class, true) ? $value::NAME : $value; - - throw RuntimeError::wrongArgumentType($arg, $name); + if ($arg->value instanceof $value) { + return; } -} -/** - * @internal - * - * @psalm-assert Arg $arg - */ -function assert_arg_int(Arg $arg): void -{ - if ( - !$arg->value instanceof IntNumber - && ($arg->value instanceof FloatNumber && $arg->value->type() !== UntypedType::UntypedRoundFloat) - ) { - throw RuntimeError::wrongArgumentType($arg, IntNumber::NAME); - } + throw RuntimeError::wrongArgumentType($arg, $value::NAME); } /** @@ -182,24 +178,17 @@ function assert_arg_int(Arg $arg): void * * @psalm-assert Arg $arg */ -function assert_arg_float(Arg $arg): void +function assert_arg_int_for_builtin(Arg $arg): void { - if ( - !$arg->value instanceof FloatNumber - && !$arg->value instanceof IntNumber - ) { - throw RuntimeError::wrongArgumentType($arg, FloatNumber::NAME); + if ($arg->value instanceof IntNumber) { + return; } -} -/** - * @internal - */ -function assert_arg_type(Arg $arg, GoType $type): void -{ - if (!$type->isCompatible($arg->value->type())) { - throw RuntimeError::wrongArgumentType($arg, $type->name()); + if ($arg->value->type() === UntypedType::UntypedRoundFloat) { + return; } + + throw RuntimeError::wrongArgumentTypeForBuiltin($arg->value, IntNumber::NAME); } /** @@ -248,16 +237,19 @@ function assert_index_sliceable(int $cap, int $low, int $high, ?int $max = null) /** * @internal * - * @psalm-assert IntNumber|FloatNumber $index + * @psalm-assert IntNumber|UntypedFloatValue $index */ function assert_index_int(GoValue $index, string $context): void { - if ( - !$index instanceof IntNumber - && $index->type() !== UntypedType::UntypedRoundFloat - ) { - throw RuntimeError::indexOfWrongType($index, IntNumber::NAME, $context); + if ($index instanceof IntNumber) { + return; } + + if ($index->type() === UntypedType::UntypedRoundFloat) { + return; + } + + throw RuntimeError::indexOfWrongType($index, IntNumber::NAME, $context); } /** @@ -265,9 +257,11 @@ function assert_index_int(GoValue $index, string $context): void */ function assert_index_type(GoValue $index, GoType $type, string $context): void { - if (!$index->type()->isCompatible($type)) { - throw RuntimeError::indexOfWrongType($index, $type->name(), $context); + if ($index->type()->isCompatible($type)) { + return; } + + throw RuntimeError::indexOfWrongType($index, $type->name(), $context); } /** @@ -277,7 +271,27 @@ function assert_index_type(GoValue $index, GoType $type, string $context): void */ function assert_map_key(GoValue $index): void { - if (!$index instanceof Hashable) { - throw RuntimeError::invalidMapKeyType($index->type()); + if ($index instanceof Hashable) { + return; + } + + throw RuntimeError::invalidMapKeyType($index->type()); +} + +/** + * @internal + * + * @psalm-assert FloatNumber|UntypedIntValue $value + */ +function assert_float_type(GoValue $value): void +{ + if ($value instanceof FloatNumber) { + return; } + + if ($value->type() === UntypedType::UntypedInt) { + return; + } + + throw RuntimeError::nonFloatingPointArgument($value); }