Skip to content

Commit

Permalink
TypedMap
Browse files Browse the repository at this point in the history
  • Loading branch information
vudaltsov committed Sep 4, 2024
1 parent 542f4af commit c5608c5
Show file tree
Hide file tree
Showing 4 changed files with 328 additions and 0 deletions.
144 changes: 144 additions & 0 deletions src/MutableTypedMap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php

declare(strict_types=1);

namespace Typhoon\DataStructures;

/**
* @api
*/
final class MutableTypedMap extends TypedMap
{
/**
* @param array<non-negative-int, mixed> $values
*/
private function __construct(

Check warning on line 15 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L15

Added line #L15 was not covered by tests
private array $values = [],
) {}

Check warning on line 17 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L17

Added line #L17 was not covered by tests

public static function create(TypedKVPair ...$kvPairs): self

Check warning on line 19 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L19

Added line #L19 was not covered by tests
{
$map = new self();

Check warning on line 21 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L21

Added line #L21 was not covered by tests

foreach ($kvPairs as $pair) {
$map->values[$pair->key->index] = $pair->value;

Check warning on line 24 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L23-L24

Added lines #L23 - L24 were not covered by tests
}

return $map;

Check warning on line 27 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L27

Added line #L27 was not covered by tests
}

public function with(TypedKey $key, mixed $value): static

Check warning on line 30 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L30

Added line #L30 was not covered by tests
{
$values = $this->values;
$values[$key->index] = $value;

Check warning on line 33 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L32-L33

Added lines #L32 - L33 were not covered by tests

return new self($values);

Check warning on line 35 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L35

Added line #L35 was not covered by tests
}

public function withAll(self $map): static

Check warning on line 38 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L38

Added line #L38 was not covered by tests
{
return new self(array_replace($this->values, $map->values));

Check warning on line 40 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L40

Added line #L40 was not covered by tests
}

public function without(TypedKey ...$keys): static

Check warning on line 43 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L43

Added line #L43 was not covered by tests
{
$values = $this->values;

Check warning on line 45 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L45

Added line #L45 was not covered by tests

foreach ($keys as $key) {
unset($values[$key->index]);

Check warning on line 48 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L47-L48

Added lines #L47 - L48 were not covered by tests
}

return new self($values);

Check warning on line 51 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L51

Added line #L51 was not covered by tests
}

public function contains(TypedKey $key): bool

Check warning on line 54 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L54

Added line #L54 was not covered by tests
{
return \array_key_exists($key->index, $this->values);

Check warning on line 56 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L56

Added line #L56 was not covered by tests
}

public function offsetExists(mixed $offset): bool

Check warning on line 59 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L59

Added line #L59 was not covered by tests
{
return \array_key_exists($offset->index, $this->values);

Check warning on line 61 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L61

Added line #L61 was not covered by tests
}

/**
* @template V
* @param TypedKey<V> $key
* @return V
*/
public function get(TypedKey $key): mixed

Check warning on line 69 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L69

Added line #L69 was not covered by tests
{
if (\array_key_exists($key->index, $this->values)) {

Check warning on line 71 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L71

Added line #L71 was not covered by tests
/** @var V */
return $this->values[$key->index];

Check warning on line 73 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L73

Added line #L73 was not covered by tests
}

return $key->default($this);

Check warning on line 76 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L76

Added line #L76 was not covered by tests
}

/**
* @template V
* @param TypedKey<V> $offset
* @return V
*/
public function offsetGet(mixed $offset): mixed

Check warning on line 84 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L84

Added line #L84 was not covered by tests
{
if (\array_key_exists($offset->index, $this->values)) {

Check warning on line 86 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L86

Added line #L86 was not covered by tests
/** @var V */
return $this->values[$offset->index];

Check warning on line 88 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L88

Added line #L88 was not covered by tests
}

return $offset->default($this);

Check warning on line 91 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L91

Added line #L91 was not covered by tests
}

public function count(): int

Check warning on line 94 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L94

Added line #L94 was not covered by tests
{
return \count($this->values);

Check warning on line 96 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L96

Added line #L96 was not covered by tests
}

/**
* @template V
* @param TypedKey<V> $key
* @param V $value
*/
public function put(TypedKey $key, mixed $value): void

Check warning on line 104 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L104

Added line #L104 was not covered by tests
{
$this->values[$key->index] = $value;

Check warning on line 106 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L106

Added line #L106 was not covered by tests
}

public function putAll(self $map): void

Check warning on line 109 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L109

Added line #L109 was not covered by tests
{
$this->values = array_replace($this->values, $map->values);

Check warning on line 111 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L111

Added line #L111 was not covered by tests
}

public function remove(TypedKey ...$keys): void

Check warning on line 114 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L114

Added line #L114 was not covered by tests
{
foreach ($keys as $key) {
unset($this->values[$key->index]);

Check warning on line 117 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L116-L117

Added lines #L116 - L117 were not covered by tests
}
}

/**
* @return list<array{class-string<TypedKey>, non-empty-string, mixed}>
*/
public function __serialize(): array

Check warning on line 124 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L124

Added line #L124 was not covered by tests
{
return array_map(
static fn(TypedKey $key, mixed $value): array => [$key::class, $key->method, $value],
TypedKey::byIndexes(array_keys($this->values)),
$this->values,

Check warning on line 129 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L126-L129

Added lines #L126 - L129 were not covered by tests
);
}

/**
* @param list<array{class-string<TypedKey>, non-empty-string, mixed}> $data
*/
public function __unserialize(array $data): void

Check warning on line 136 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L136

Added line #L136 was not covered by tests
{
foreach ($data as [$class, $method, $value]) {
$key = $class::$method();
\assert($key instanceof $class);
$this->values[$key->index] = $value;

Check warning on line 141 in src/MutableTypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/MutableTypedMap.php#L138-L141

Added lines #L138 - L141 were not covered by tests
}
}
}
21 changes: 21 additions & 0 deletions src/TypedKVPair.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Typhoon\DataStructures;

