Skip to content

Commit

Permalink
Embedded & promoted fields
Browse files Browse the repository at this point in the history
  • Loading branch information
tuqqu committed Sep 9, 2023
1 parent f1ca790 commit 919ab00
Show file tree
Hide file tree
Showing 21 changed files with 295 additions and 42 deletions.
5 changes: 5 additions & 0 deletions .php-cs-fixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
'array_syntax' => ['syntax' => 'short'],
'single_line_empty_body' => true,
'statement_indentation' => false,
'global_namespace_import' => [
'import_classes' => true,
'import_constants' => true,
'import_functions' => true,
],
];

CONFIG->setRules(RULES);
Expand Down
6 changes: 0 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,6 @@ Already implemented:

* see [tests](tests/Functional/files/)

To be implemented:

* channels and select statements
* type assertions
* generics

## Development

install dependencies:
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
],
"require": {
"php": "^8.2",
"tuqqu/go-parser": "^0.4.4"
"tuqqu/go-parser": "^0.5.1"
},
"require-dev": {
"symfony/var-dumper": "^6",
Expand Down
2 changes: 1 addition & 1 deletion src/Builtin/BuiltinFunc/Delete.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function __invoke(Argv $argv): VoidValue

$m->delete($key);

return new VoidValue();
return VoidValue::get();
}

public function name(): string
Expand Down
2 changes: 1 addition & 1 deletion src/Builtin/BuiltinFunc/Print_.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function __invoke(Argv $argv): VoidValue

$this->stderr->write(implode('', $output));

return new VoidValue();
return VoidValue::get();
}

public function name(): string
Expand Down
2 changes: 1 addition & 1 deletion src/Builtin/BuiltinFunc/Println.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function __invoke(Argv $argv): VoidValue

$this->stderr->writeln(implode(' ', $output));

return new VoidValue();
return VoidValue::get();
}

public function name(): string
Expand Down
2 changes: 1 addition & 1 deletion src/Error/ParserError.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static function fromInternalParserError(Error $error): self
match (true) {
$error instanceof LexError => $error->pos,
$error instanceof SyntaxError => $error->pos,
default => throw new InternalError(sprintf('Unknown error type %s', $error::class)),
default => throw new InternalError(sprintf('unknown error type %s', $error::class)),
},
(string) $error,
);
Expand Down
19 changes: 10 additions & 9 deletions src/Error/RuntimeError.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace GoPhp\Error;

use GoParser\Ast\Keyword;
use GoParser\Ast\Stmt\IfStmt;
use GoParser\Ast\Stmt\Stmt;
use GoPhp\Arg;
use GoPhp\Argv;
Expand Down Expand Up @@ -118,7 +117,7 @@ public static function mismatchedTypes(GoType $a, GoType $b): self
'invalid operation: mismatched types %s and %s',
$a->name(),
$b->name(),
)
),
);
}

Expand All @@ -130,14 +129,16 @@ public static function untypedNilInVarDecl(): self
public static function nonBooleanCondition(Stmt $context): self
{
/** @psalm-suppress NoInterfaceProperties */
$keyword = match (true) {
$context instanceof IfStmt => $context->if,
isset($context->keyword)
&& $context->keyword instanceof Keyword => $context->keyword,
default => throw InternalError::unreachable($context),
};
if (!isset($context->keyword) || !$context->keyword instanceof Keyword) {
throw InternalError::unreachable($context);
}

return new self(sprintf('non-boolean condition in %s statement', $keyword->word));
return new self(
sprintf(
'non-boolean condition in %s statement',
$context->keyword->word,
),
);
}

public static function multipleValueInSingleContext(TupleValue $value): self
Expand Down
2 changes: 1 addition & 1 deletion src/ErrorHandler/Collector.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use GoPhp\Error\GoError;

