Skip to content

[TypeInfo] Enforce stricter types sanity to avoid edge cases #57943

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

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions src/Symfony/Component/TypeInfo/Tests/Type/BackedEnumTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,16 @@ public function testToString()
$this->assertSame(DummyBackedEnum::class, (string) new BackedEnumType(DummyBackedEnum::class, Type::int()));
}

public function testIsNullable()
public function testGetTypeIdentifier()
{
$this->assertFalse((new BackedEnumType(DummyBackedEnum::class, Type::int()))->isNullable());
$this->assertSame(TypeIdentifier::OBJECT, (new BackedEnumType(DummyBackedEnum::class, Type::int()))->getTypeIdentifier());
}

public function testGetBaseType()
{
$this->assertEquals(new BackedEnumType(DummyBackedEnum::class, Type::int()), (new BackedEnumType(DummyBackedEnum::class, Type::int()))->getBaseType());
}

public function testAsNonNullable()
{
$type = new BackedEnumType(DummyBackedEnum::class, Type::int());

$this->assertSame($type, $type->asNonNullable());
}

public function testIsA()
{
$this->assertFalse((new BackedEnumType(DummyBackedEnum::class, Type::int()))->isA(TypeIdentifier::ARRAY));
Expand Down
16 changes: 12 additions & 4 deletions src/Symfony/Component/TypeInfo/Tests/Type/BuiltinTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,19 @@ public function testGetBaseType()
$this->assertEquals(new BuiltinType(TypeIdentifier::INT), (new BuiltinType(TypeIdentifier::INT))->getBaseType());
}

public function testIsNullable()
/**
* @dataProvider provideNullabilityResults
*/
public function testIsNullable(TypeIdentifier $typeIdentifier, bool $isNullable): void
{
$this->assertFalse((new BuiltinType(TypeIdentifier::INT))->isNullable());
$this->assertTrue((new BuiltinType(TypeIdentifier::NULL))->isNullable());
$this->assertTrue((new BuiltinType(TypeIdentifier::MIXED))->isNullable());
$this->assertSame($isNullable, (new BuiltinType($typeIdentifier))->isNullable());
}

public static function provideNullabilityResults(): iterable
{
foreach (TypeIdentifier::cases() as $case) {
yield $case->value => [$case, TypeIdentifier::NULL === $case || TypeIdentifier::MIXED === $case];
}
}

public function testAsNonNullable()
Expand Down
14 changes: 0 additions & 14 deletions src/Symfony/Component/TypeInfo/Tests/Type/CollectionTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,20 +79,6 @@ public function testGetBaseType()
$this->assertEquals(Type::int(), Type::collection(Type::generic(Type::int(), Type::string()))->getBaseType());
}

public function testIsNullable()
{
$this->assertFalse((new CollectionType(Type::generic(Type::builtin(TypeIdentifier::ARRAY), Type::int())))->isNullable());
$this->assertTrue((new CollectionType(Type::generic(Type::null(), Type::int())))->isNullable());
$this->assertTrue((new CollectionType(Type::generic(Type::mixed(), Type::int())))->isNullable());
}

public function testAsNonNullable()
{
$type = new CollectionType(Type::builtin(TypeIdentifier::ITERABLE));

$this->assertSame($type, $type->asNonNullable());
}

public function testIsA()
{
$type = new CollectionType(new GenericType(Type::builtin(TypeIdentifier::ARRAY), Type::string(), Type::bool()));
Expand Down
15 changes: 4 additions & 11 deletions src/Symfony/Component/TypeInfo/Tests/Type/EnumTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,14 @@ public function testToString()
$this->assertSame(DummyEnum::class, (string) new EnumType(DummyEnum::class));
}

public function testGetBaseType()
{
$this->assertEquals(new EnumType(DummyEnum::class), (new EnumType(DummyEnum::class))->getBaseType());
}

public function testIsNullable()
public function testGetTypeIdentifier(): void
{
$this->assertFalse((new EnumType(DummyEnum::class))->isNullable());
$this->assertSame(TypeIdentifier::OBJECT, (new EnumType(DummyEnum::class))->getTypeIdentifier());
}

