Skip to content

Commit

Permalink
Any interface type, simple type assertions
Browse files Browse the repository at this point in the history
  • Loading branch information
tuqqu committed Sep 11, 2024
1 parent 51c715d commit 1e61a2b
Show file tree
Hide file tree
Showing 26 changed files with 573 additions and 127 deletions.
2 changes: 1 addition & 1 deletion src/Builtin/BuiltinFunc/Recover.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function __invoke(Argv $argv): InterfaceValue
}

// fixme type
return InterfaceValue::nil(new InterfaceType());
return InterfaceValue::nil(InterfaceType::any());
}

public function name(): string
Expand Down
2 changes: 2 additions & 0 deletions src/Builtin/StdBuiltinProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use GoPhp\Builtin\BuiltinFunc\Real;
use GoPhp\Builtin\BuiltinFunc\Recover;
use GoPhp\Env\Environment;
use GoPhp\GoType\InterfaceType;
use GoPhp\GoType\NamedType;
use GoPhp\GoType\UntypedType;
use GoPhp\GoValue\BoolValue;
Expand Down Expand Up @@ -113,5 +114,6 @@ protected function defineTypes(Environment $env): void

$env->defineTypeAlias('byte', $uint8);
$env->defineTypeAlias('rune', $int32);
$env->defineTypeAlias('any', new TypeValue(InterfaceType::any()));
}
}
27 changes: 13 additions & 14 deletions src/Error/InterfaceTypeError.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,36 @@

namespace GoPhp\Error;

use GoPhp\GoType\GoType;
use GoPhp\GoType\InterfaceType;
use GoPhp\GoType\WrappedType;
use GoPhp\GoValue\WrappedValue;
use GoPhp\GoValue\GoValue;

use function sprintf;

