From 82a311fd3690fb2bf7b64d5c98f912b3dd746140 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 13 Oct 2024 13:20:03 +0200 Subject: [PATCH] Support for `non-empty-array` and `non-empty-list` array shape kind --- src/Ast/Type/ArrayShapeNode.php | 2 + src/Parser/TypeParser.php | 16 ++++- tests/PHPStan/Parser/TypeParserTest.php | 82 +++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 2 deletions(-) diff --git a/src/Ast/Type/ArrayShapeNode.php b/src/Ast/Type/ArrayShapeNode.php index 1f4ed4a..73d162d 100644 --- a/src/Ast/Type/ArrayShapeNode.php +++ b/src/Ast/Type/ArrayShapeNode.php @@ -10,6 +10,8 @@ class ArrayShapeNode implements TypeNode public const KIND_ARRAY = 'array'; public const KIND_LIST = 'list'; + public const KIND_NON_EMPTY_ARRAY = 'non-empty-array'; + public const KIND_NON_EMPTY_LIST = 'non-empty-list'; use NodeAttributes; diff --git a/src/Parser/TypeParser.php b/src/Parser/TypeParser.php index 2be2839..982eba7 100644 --- a/src/Parser/TypeParser.php +++ b/src/Parser/TypeParser.php @@ -180,7 +180,13 @@ private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { $type = $this->tryParseArrayOrOffsetAccess($tokens, $type); - } elseif (in_array($type->name, ['array', 'list', 'object'], true) && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) { + } elseif (in_array($type->name, [ + Ast\Type\ArrayShapeNode::KIND_ARRAY, + Ast\Type\ArrayShapeNode::KIND_LIST, + Ast\Type\ArrayShapeNode::KIND_NON_EMPTY_ARRAY, + Ast\Type\ArrayShapeNode::KIND_NON_EMPTY_LIST, + 'object', + ], true) && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) { if ($type->name === 'object') { $type = $this->parseObjectShape($tokens); } else { @@ -690,7 +696,13 @@ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNo $startIndex )); - } elseif (in_array($type->name, ['array', 'list', 'object'], true) && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) { + } elseif (in_array($type->name, [ + Ast\Type\ArrayShapeNode::KIND_ARRAY, + Ast\Type\ArrayShapeNode::KIND_LIST, + Ast\Type\ArrayShapeNode::KIND_NON_EMPTY_ARRAY, + Ast\Type\ArrayShapeNode::KIND_NON_EMPTY_LIST, + 'object', + ], true) && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) { if ($type->name === 'object') { $type = $this->parseObjectShape($tokens); } else { diff --git a/tests/PHPStan/Parser/TypeParserTest.php b/tests/PHPStan/Parser/TypeParserTest.php index 3610fa5..5ff5dd8 100644 --- a/tests/PHPStan/Parser/TypeParserTest.php +++ b/tests/PHPStan/Parser/TypeParserTest.php @@ -761,6 +761,88 @@ public function provideParseData(): array ArrayShapeNode::KIND_LIST ), ], + [ + 'non-empty-array{ + int, + string + }', + new ArrayShapeNode( + [ + new ArrayShapeItemNode( + null, + false, + new IdentifierTypeNode('int') + ), + new ArrayShapeItemNode( + null, + false, + new IdentifierTypeNode('string') + ), + ], + true, + ArrayShapeNode::KIND_NON_EMPTY_ARRAY + ), + ], + [ + 'callable(): non-empty-array{int, string}', + new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayShapeNode( + [ + new ArrayShapeItemNode( + null, + false, + new IdentifierTypeNode('int') + ), + new ArrayShapeItemNode( + null, + false, + new IdentifierTypeNode('string') + ), + ], + true, + ArrayShapeNode::KIND_NON_EMPTY_ARRAY + )), + ], + [ + 'callable(): non-empty-list{int, string}', + new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayShapeNode( + [ + new ArrayShapeItemNode( + null, + false, + new IdentifierTypeNode('int') + ), + new ArrayShapeItemNode( + null, + false, + new IdentifierTypeNode('string') + ), + ], + true, + ArrayShapeNode::KIND_NON_EMPTY_LIST + )), + ], + [ + 'non-empty-list{ + int, + string + }', + new ArrayShapeNode( + [ + new ArrayShapeItemNode( + null, + false, + new IdentifierTypeNode('int') + ), + new ArrayShapeItemNode( + null, + false, + new IdentifierTypeNode('string') + ), + ], + true, + ArrayShapeNode::KIND_NON_EMPTY_LIST + ), + ], [ 'array{...}', new ArrayShapeNode(