Skip to content

Commit 7338608

Browse files
committed
Add tuple type handling and tests for keyless tuples in ArrayAndListTypesTest
1 parent 739840c commit 7338608

2 files changed

Lines changed: 85 additions & 1 deletion

File tree

tests/Fixtures/Types/GlobalTypes.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
/**
88
* @phpstan-type SharedShape array{id: positive-int, name: non-empty-string}
9+
* @phpstan-type SharedTupleShape array{list<positive-int>, non-empty-string}
910
*/
1011
class GlobalTypes
1112
{
12-
}
13+
}

tests/TypeChecking/ArraysAndShapes/ArrayAndListTypesTest.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,54 @@ function testComplexNestedShapeParam(array $data): bool
106106
return true;
107107
}
108108

109+
/**
110+
* Test function for Issue #20: Implicit keyless tuple array shapes
111+
*
112+
* @param array{list<positive-int>, list<non-empty-string>} $tuple
113+
*/
114+
function testKeylessImplicitTupleShape(array $tuple): bool
115+
{
116+
return true;
117+
}
118+
119+
/**
120+
* Helpers for Issue #20 Edge Cases
121+
*
122+
* @phpstan-type LocalTupleAlias array{list<positive-int>, list<non-empty-string>}
123+
* @phpstan-type MixedTupleShape array{non-empty-string, code: positive-int, list<int>}
124+
* @phpstan-import-type SharedTupleShape from \TypePHP\Tests\Fixtures\Types\GlobalTypes as ImportedTuple
125+
*
126+
* @param LocalTupleAlias $payload
127+
* @param MixedTupleShape $mixedPayload
128+
*/
129+
function testLocalTupleAliasParam(array $payload, array $mixedPayload): bool
130+
{
131+
return true;
132+
}
133+
134+
/**
135+
* @phpstan-import-type SharedTupleShape from \TypePHP\Tests\Fixtures\Types\GlobalTypes as ImportedTuple
136+
*
137+
* @param ImportedTuple $tuple
138+
*/
139+
function testImportedTupleAliasParam(array $tuple): bool
140+
{
141+
return true;
142+
}
143+
144+
/**
145+
* @return array{list<positive-int>, non-empty-string}
146+
*/
147+
function testReturnKeylessTuple(bool $valid): array
148+
{
149+
if (! $valid) {
150+
return [[10, -5], 'bundle'];
151+
}
152+
153+
return [[10, 20], 'bundle'];
154+
}
155+
156+
109157
describe('Class Object Arrays (Dog[])', function () {
110158
test('accepts array of matching class instances', function () {
111159
expect(testDogArrayParam([new Dog(), new Dog()]))->toBe(2);
@@ -297,3 +345,38 @@ function testComplexNestedShapeParam(array $data): bool
297345
;
298346
});
299347
});
348+
349+
describe('Issue #20 Edge Cases: Keyless Tuples in Type Aliases, Returns, and Mixed Keys', function () {
350+
test('resolves keyless tuple shapes defined inside local @phpstan-type aliases', function () {
351+
expect(testLocalTupleAliasParam(
352+
[[10, 20], ['a', 'b']],
353+
['status_ok', 'code' => 200, [1, 2, 3]]
354+
))->toBeTrue();
355+
356+
expect(fn () => testLocalTupleAliasParam(
357+
[[10, -5], ['a', 'b']],
358+
['status_ok', 'code' => 200, [1, 2, 3]]
359+
))->toThrow(TypeError::class, "Argument \$payload['0'][1] must be of type positive-int");
360+
361+
expect(fn () => testLocalTupleAliasParam(
362+
[[10, 20], ['a', 'b']],
363+
['status_ok', 'code' => -100, [1, 2, 3]]
364+
))->toThrow(TypeError::class, "Argument \$mixedPayload['code'] must be of type positive-int");
365+
});
366+
367+
test('resolves keyless tuple shapes imported via @phpstan-import-type', function () {
368+
expect(testImportedTupleAliasParam([[100, 200], 'valid_string']))->toBeTrue();
369+
370+
expect(fn () => testImportedTupleAliasParam([[100, 200], '']))
371+
->toThrow(TypeError::class, "Argument \$tuple['1'] must be of type non-empty-string")
372+
;
373+
});
374+
375+
test('validates keyless tuple shapes returned from functions', function () {
376+
expect(testReturnKeylessTuple(true))->toBe([[10, 20], 'bundle']);
377+
378+
expect(fn () => testReturnKeylessTuple(false))
379+
->toThrow(TypeError::class, "Return value['0'][1] must be of type positive-int")
380+
;
381+
});
382+
});

0 commit comments

Comments
 (0)