class InterfaceTypeError extends RuntimeError
{
private WrappedValue|WrappedType|null $value = null;
private GoValue|GoType|null $value = null;
private ?string $missingMethod = null;

public static function cannotUseAsInterfaceType(WrappedValue|WrappedType $value, InterfaceType $interfaceType): self
public static function cannotUseAsInterfaceType(GoValue|GoType $value, InterfaceType $interfaceType): self
{
$missingMethod = (string) $interfaceType->tryGetMissingMethod(
$value instanceof WrappedType
$value instanceof GoType
? $value
: $value->type(),
);

$error = self::cannotUseAsType($value, $interfaceType, $missingMethod);

$error->value = $value;
$error->missingMethod = $missingMethod;

return $error;
}

public static function fromOther(self $error, WrappedType $interfaceType): self
public static function fromOther(self $error, GoType $interfaceType): self
{
if (!isset($error->value, $error->missingMethod)) {
throw InternalError::unreachable('cannot convert error from other error');
throw InternalError::unreachable('cannot create interface error from another error');
}

return self::cannotUseAsType(
Expand All @@ -45,21 +44,21 @@ public static function fromOther(self $error, WrappedType $interfaceType): self
}

private static function cannotUseAsType(
WrappedValue|WrappedType $value,
InterfaceType|WrappedType $interfaceType,
GoValue|GoType $value,
InterfaceType|GoType $interfaceType,
string $missingMethod,
): self {
return new self(
sprintf(
"cannot use %s (value of type %s) as type %s:\n\t%s does not implement %s (missing %s method)",
$value instanceof WrappedType
'cannot use %s (value of type %s) as type %s: %s does not implement %s (missing method %s)',
$value instanceof GoType
? $value->name()
: self::valueToString($value),
$value instanceof WrappedType
$value instanceof GoType
? $value->name()
: $value->type()->name(),
$interfaceType->name(),
$value instanceof WrappedType
$value instanceof GoType
? $value->name()
: $value->type()->name(),
$interfaceType->name(),
Expand Down
5 changes: 4 additions & 1 deletion src/Error/InternalError.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace GoPhp\Error;

use LogicException;
use ReflectionClass;

use function get_debug_type;
use function is_object;
Expand All @@ -14,6 +15,8 @@
/**
* Errors that indicate a bug in the code.
* They must not occur even when running a wrongly written program.
*
* Non-implemented yet features also throw this exception.
*/
final class InternalError extends LogicException
{
Expand All @@ -25,7 +28,7 @@ public static function unreachableMethodCall(): self
public static function unreachable(object|string|null $context): self
{
$context = match (true) {
is_object($context) => $context::class,
is_object($context) => (new ReflectionClass($context))->getShortName(),
is_string($context) => $context,
$context === null => '',
};
Expand Down
26 changes: 26 additions & 0 deletions src/Error/PanicError.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@

namespace GoPhp\Error;

use GoPhp\GoType\GoType;
use GoPhp\GoType\UntypedNilType;
use GoPhp\GoValue\AddressableValue;
use GoPhp\GoValue\Interface\InterfaceValue;
use GoPhp\GoValue\String\UntypedStringValue;
use RuntimeException;

use function sprintf;

use const GoPhp\GoValue\NIL;

class PanicError extends RuntimeException implements GoError
{
public readonly AddressableValue $panicValue;
Expand All @@ -31,6 +36,27 @@ public static function nilMapAssignment(): self
return new self(new UntypedStringValue('assignment to entry in nil map'));
}

public static function interfaceConversion(InterfaceValue $interface, GoType $type): self
{
return new self(new UntypedStringValue(
sprintf(
'interface conversion: %s is %s, not %s',
$interface->type()->name(),
$interface->value === NIL
? UntypedNilType::NAME
: $interface->value->type()->name(),
$type->name(),
),
));
}

public static function indexOutOfRange(int $index, int $len): self
{
return new self(new UntypedStringValue(
sprintf('index out of range [%d] with length %d', $index, $len),
));
}

public function getPosition(): null
{
return null;
Expand Down
20 changes: 15 additions & 5 deletions src/Error/RuntimeError.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ public static function cannotFindPackage(string $path): self
return new self(sprintf('cannot find package "." in: %s', $path));
}

public static function typeOutsideTypeSwitch(): self
{
return new self('invalid syntax tree: use of .(type) outside type switch');
}

public static function undefinedOperator(Operator $op, AddressableValue $value, bool $unary = false): self
{
if ($op === Operator::Eq) {
Expand Down Expand Up @@ -360,6 +365,16 @@ public static function wrongFirstArgumentTypeForAppend(Arg $arg): self
);
}

public static function nonInterfaceAssertion(GoValue $value): self
{
return new self(
sprintf(
'invalid operation: %s is not an interface',
self::valueToString($value),
),
);
}

public static function indexNegative(GoValue|int $value): self
{
return new self(
Expand All @@ -380,11 +395,6 @@ public static function nonConstantExpr(GoValue $value): self
return new self(sprintf('%s is not constant', self::valueToString($value)));
}

public static function indexOutOfRange(int $index, int $len): self
{
return new self(sprintf('index out of range [%d] with length %d', $index, $len));
}

public static function invalidSliceIndices(int $low, int $high): self
{
return new self(sprintf('invalid slice indices: %d < %d', $low, $high));
Expand Down
85 changes: 64 additions & 21 deletions src/GoType/InterfaceType.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,37 @@
use function sprintf;

/**
* @psalm-type MethodMap = array<string, FuncType>
* @template-implements Hashable<string>
*/
final class InterfaceType implements Hashable, GoType
final class InterfaceType implements Hashable, RefType
{
/**
* @param array<string, FuncType> $methods
* @param MethodMap $methods
*/
public function __construct(
private readonly array $methods = [],
private readonly ?Environment $envRef = null,
private function __construct(
private readonly array $methods,
private readonly ?Environment $envRef,
) {}

/**
* @param MethodMap $methods
*/
public static function withMethods(array $methods, Environment $envRef): self
{
return new self($methods, $envRef);
}

public static function any(): self
{
return new self([], null);
}

public function isAny(): bool
{
return empty($this->methods);
}

public function name(): string
{
$methods = [];
Expand All @@ -43,18 +62,37 @@ public function name(): string

public function equals(GoType $other): bool
{
return match (true) {
$other instanceof UntypedNilType,
$other instanceof self => true,
$other instanceof WrappedType => $this->checkWrappedType($other),
default => false,
};
if ($other instanceof UntypedNilType) {
return true;
}

if ($this->isAny()) {
return true;
}

if (!$other instanceof self) {
return false;
}

if ($this->noMissingMethods($other)) {
return true;
}

return false;
}

public function isCompatible(GoType $other): bool
{
return $other instanceof UntypedNilType
|| $this->equals($other);
if ($this->isAny()) {
return true;
}

return match (true) {
$other instanceof UntypedNilType => true,
$other instanceof WrappedType,
$other instanceof self => $this->noMissingMethods($other),
default => false,
};
}

public function zeroValue(): InterfaceValue
Expand All @@ -68,7 +106,7 @@ public function convert(AddressableValue $value): AddressableValue
return $value;
}

if ($this->checkWrappedType($value->type())) {
if ($this->noMissingMethods($value->type())) {
return $value;
}

Expand All @@ -80,30 +118,35 @@ public function hash(): string
return $this->name();
}

public function tryGetMissingMethod(WrappedType $other): ?string
public function tryGetMissingMethod(GoType $other): ?string
{
if ($this->envRef === null) {
return null;
}

foreach ($this->methods as $name => $method) {
if (
!$this->envRef->hasMethod($name, $other)
$other instanceof WrappedType
&& !$this->envRef->hasMethod($name, $other)
&& !$this->envRef->hasMethod($name, $other->underlyingType)
) {
return $name;
}

if ($other instanceof self) {
$hasMethod = isset($other->methods[$name]) && $method->equals($other->methods[$name]);

if (!$hasMethod) {
return $name;
}
}
}

return null;
}

private function checkWrappedType(WrappedType $other): bool
private function noMissingMethods(self|WrappedType $other): bool
{
if ($this->envRef === null) {
return true;
}

return $this->tryGetMissingMethod($other) === null;
}
}
2 changes: 2 additions & 0 deletions src/GoType/UntypedNilType.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/
final class UntypedNilType implements RefType
{
public const string NAME = 'nil';

public function name(): string
{
return 'untyped nil';
Expand Down
14 changes: 11 additions & 3 deletions src/GoType/UntypedType.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,20 @@ public function isString(): bool
return $this === self::UntypedString;
}

public function convert(AddressableValue $value): never
public function zeroValue(): AddressableValue
{
throw InternalError::unreachableMethodCall();
return (match ($this) {
self::UntypedInt => NamedType::Int,
self::UntypedRune => NamedType::Int32,
self::UntypedRoundFloat,
self::UntypedFloat => NamedType::Float64,
self::UntypedBool => NamedType::Bool,
self::UntypedComplex => NamedType::Complex128,
self::UntypedString => NamedType::String,
})->zeroValue();
}

public function zeroValue(): never
public function convert(AddressableValue $value): never
{
throw InternalError::unreachableMethodCall();
}
Expand Down
2 changes: 1 addition & 1 deletion src/GoValue/Float/UntypedFloatValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

final class UntypedFloatValue extends FloatNumber
{
private UntypedType $type;
private readonly UntypedType $type;

public function __construct(float $value)
{
Expand Down
Loading

0 comments on commit 1e61a2b

Please sign in to comment.