-
-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: introduce unsealed shaped array syntax #510
Conversation
Hello @Ocramius, if you have some time I'd really appreciate if you could play with this and try to make it crash. 😛 |
@@ -134,6 +134,15 @@ final class SomeClass | |||
|
|||
/** @var array{string, bar: int} */ | |||
private array $shapedArrayWithUndefinedKey, | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I definitely need to rework this entire documentation page. 😬
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still, the additions are useful/understandable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@romm I lack the low level understanding of your library to provide an implementation-level review, but based off your comments on expected behavior, this is very good!
|
||
use function implode; | ||
|
||
/** @internal */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, the fact that you meticulously add @internal
everywhere is :chefkiss:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be sure I don't forget to add it, I'm covered by this PHPStan rule 😁 : https://github.com/CuyZ/Valinor/blob/master/qa/PHPStan/Extension/ApiAndInternalAnnotationCheck.php
@@ -121,12 +126,50 @@ private function shapedArrayType(TokenStream $stream): ShapedArrayType | |||
|
|||
$optional = false; | |||
|
|||
if ($stream->next() instanceof TripleDotsToken) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This lexer is going to grow over time: I wonder if there's a way to declare a grammar somewhere 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have no clue about that; for now I find the current approach readable and understandable, but indeed that may change in the future.
public function test_unsealed_shaped_array_with_missing_closing_bracket_throws_exception(): void | ||
{ | ||
$this->expectException(ShapedArrayClosingBracketMissing::class); | ||
$this->expectExceptionCode(1631283658); | ||
$this->expectExceptionMessage('Missing closing curly bracket in shaped array signature `array{0: int, ...`.'); | ||
|
||
$this->parser->parse('array{int, ...'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would check '...'
(quotes around the triple dots) in a separate test: it's just a detail, but I'm wondering if your lexer works well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like array{'...': '...', ...}
to see what happens :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well that does not really need a test because that kind of cases is already covered. But because you're curious about it, here are the results 😁
(new \CuyZ\Valinor\MapperBuilder())
->allowPermissiveTypes()
->mapper()
->map('array{"...": "...", ...array<string>}', [
'...' => '...',
'foo' => 'bar'
]); // ✅
(new \CuyZ\Valinor\MapperBuilder())
->allowPermissiveTypes()
->mapper()
->map('array{"...": "...", ...array<string>}', [
'foo' => 'bar'
]); // ❌ An error occurred at path ...: Cannot be empty and must be filled with a value matching type "...".
} catch (MappingError $error) { | ||
$this->mappingFail($error); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've seen this in many mapper integration tests: I think this try-catch flow should be abstracted away in the test suite
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MyTestUtility::useMapper($this->mapper)
would be sufficient, for example.
Alternatively:
final class TestMapper implements TreeMapper
{
private TreeMapper $realMapper;
// add try-catch logic here
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I already thought about something like this, at some point I'll just do it… I hope 😁
@Ocramius I was actually just asking for real tests on your end, but thanks for the review it's really much appreciated. If you are done we can merge it. 😉 |
Nothing immediate in userland projects: I think it used to crash in a project last year, due to parsing failing |
This prevents PHPStan from crashing when giving a type it does not understand to `\CuyZ\Valinor\Mapper\TreeMapper::map()`.
This syntax enables an extension of the shaped array type by allowing additional values that must respect a certain type. ```php $mapper = (new \CuyZ\Valinor\MapperBuilder())->mapper(); // Default syntax can be used like this: $mapper->map( 'array{foo: string, ...array<string>}', [ 'foo' => 'foo', 'bar' => 'bar', // ✅ valid additional value ] ); $mapper->map( 'array{foo: string, ...array<string>}', [ 'foo' => 'foo', 'bar' => 1337, // ❌ invalid value 1337 ] ); // Key type can be added as well: $mapper->map( 'array{foo: string, ...array<int, string>}', [ 'foo' => 'foo', 42 => 'bar', // ✅ valid additional key ] ); $mapper->map( 'array{foo: string, ...array<int, string>}', [ 'foo' => 'foo', 'bar' => 'bar' // ❌ invalid key ] ); // Advanced types can be used: $mapper->map( "array{ 'en_US': non-empty-string, ...array<non-empty-string, non-empty-string> }", [ 'en_US' => 'Hello', 'fr_FR' => 'Salut', // ✅ valid additional value ] ); $mapper->map( "array{ 'en_US': non-empty-string, ...array<non-empty-string, non-empty-string> }", [ 'en_US' => 'Hello', 'fr_FR' => '', // ❌ invalid value ] ); // If the permissive type is enabled, the following will work: (new \CuyZ\Valinor\MapperBuilder()) ->allowPermissiveTypes() ->mapper() ->map( 'array{foo: string, ...}', ['foo' => 'foo', 'bar' => 'bar', 42 => 1337] ); // ✅ ```
b0ddcf1
to
4b6faa2
Compare
Let's go for it then! 💪 |
This syntax enables an extension of the shaped array type by allowing additional values that must respect a certain type.
Fixes #438