/**
* Error handler that collects given errors into an array.
* Error handler that collects errors into an array.
*/
final class Collector implements ErrorHandler
{
Expand Down
2 changes: 1 addition & 1 deletion src/ErrorHandler/OutputToStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use GoPhp\Stream\OutputStream;

/**
* Error handler that outputs error messages to a given stream.
* Error handler that outputs error messages to a stream.
*/
final class OutputToStream implements ErrorHandler
{
Expand Down
7 changes: 7 additions & 0 deletions src/GoType/StructType.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ final class StructType implements GoType
{
/**
* @param array<string, GoType> $fields
* @param list<string> $promotedNames
*/
public function __construct(
public readonly array $fields,
public readonly array $promotedNames,
) {}

public function name(): string
Expand Down Expand Up @@ -78,4 +80,9 @@ public function convert(AddressableValue $value): AddressableValue
{
return DefaultConverter::convert($value, $this);
}

public function hasField(string $name): bool
{
return isset($this->fields[$name]);
}
}
4 changes: 2 additions & 2 deletions src/GoValue/Complex/ComplexNumber.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
use GoPhp\GoValue\SimpleNumber;
use GoPhp\Operator;

use function sprintf;
use function GoPhp\assert_values_compatible;
use function GoPhp\try_unwind;
use function sprintf;

/**
* @psalm-type ComplexTuple = array{float, float}
Expand Down Expand Up @@ -250,7 +250,7 @@ private static function computeForMul(self $lhs, self $rhs): array
*/
private static function computeForDiv(self $lhs, self $rhs): array
{
$denominator = $rhs->real**2 + $rhs->imag**2;
$denominator = $rhs->real ** 2 + $rhs->imag ** 2;
$real = ($lhs->real * $rhs->real + $lhs->imag * $rhs->imag) / $denominator;
$imag = ($lhs->imag * $rhs->real - $lhs->real * $rhs->imag) / $denominator;

Expand Down
4 changes: 2 additions & 2 deletions src/GoValue/Func/Func.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public function __invoke(Argv $argv): GoValue

if ($stmtJump instanceof None) {
return $this->type->returnArity === ReturnJump::LEN_VOID
? new VoidValue()
? VoidValue::get()
: throw RuntimeError::wrongReturnValueNumber([], $this->type->returns);
}

Expand Down Expand Up @@ -165,7 +165,7 @@ public function zeroReturnValue(): GoValue
}

return match (count($zeroValues)) {
ReturnJump::LEN_VOID => new VoidValue(),
ReturnJump::LEN_VOID => VoidValue::get(),
ReturnJump::LEN_SINGLE => $zeroValues[0],
default => new TupleValue($zeroValues),
};
Expand Down
20 changes: 19 additions & 1 deletion src/GoValue/Struct/StructValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,25 @@ public function copy(): self

public function accessField(string $name): GoValue
{
return $this->fields->get($name)->unwrap();
$field = $this->fields->tryGet($name);

if ($field === null) {
foreach ($this->type->promotedNames as $promotedName) {
$field = $this->fields->get($promotedName);
/** @var StructValue $promotedField */
$promotedField = try_unwind($field->unwrap());

if ($promotedField->type->hasField($name)) {
return $promotedField->accessField($name);
}
}
}

if ($field === null) {
throw RuntimeError::redeclaredName($name);
}

return $field->unwrap();
}

private function equals(self $rhs): BoolValue
Expand Down
7 changes: 7 additions & 0 deletions src/GoValue/VoidValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
*/
final class VoidValue implements GoValue
{
private static ?self $instance = null;

public static function get(): self
{
return self::$instance ??= new self();
}

public function unwrap(): never
{
throw RuntimeError::noValueUsedAsValue();
Expand Down
2 changes: 1 addition & 1 deletion src/Interpreter.php
Original file line number Diff line number Diff line change
Expand Up @@ -1098,7 +1098,7 @@ private function evalIncDecStmt(IncDecStmt $stmt): None
->evalExpr($stmt->lhs)
->mutate(
Operator::fromAst($stmt->op),
new UntypedIntValue(1)
new UntypedIntValue(1),
);

return self::$noneJump;
Expand Down
2 changes: 1 addition & 1 deletion src/StmtJump/ReturnJump.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ private function __construct(
*/
public static function fromVoid(): self
{
return new self(new VoidValue(), self::LEN_VOID);
return new self(VoidValue::get(), self::LEN_VOID);
}

/**
Expand Down
43 changes: 34 additions & 9 deletions src/TypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace GoPhp;

use Closure;
use GoParser\Ast\EmbeddedFieldDecl;
use GoParser\Ast\Expr\ArrayType as AstArrayType;
use GoParser\Ast\Expr\Expr;
use GoParser\Ast\Expr\FuncType as AstFuncType;
Expand Down Expand Up @@ -156,18 +157,26 @@ private function resolveStructType(AstStructType $structType, bool $composite):
{
/** @var array<string, GoType> $fields */
$fields = [];
$promotedNames = [];

foreach ($structType->fieldDecls as $fieldDecl) {
if ($fieldDecl->identList === null) {
// fixme add anonymous fields
throw InternalError::unimplemented();
}
$type = $this->resolve($fieldDecl->type, $composite);

if ($fieldDecl->type === null) {
throw InternalError::unimplemented();
}
if ($fieldDecl instanceof EmbeddedFieldDecl) {
$name = self::getNameForEmbeddedField($fieldDecl);

$type = $this->resolve($fieldDecl->type, $composite);
if (isset($fields[$name])) {
throw RuntimeError::redeclaredName($name);
}

$fields[$name] = $type;

if (try_unwind($type) instanceof StructType) {
$promotedNames[] = $name;
}

continue;
}

foreach ($fieldDecl->identList->idents as $ident) {
if (isset($fields[$ident->name])) {
Expand All @@ -178,7 +187,7 @@ private function resolveStructType(AstStructType $structType, bool $composite):
}
}

return new StructType($fields);
return new StructType($fields, $promotedNames);
}

private function resolveInterfaceType(AstInterfaceType $interfaceType, bool $composite): InterfaceType
Expand Down Expand Up @@ -227,4 +236,20 @@ private function getTypeFromEnv(string $name, string $namespace): GoType

return $value->unwrap();
}

private static function getNameForEmbeddedField(EmbeddedFieldDecl $fieldDecl): string
{
$type = $fieldDecl->type;

if ($type instanceof AstPointerType) {
$type = $type->type;
}

if ($type instanceof QualifiedTypeName) {
$type = $type->typeName;
}

/** @var SingleTypeName $type */
return $type->name->name;
}
}
13 changes: 9 additions & 4 deletions tests/Functional/InterpreterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

use function glob;
use function file_get_contents;
use function sprintf;
use function basename;

final class InterpreterTest extends TestCase
{
private const SRC_FILES_PATH = __DIR__ . '/files';
Expand Down Expand Up @@ -43,12 +48,12 @@ public function testSourceFiles(string $goProgram, string $expectedOutput): void

public static function sourceFileProvider(): iterable
{
$files = \glob(\sprintf('%s/*.go', self::SRC_FILES_PATH));
$files = glob(sprintf('%s/*.go', self::SRC_FILES_PATH));

foreach ($files as $file) {
$goProgram = \file_get_contents($file);
$expectedOutput = \file_get_contents(
\sprintf('%s/%s.out', self::OUTPUT_FILES_PATH, \basename($file, '.go'))
$goProgram = file_get_contents($file);
$expectedOutput = file_get_contents(
sprintf('%s/%s.out', self::OUTPUT_FILES_PATH, basename($file, '.go'))
);

yield $file => [$goProgram, $expectedOutput];
Expand Down
Loading

0 comments on commit 919ab00

Please sign in to comment.