public function testAsNonNullable()
public function testGetBaseType()
{
$type = new EnumType(DummyEnum::class);

$this->assertSame($type, $type->asNonNullable());
$this->assertEquals(new EnumType(DummyEnum::class), (new EnumType(DummyEnum::class))->getBaseType());
}

public function testIsA()
Expand Down
14 changes: 5 additions & 9 deletions src/Symfony/Component/TypeInfo/Tests/Type/GenericTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,14 @@ public function testGetBaseType()
$this->assertEquals(Type::object(), Type::generic(Type::object(), Type::int())->getBaseType());
}

/**
* This should fail! GenericType::isNullable() does not exist. We need to remove __call and use interfaces.
*/
public function testIsNullable()
{
$this->assertFalse((new GenericType(Type::builtin(TypeIdentifier::ARRAY), Type::int()))->isNullable());
$this->assertTrue((new GenericType(Type::null(), Type::int()))->isNullable());
$this->assertTrue((new GenericType(Type::mixed(), Type::int()))->isNullable());
}

public function testAsNonNullable()
{
$type = new GenericType(Type::builtin(TypeIdentifier::ARRAY), Type::int());

$this->assertSame($type, $type->asNonNullable());
//$this->assertTrue((new GenericType(Type::null(), Type::int()))->isNullable());
//$this->assertTrue((new GenericType(Type::mixed(), Type::int()))->isNullable());
}

public function testIsA()
Expand Down
90 changes: 45 additions & 45 deletions src/Symfony/Component/TypeInfo/Tests/Type/IntersectionTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,89 +17,89 @@
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\Type\BuiltinType;
use Symfony\Component\TypeInfo\Type\IntersectionType;
use Symfony\Component\TypeInfo\Type\ObjectType;
use Symfony\Component\TypeInfo\TypeIdentifier;

