Skip to content

Commit

Permalink
Simplify library API
Browse files Browse the repository at this point in the history
the main use case is to create a signature using an existing closure, which greatly reduces the amount of code needed if we limit the library to this directly.
In theory, it should also perform better.
  • Loading branch information
dktapps committed Mar 29, 2024
1 parent 19677a2 commit 21b398a
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 283 deletions.
64 changes: 35 additions & 29 deletions src/MatchTester.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@

namespace DaveRandom\CallbackValidator;

use DaveRandom\CallbackValidator\Type\BaseType;
use DaveRandom\CallbackValidator\Type\BuiltInType;
use DaveRandom\CallbackValidator\Type\IntersectionType;
use DaveRandom\CallbackValidator\Type\NamedType;
use DaveRandom\CallbackValidator\Type\UnionType;

final class MatchTester{
/**
* Thou shalt not instantiate
*/
private function __construct(){}

public static function isCovariant(?BaseType $acceptingType, ?BaseType $givenType) : bool{
public static function isCovariant(?\ReflectionType $acceptingType, ?\ReflectionType $givenType) : bool{
// If the super type is unspecified, anything is a match
if($acceptingType === null || ($acceptingType instanceof NamedType && $acceptingType->type === BuiltInType::VOID)){
if($acceptingType === null || ($acceptingType instanceof \ReflectionNamedType && $acceptingType->getName() === BuiltInType::VOID->value)){
return true;
}

Expand All @@ -25,72 +21,82 @@ public static function isCovariant(?BaseType $acceptingType, ?BaseType $givenTyp
return false;
}

// given type cannot accept null if the accepting type does not
// we don't want to consider allowsNull except at the top level
if(!$acceptingType->allowsNull() && $givenType->allowsNull()){
return false;
}

return self::isCompositeTypeCovariant($acceptingType, $givenType);
}

private static function isCompositeTypeCovariant(\ReflectionType $acceptingType, \ReflectionType $givenType) : bool{
//composite type acceptance:
//super named type -> named, union (all parts must be accepted by super), intersection (at least 1 part must be accepted by super)
//super union -> named (must be accepted by at least 1 super), union (all parts must be accepted by at least 1 super), intersection (at least 1 part must be accepted by at least 1 super?)
//super intersection -> named (must be accepted by all supers), union (all parts must be accepted by all supers), intersection (all parts must be accepted by all supers)

//given union is handled the same no matter what the accepting type is
//ensure all parts are covariant with the accepting type
if($givenType instanceof UnionType){
foreach($givenType->types as $type){
if(!self::isCovariant($acceptingType, $type)){
if($givenType instanceof \ReflectionUnionType){
foreach($givenType->getTypes() as $type){
if(!self::isCompositeTypeCovariant($acceptingType, $type)){
return false;
}
}

return true;
}

if($acceptingType instanceof NamedType){
if($acceptingType instanceof \ReflectionNamedType){
//at least 1 part of a given intersection must be covariant with the accepting type
//given intersection can only be compared with a named type to validate variance - the parts cannot
//be individually tested against a composite type
if($givenType instanceof IntersectionType){
foreach($givenType->types as $type){
if(self::isCovariant($acceptingType, $type)){
if($givenType instanceof \ReflectionIntersectionType){
foreach($givenType->getTypes() as $type){
if(self::isCompositeTypeCovariant($acceptingType, $type)){
return true;
}
}

return false;
}

if($givenType instanceof NamedType){
$acceptingTypeName = $acceptingType->type;
$givenTypeName = $givenType->type;
if($givenType instanceof \ReflectionNamedType){
$acceptingTypeName = $acceptingType->getName();
$givenTypeName = $givenType->getName();
if($acceptingTypeName === $givenTypeName){
// Exact match
return true;
}

if($acceptingTypeName === BuiltInType::MIXED && $givenTypeName !== BuiltInType::VOID){
if($acceptingTypeName === BuiltInType::MIXED->value && $givenTypeName !== BuiltInType::VOID->value){
//anything is covariant with mixed except void
return true;
}

if($acceptingTypeName === BuiltInType::FLOAT && $givenTypeName === BuiltInType::INT){
if($acceptingTypeName === BuiltInType::FLOAT->value && $givenTypeName === BuiltInType::INT->value){
//int is covariant with float even in strict mode
return true;
}

// Check iterable
if($acceptingTypeName === BuiltInType::ITERABLE){
return $givenTypeName === BuiltInType::ARRAY
if($acceptingTypeName === BuiltInType::ITERABLE->value){
return $givenTypeName === BuiltInType::ARRAY->value
|| $givenTypeName === \Traversable::class
|| \is_subclass_of($givenTypeName, \Traversable::class);
}

// Check callable
if($acceptingTypeName === BuiltInType::CALLABLE){
if($acceptingTypeName === BuiltInType::CALLABLE->value){
return $givenTypeName === \Closure::class
|| \method_exists($givenTypeName, '__invoke')
|| \is_subclass_of($givenTypeName, \Closure::class);
}

if($acceptingTypeName === BuiltInType::OBJECT){
if($acceptingTypeName === BuiltInType::OBJECT->value){
//a class type is covariant with object
return !$givenTypeName instanceof BuiltInType;
return BuiltInType::tryFrom($givenTypeName) === null;
}

return is_string($givenTypeName) && is_string($acceptingTypeName) && \is_subclass_of($givenTypeName, $acceptingTypeName);
Expand All @@ -99,21 +105,21 @@ public static function isCovariant(?BaseType $acceptingType, ?BaseType $givenTyp
throw new \AssertionError("Unhandled reflection type " . get_class($givenType));
}

if($acceptingType instanceof UnionType){
if($acceptingType instanceof \ReflectionUnionType){
//accepting union - the given type must be covariant with at least 1 part
foreach($acceptingType->types as $type){
if(self::isCovariant($type, $givenType)){
foreach($acceptingType->getTypes() as $type){
if(self::isCompositeTypeCovariant($type, $givenType)){
return true;
}
}

return false;
}

if($acceptingType instanceof IntersectionType){
if($acceptingType instanceof \ReflectionIntersectionType){
//accepting intersection - the given type must be covariant with all parts
foreach($acceptingType->types as $type){
if(!self::isCovariant($type, $givenType)){
foreach($acceptingType->getTypes() as $type){
if(!self::isCompositeTypeCovariant($type, $givenType)){
return false;
}
}
Expand Down
42 changes: 0 additions & 42 deletions src/ParameterInfo.php

This file was deleted.

Loading

0 comments on commit 21b398a

Please sign in to comment.