/**
* @api
* @template V
*/
final class TypedKVPair
{
/**
* @param TypedKey<V> $key
* @param V $value
*/
public function __construct(

Check warning on line 17 in src/TypedKVPair.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKVPair.php#L17

Added line #L17 was not covered by tests
public readonly TypedKey $key,
public readonly mixed $value,
) {}

Check warning on line 20 in src/TypedKVPair.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKVPair.php#L20

Added line #L20 was not covered by tests
}
101 changes: 101 additions & 0 deletions src/TypedKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

declare(strict_types=1);

namespace Typhoon\DataStructures;

/**
* @api
* @template V
* @psalm-consistent-templates
*/
abstract class TypedKey
{
/**
* @var array<non-empty-string, self>
*/
private static array $keys = [];

/**
* @internal
* @psalm-internal Typhoon\DataStructures
* @param list<non-negative-int> $indexes
* @return list<self>
*/
final public static function byIndexes(array $indexes): array

Check warning on line 25 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L25

Added line #L25 was not covered by tests
{
$keys = array_values(self::$keys);

Check warning on line 27 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L27

Added line #L27 was not covered by tests

return array_map(
static fn(int $index): self => $keys[$index] ?? throw new \LogicException(),
$indexes,

Check warning on line 31 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L29-L31

Added lines #L29 - L31 were not covered by tests
);
}

/**
* @template D
* @param ?callable(TypedMap): D $default
* @return static<D>
*/
final protected static function init(?callable $default = null): static

Check warning on line 40 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L40

Added line #L40 was not covered by tests
{
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1] ?? [];

Check warning on line 42 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L42

Added line #L42 was not covered by tests

\assert(
isset($trace['class'], $trace['function']) && $trace['class'] === static::class && $trace['function'] !== '',
\sprintf('Invalid %s call', self::class),

Check warning on line 46 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L44-L46

Added lines #L44 - L46 were not covered by tests
);

$name = \sprintf('%s::%s', static::class, $trace['function']);

Check warning on line 49 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L49

Added line #L49 was not covered by tests

\assert(!isset(self::$keys[$name]), \sprintf('Please ensure you memoize key in %s()', $name));

Check warning on line 51 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L51

Added line #L51 was not covered by tests

return self::$keys[$name] = new static(
index: \count(self::$keys),
method: $trace['function'],
default: $default ?? static fn(): never => throw new \LogicException(\sprintf('Key %s() does not have a default value', $name)),

Check warning on line 56 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L53-L56

Added lines #L53 - L56 were not covered by tests
);
}

/**
* @param non-negative-int $index
* @param non-empty-string $method
* @param callable(TypedMap): V $default
*/
final private function __construct(

Check warning on line 65 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L65

Added line #L65 was not covered by tests
public readonly int $index,
public readonly string $method,
private readonly mixed $default,
) {}