class IntersectionTypeTest extends TestCase
{
public function testCannotCreateWithOnlyOneType()
public function testCannotCreateWithOnlyOneType(): void
{
$this->expectException(InvalidArgumentException::class);
new IntersectionType(Type::int());
new IntersectionType(Type::object('Foo'));
}

public function testCannotCreateWithIntersectionTypeParts()
public static function getInvalidParts(): iterable
{
$foo = Type::object('Foo');
$bar = Type::object('Bar');

yield 'intersection' => [Type::intersection($foo, $bar), Type::intersection($foo, $bar)];
yield 'union' => [Type::intersection($foo, $bar), Type::intersection($foo, $bar)];
foreach (TypeIdentifier::cases() as $case) {
yield $case->value => [Type::builtin($case), Type::builtin($case)];
}
yield 'generic<builtin>' => [Type::object('Foo'), Type::generic(Type::builtin('array'), Type::string())];
yield 'collection<builtin>' => [Type::object('Foo'), Type::collection(Type::generic(Type::builtin('array')))];
}

/**
* @dataProvider getInvalidParts
*/
public function testCannotCreateWithNonObjectParts(Type ...$parts): void
{
$this->expectException(InvalidArgumentException::class);
new IntersectionType(Type::int(), new IntersectionType());

new IntersectionType(...$parts);
}

public function testSortTypesOnCreation()
public function testCreateWithObjectParts(): void
{
$type = new IntersectionType(Type::int(), Type::string(), Type::bool());
$this->assertEquals([Type::bool(), Type::int(), Type::string()], $type->getTypes());
$foo = Type::object('Foo');
$bar = Type::generic(Type::object('Bar'), Type::string());
$baz = Type::collection(Type::generic(Type::object('Baz'), Type::string()));

$type = new IntersectionType($foo, $bar, $baz);
$this->assertEquals([$bar, $baz, $foo], $type->getTypes());
}

public function testAtLeastOneTypeIs()
public function testAtLeastOneTypeIs(): void
{
$type = new IntersectionType(Type::int(), Type::string(), Type::bool());
$type = new IntersectionType(Type::object('Foo'), Type::object('Bar'), Type::object('Baz'));

$this->assertTrue($type->atLeastOneTypeIs(fn (Type $t) => 'int' === (string) $t));
$this->assertFalse($type->atLeastOneTypeIs(fn (Type $t) => 'float' === (string) $t));
$this->assertTrue($type->atLeastOneTypeIs(fn (Type $t) => 'Bar' === (string) $t));
$this->assertFalse($type->atLeastOneTypeIs(fn (Type $t) => 'Blip' === (string) $t));
}

public function testEveryTypeIs()
{
$type = new IntersectionType(Type::int(), Type::string(), Type::bool());
$this->assertTrue($type->everyTypeIs(fn (Type $t) => $t instanceof BuiltinType));
$type = new IntersectionType(Type::object('Foo'), Type::object('Bar'), Type::object('Baz'));
$this->assertTrue($type->everyTypeIs(fn (Type $t) => $t instanceof ObjectType));

$type = new IntersectionType(Type::int(), Type::string(), Type::template('T'));
$this->assertFalse($type->everyTypeIs(fn (Type $t) => $t instanceof BuiltinType));
$type = new IntersectionType(Type::object('Foo'), Type::object('Bar'), Type::generic(Type::object('Baz')));
$this->assertFalse($type->everyTypeIs(fn (Type $t) => $t instanceof ObjectType));
}

public function testGetBaseType()
{
$this->expectException(LogicException::class);
(new IntersectionType(Type::string(), Type::int()))->getBaseType();
(new IntersectionType(Type::object('Bar'), Type::object('Foo')))->getBaseType();
}

public function testToString()
{
$type = new IntersectionType(Type::int(), Type::string(), Type::float());
$this->assertSame('float&int&string', (string) $type);

$type = new IntersectionType(Type::int(), Type::string(), Type::union(Type::float(), Type::bool()));
$this->assertSame('(bool|float)&int&string', (string) $type);
}

public function testIsNullable()
{
$this->assertFalse((new IntersectionType(Type::int(), Type::string(), Type::float()))->isNullable());
$this->assertTrue((new IntersectionType(Type::null(), Type::union(Type::int(), Type::mixed())))->isNullable());
}

public function testAsNonNullable()
{
$type = new IntersectionType(Type::int(), Type::string(), Type::float());

$this->assertSame($type, $type->asNonNullable());
}

public function testCannotTurnNullIntersectionAsNonNullable()
{
$this->expectException(LogicException::class);

$type = (new IntersectionType(Type::null(), Type::mixed()))->asNonNullable();
$type = new IntersectionType(Type::object('Foo'), Type::object('Bar'), Type::generic(Type::object('Baz'), Type::string()));
$this->assertSame('Bar&Baz<string>&Foo', (string) $type);
}

public function testIsA()
{
$type = new IntersectionType(Type::int(), Type::string(), Type::float());
$type = new IntersectionType(Type::object('Foo'), Type::object('Bar'));
$this->assertFalse($type->isA(TypeIdentifier::ARRAY));

$type = new IntersectionType(Type::int(), Type::string(), Type::union(Type::float(), Type::bool()));
$type = new IntersectionType(Type::object('Foo'), Type::object('Bar'));
$this->assertFalse($type->isA(TypeIdentifier::INT));

$type = new IntersectionType(Type::int(), Type::union(Type::int(), Type::int()));
$this->assertTrue($type->isA(TypeIdentifier::INT));
$type = new IntersectionType(Type::object('Foo'), Type::object('Bar'));
$this->assertTrue($type->isA(TypeIdentifier::OBJECT));
}
}
4 changes: 2 additions & 2 deletions src/Symfony/Component/TypeInfo/Tests/Type/ObjectTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ public function testToString()
$this->assertSame(self::class, (string) new ObjectType(self::class));
}

public function testIsNullable()
public function testGetTypeIdentifier(): void
{
$this->assertFalse((new ObjectType(self::class))->isNullable());
$this->assertSame(TypeIdentifier::OBJECT, (new ObjectType(self::class))->getTypeIdentifier());
}

public function testGetBaseType()
Expand Down
Loading