-
-
Notifications
You must be signed in to change notification settings - Fork 934
Description
Feature request
The idea is we should ban $type instanceof *Type in both inside and outside of PHPStan. I've outlined the reasons in a recent article:
PHP is a complex language. A type often stands in for a different type. A string can be a callable. A callable can be an array. Asking
$type instanceof StringTypedoesn't cover all possible situations, because a lot of other Type implementations can be a string too.
So we changed the preferred way to ask "is this an array?" to
Type::isArray(): TrinaryLogic. And "is this a string?" toType::isString(): TrinaryLogic. Every step like that helps us to get rid of a lot of bugs.
When we replace all instances of
$type instanceof *Type, theTypeinterface is going to have hundreds of methods. And I'm persuaded it's the correct solution to this problem 🤣
So the reasons are:
- Enforce correct usage and cover more scenarios. Currently people have
$type instanceof ConstantStringTypein their code which doesn't handle'foo'|'bar'. If we force them to useType::getConstantStrings(): list<ConstantStringType>it's gonna handle the union automatically too. - This is gonna directly lead to less bugs "for free". I wouldn't have foreseen that this implement isClassStringType() on Type phpstan-src#1970 is gonna lead to this Interface method reported to not exist in spite of manual check #6147 being fixed.
Remaining refactorings
- TypeUtils::getConstantStrings, ConstantStringType -> Type::getConstantStrings()
- TypeUtils::getConstantIntegers, getIntegerRanges, ConstantIntegerType, IntegerRangeType (not sure what to replace it with, IntegerRangeType complicates things.
- TypeUtils::getConstantTypes, getAnyConstantTypes - no known usages, maybe just deprecate it
- TypeUtils::getDirectClassNames -> Type::getObjectClassNames()
- TypeUtils::getConstantScalars - Type::getConstantScalars()
- TypeUtils::getEnumCaseObjects
- TypeUtils::toBenevolentUnion
- TypeUtils::flattenTypes
- VoidType -> isVoid
- ResourceType -> isResource
- ConstantType
- LateResolvableType
Here are some of the hardest. There isn't going to be a single replacement method for every type, we need to figure out why we ask "$type instanceof ObjectType" and come up with the right use-case method for that on Type.
- SubtractableType
- ObjectWithoutClassType -> isObject (&& getObjectClassNames returns empty array)
- StaticType, ThisType
- ObjectType
- AccessoryType, TypeUtils::getAccessoryTypes
- TemplateType
- MIxedType
- ErrorType
- NeverType
- CompoundType (this one has to be done as the last one). It's heavily used in Type::accepts() and Type::isSuperTypeOf(). The implementation of these methods should be a lot simpler after the other refactorings are done.
The endgame
And once we provide methods for all use cases on Type and we deprecate $type instanceof *Type, we'll be able to do:
- Type implementations are gonna be a flat structure. No more inheritance.
ConstantStringTypewill not extendStringTypeetc. - There isn't going to be any need for
CompoundType. - Get rid of TypeWithClassName
- There's going to be a TemplateType class (currently it's an interface) and all bounds will be supported "for free".
- There's going to be a SubtractedType class (currently it's a SubtractableType interface with a few implementations) and all types will be subtractable "for free".