Check warning on line 69 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L69

Added line #L69 was not covered by tests

/**
* @return V
*/
final public function default(TypedMap $map): mixed

Check warning on line 74 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L74

Added line #L74 was not covered by tests
{
return ($this->default)($map);

Check warning on line 76 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L76

Added line #L76 was not covered by tests
}

/**
* @return non-empty-string
*/
final public function toString(): string

Check warning on line 82 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L82

Added line #L82 was not covered by tests
{
return self::class . '::' . $this->method . '()';

Check warning on line 84 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L84

Added line #L84 was not covered by tests
}

final public function __serialize(): never

Check warning on line 87 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L87

Added line #L87 was not covered by tests
{
throw new \BadMethodCallException(\sprintf('%s does not support serialization', self::class));

Check warning on line 89 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L89

Added line #L89 was not covered by tests
}

final public function __unserialize(array $_data): never

Check warning on line 92 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L92

Added line #L92 was not covered by tests
{
throw new \BadMethodCallException(\sprintf('%s does not support deserialization', self::class));

Check warning on line 94 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L94

Added line #L94 was not covered by tests
}

final public function __clone()

Check warning on line 97 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L97

Added line #L97 was not covered by tests
{
throw new \BadMethodCallException(\sprintf('%s does not support cloning', self::class));

Check warning on line 99 in src/TypedKey.php

View check run for this annotation

Codecov / codecov/patch

src/TypedKey.php#L99

Added line #L99 was not covered by tests
}
}
62 changes: 62 additions & 0 deletions src/TypedMap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace Typhoon\DataStructures;

/**
* @api
* @implements \ArrayAccess<TypedKey, mixed>
* @psalm-suppress UnusedClass
*/
abstract class TypedMap implements \ArrayAccess, \Countable
{
public static function create(TypedKVPair ...$kvPairs): self

Check warning on line 14 in src/TypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/TypedMap.php#L14

Added line #L14 was not covered by tests
{
return MutableTypedMap::create(...$kvPairs);

Check warning on line 16 in src/TypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/TypedMap.php#L16

Added line #L16 was not covered by tests
}

/**
* @template V
* @param TypedKey<V> $key
* @param V $value
*/
abstract public function with(TypedKey $key, mixed $value): static;

Check warning on line 24 in src/TypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/TypedMap.php#L24

Added line #L24 was not covered by tests

abstract public function withAll(MutableTypedMap $map): static;

Check warning on line 26 in src/TypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/TypedMap.php#L26

Added line #L26 was not covered by tests

abstract public function without(TypedKey ...$keys): static;

Check warning on line 28 in src/TypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/TypedMap.php#L28

Added line #L28 was not covered by tests

abstract public function contains(TypedKey $key): bool;

Check warning on line 30 in src/TypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/TypedMap.php#L30

Added line #L30 was not covered by tests

abstract public function offsetExists(mixed $offset): bool;

Check warning on line 32 in src/TypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/TypedMap.php#L32

Added line #L32 was not covered by tests

/**
* @template V
* @param TypedKey<V> $key
* @return V
*/
abstract public function get(TypedKey $key): mixed;

Check warning on line 39 in src/TypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/TypedMap.php#L39

Added line #L39 was not covered by tests

/**
* @template V
* @param TypedKey<V> $offset
* @return V
*/
abstract public function offsetGet(mixed $offset): mixed;

Check warning on line 46 in src/TypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/TypedMap.php#L46

Added line #L46 was not covered by tests

/**
* @return non-negative-int
*/
abstract public function count(): int;

Check warning on line 51 in src/TypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/TypedMap.php#L51

Added line #L51 was not covered by tests

public function offsetSet(mixed $offset, mixed $value): never

Check warning on line 53 in src/TypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/TypedMap.php#L53

Added line #L53 was not covered by tests
{
throw new \BadMethodCallException();

Check warning on line 55 in src/TypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/TypedMap.php#L55

Added line #L55 was not covered by tests
}

public function offsetUnset(mixed $offset): never

Check warning on line 58 in src/TypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/TypedMap.php#L58

Added line #L58 was not covered by tests
{
throw new \BadMethodCallException();

Check warning on line 60 in src/TypedMap.php

View check run for this annotation

Codecov / codecov/patch

src/TypedMap.php#L60

Added line #L60 was not covered by tests
}
}

0 comments on commit c5608c5

Please sign in to comment.