Phan NEWS
Jan 16 2026, Phan 6.0.0
Bug fixes:
- Fixed crash when analyzing implicit float-to-int conversion in modulo operator (#5392)
- Fixed
PhanUnreferencedClosurebeing emitted too aggressively for closure function parameters (#5389) - Fixed false positives for readonly properties in traits when targeting PHP < 8.2 (#5390)
- Fixed detection of property usages from trait methods (#5391)
- Fixed
matchexpression type narrowing when multiple conditional expressions are used (#5398) - Fixed
PhanTypeMismatchArgumentSuperTypefalse positives on closure parameters (#5402) - Fixed array shape narrowing with
count()- only narrow closed array shapes with optional keys (#5406) - Improved distinction between list-like array shapes and general array shapes (#5407)
- Fixed "Possibly invalid offset" only appearing for some accesses in a foreach loop (#5409)
- Fixed
IS_PROMOTED_PROPERTYflag detection for constructor property promotion (#5411, #5416) - Fixed non-exhaustive
matchdetection with literal bool edge cases (#5388)
Improvements:
- Performance optimizations for template type substitution and FQSEN caching (#5417, #5418)
- Early-exit checks in
Method::cloneWithTemplateParameterTypeMapto avoid unnecessary cloning - Optimized
CallableParamPluginfor functions with no parameters - Two-level cache for
FullyQualifiedClassElement::makeusing object IDs
- Early-exit checks in
- Allow Symfony 8.0 compatibility (#5401)
- Fixed
composer-patchespackage location for PHP 8.5 compatibility (#5387)
Nov 20 2025, Phan 6.0.0-beta
Breaking changes:
- Requires PHP 8.1+ to run and minimum target version is now PHP 8.1
- Requires php-ast 1.1.3+ for PHP 8.4+ analysis (AST version 110/120 support)
- The
-iCLI option is now an alias of--incrementalinstead of--ignore-undeclared - Renamed the
PhanPluginCanUsePHP71Voidissue toPhanPluginCanUseVoidReturnType - Dropped the
allow_method_param_type_wideningconfig option (contravariance is now always allowed) - Dropped the
backward_compatibility_checksconfig option, along with its respective--backward-compatibility-checksand-bCLI flags
New features (Analysis):
- Large literal arrays are now summarized to representative samples during parsing, significantly reducing peak memory when analyzing massive datasets.
- Default limits can be tuned via the new config options
ast_trim_max_elements_per_level(default 256) andast_trim_max_total_elements(default 512).
- Default limits can be tuned via the new config options
- Union types are automatically clamped once they exceed
max_union_type_set_size(default 1024), preventing runaway growth from deeply nested array merges while keeping type inference useful. - These trim/clamp thresholds are also exposed as CLI flags (
--ast-trim-max-elements-per-level,--ast-trim-max-total-elements,--max-union-type-set-size) for easy experimentation without editing config files. - Detect implicit float-to-int conversion in modulo operator (deprecated in PHP 8.1)
- New issue type: PhanTypeInvalidModuloOperand
- Warns when float types are used with the modulo (%) operator
- Improved analysis of intersection types with unknown classes (#4431)
- When an intersection type includes both known and unknown classes, Phan now analyzes the known types
- Method calls and property access are checked against known classes in the intersection
- Enables type checking even when some dependencies are missing or stubs are incomplete
- Also improves analysis of catch blocks with mixed known/unknown exception types
- Full support for PHP 8.5 features:
- #[NoDiscard] attribute support with detection of ignored return values (PhanNoDiscardReturnValueIgnored)
- #[Override] attribute extended to properties (in addition to methods)
- Pipe operator (
|>) with full type inference through piped call chains (void)cast support for suppressing NoDiscard warnings- Updated function signatures for PHP 8.5 standard library changes
- Full support for PHP 8.4 property hooks
- Parse and validate property hook syntax (get/set hooks)
- Parameter type checking for set hook parameters
- Validation of hooks with default values (PhanPropertyHookWithDefaultValue)
- Validation of readonly properties with set hooks (PhanReadonlyPropertyHasSetHook)
- Return type compatibility validation (PhanPropertyHookIncompatibleReturnType)
- Parameter type compatibility validation (PhanPropertyHookIncompatibleParamType)
- Final hook override detection (PhanPropertyHookFinalOverride)
- Support for PHP 8.4 #[Deprecated] attribute
- Recognize #[Deprecated] attribute on functions, methods, and class constants
- Works alongside existing @deprecated PHPDoc comment support
- Emits PhanDeprecatedFunction and PhanDeprecatedClassConstant warnings when deprecated elements are used
- Support for new PHP 8.4 functions and methods
- DateTime::createFromTimestamp() / DateTimeImmutable::createFromTimestamp()
- DOMXPath::registerPHPFunctionNS()
- All new PHP 8.4 functions: array_find, array_find_key, array_any, array_all, mb_trim, mb_ltrim, mb_rtrim, mb_ucfirst, mb_lcfirst, and more
- Full support for "new without parentheses" syntax (e.g.,
new MyClass()->method())- Type inference works correctly across method chains
- Property and array access supported
- Multiline doc comments for
@param,@var, and@return- Doc comment parsing now normalizes multi-line
@param,@var, and@return
annotations before analysis, allowing nested array/generic syntax to be laid out
across several lines without being misinterpreted. - Extended multiline support to Phan-specific annotations:
@phan-param,@phan-var,@phan-return,@phan-real-return,@phan-property*,@phan-assert,@phan-type,@phan-implements,@phan-extends,@phan-inherits,@phan-use(#4252) PHPDocRedundantPluginnow flags redundant@varannotations on typed
properties, matching its existing coverage for functions and methods.
- Doc comment parsing now normalizes multi-line
PreferNamespaceUsePluginnow handles union types- Improved generics support (#5182)
- Enhanced template type handling and generic type inference
- Better resolution of generic types in complex scenarios
- Full support for PHP 8.3 typed class constants (#5140, #5128)
- Inheritance checking and validation
- New issue types: PhanTypeMismatchDeclaredConstant, PhanTypeMismatchDeclaredConstantNever, PhanConstantTypeMismatchInheritance
- Proper type narrowing for class constants in conditions (#5127)
- Literal type exclusion in
!in_array()checks (#5185)- Type narrowing when checking values against literal arrays (up to 50 elements)
- Improves precision when checking against known sets of values
- Reference assignment literal type erasure (#5197, #4354)
- Variables involved in reference assignments (
$var2 =& $var1) now have their literal types erased - Prevents incorrect literal type tracking when variables are aliased
- Applies to both sides of reference assignment and persists across subsequent assignments
- Variables involved in reference assignments (
- Enhanced type inference improvements:
array_filter()recognizes null-stripping callbacks and keeps element types non-null (#5100)array_chunk()return type inference based onpreserve_keysparameter (#5165)constant()return type inference from Phan's constant table for non-dynamic constants (#5157)- stdClass property inference from array shape casts (#5151)
- Better static return type resolution preserving intersection types and generics (#5126)
- Improved
foreachiterator type inference honoring explicit iterator generics (#5108) - Conditional type refinement after array field checks (#5179)
- Static properties in conditional expressions (#5194)
- Type conditions with intermediary variables (#5125)
- Enum property access in constant expressions (#5158): support for
self::CASE->value
- New detection capabilities:
- UncoveredEnumCasesInMatchPlugin (#5164): Detects when match expressions with enum conditions don't cover all enum cases
- Duplicate static variable detection (#5176): New PhanDuplicateStaticVariable issue (fatal error in PHP 8.3+)
- Multiple readonly property assignment (#5175): New PhanAccessReadOnlyPropertyMultipleTimes issue
- Redundant boolean type combinations (#5174): Warns on redundant type combinations
- Trait constant compatibility (#5119): New PhanIncompatibleCompositionConstant for conflicting trait constants
- Interface traits and readonly classes (#5118): Warns when interfaces use traits or readonly classes use non-readonly trait properties
- Improved unused variable detection (#5093): Catches more cases with compound assignment operators (
+=,-=, etc.) - Redundant property comments (#5107): Warns on redundant
@vardocblocks on typed properties
New features (CLI):
- Git-style config discovery (#5092):
- Searches parent directories for
.phan/config.php - Filters output when running from subdirectories
- New
--subdirectory-onlyflag for analyzing specific modules with better performance
- Searches parent directories for
-n/--no-config-fileimplicitly limits analysis to just the files provided
on the command line (e.g.phan -n test1.php test2.php) avoiding analysis of
the rest of the project when you only want quick ad-hoc checks.- Added support for incremental analysis, where only changed files and their dependents
will be analyzed on subsequent runs. Significantly speeds up re-analysis.- Add the
--incremental/-ioption that enables incremental analysis. - Add the
--force-full-analysisflag to force a full re-analysis of all files, ignoring the incremental analysis manifest. - Add the
--no-incremental/-Noption that disables incremental analysis (useful if it was enabled in config.php).
- Add the
Performance improvements:
- phan_helpers C extension integration (#5096):
- Optional C extension for 2-3x faster AST hashing and type deduplication
- Overall analysis speedup: 5-15% for large projects
- Automatically detected and used when available
- Internal PHP stub definitions are now cached between CodeBase instances, so repeated test runs and daemon/server reloads reuse the previously parsed classes/functions without reparsing dozens of stub files on every initialization.
- Conditional visitor optimization (#5186):
- Skip visitor creation for ~60-70% of if statements (those without else/elseif)
- Reduces memory usage and improves analysis speed
- Incremental analysis support (see CLI features above)
- Subdirectory-only mode for faster focused analysis
New features (Tools):
- Add
tool/add_suppressions.phpto assist in adding@suppressor@phan-suppressannotations to files- Useful for batch suppressing specific issue types across the codebase
- See
tool/SuppressionsToolImplementation.mdfor technical details
Bug fixes:
Type Inference & Analysis:
- Fixed too strict type inference after
!empty()condition on arrays (#5295) - Fixed union types containing both known objects and mixed being checked too strictly (#5292)
- Fixed
PhanAccessReadOnlyPropertyMultipleTimesfailing to account for mutually exclusive branches (#5285) - Fixed
declareblocks causing Phan to lose track of declared variables (#5284) - Fixed conditionals causing wrong type inference for static properties (#5283)
- Fixed too strict
PhanTemplateTypeConstraintViolationwhen type can't be resolved (#5282) - Fixed Phan using unrelated array elements when inferring types (#5281)
- Fixed array shape type inference for
is_objectand!is_arraychecks with mixed types (#5277) - Fixed static property type narrowing with type-check functions like
is_null()and!is_null()(#5277) - Fixed type accumulation for interface-typed properties ensuring method calls are validated against interface contract (#5300)
- Fixed spurious
PhanPossiblyUndeclaredVariablewith function calls and following usage (#5269) - Fixed
PhanTypeMismatchGeneratorYieldKeyfalse positive when yield has no key (#5258) - Fixed
PhanAccessOwnConstructorincorrectly warning on valid usage ofself::__construct(#5257) - Fixed unnecessary array key aliasing in type narrowing when assignment happens in foreach iteration (#5255)
- Fixed array unions containing mixed types being checked too strictly for array offsets (#5299)
- Fixed condition-based type narrowing using outdated context when variable is modified in conditionals (#5301)
- Fixed
isset()andempty()warning on undefined properties (now only warns on direct access and unset) (#5273) - Fixed property type narrowing scope leaking into unrelated code paths (#5249)
- Fixed ArrayAccess type checking not validating offsetGet signature (#5251)
- Fixed DNF type false positive when using null defaults (#5252)
- Fixed
array_combinesignature being too strict for null values (#5219) - Improved type inference for
get_class()to return more specific class-string types (#5274) - Improved handling of
getIterator()returning Iterator with explicit generics (#5108) - Traits can now declare
@require-extends/@require-implements(and their Psalm aliases) to ensure consuming classes satisfy those requirements - Fixed
PhanPossiblyInfiniteRecursionSameParamsfalse positive for instance methods that guard recursion with property state (#5371) - Fixed
PhanTypeMismatchDeclaredConstantfalse positives for enum cases whose values are strings/ints (#5378) - Improved parameter and property default type representation in error messages (#5271)
Tooling:
- Baselines generated with
--save-baselinenow store suppressions per symbol (class/method/function) instead of line numbers and normalize anonymous class/closure names, so suppressions remain stable when code moves or hashes change (#5381/#5383)
Plugins:
- Fixed UnusedSuppressionPlugin incorrectly flagging magic method suppressions in class doc comments as unused (#5304)
- Added missing ANSI color mappings for terminal output (#5294)
- Improved confusing
PhanSuspiciousValueComparisonmessage when all literal possibilities exhausted (#5276) - Fixed issue message for
PhanCommentAbstractOnInheritance(#5265)
Other Fixes:
- Fixed nested closure scope tracking bug (#5291)
- Fixed multiline PHPDoc comment parsing issues (#5293)
- Fixed private property static/non-static mismatch detection in traits (#5247)
- Fixed internal stubs not being included in releases (#5244)
- Fixed nullsafe property access: No more strict object checking false positives with
?->(#5112) - Fixed trait method multi-level inheritance: Track trait methods through multiple inheritance levels (#5120)
- Fixed DNS aliases: Corrected reversed DNS aliases (#5170)
- Fixed private final constructor: Don't warn on private final constructors (exempted in PHP 8.0+) (#5169)
- Fixed float-to-int in modulo: Detect implicit float-to-int conversion in
%operator (#5189) - Fixed sibling type compatibility: Assignment visitor type checking now works correctly (#5168)
- Fixed dynamic array offset constants: Skip premature constant resolution for
define()(#5156) - Fixed unary operator type aggregation: Corrected numeric fallback ordering bug (#5146)
- Fixed xml_parser_create signature: Corrected incorrect signature (#5139)
- Fixed crash with intersection types: No more
EmptyFQSENExceptionwith--analyze-twice(#5086) - Fixed class constant regression: Corrected typed constant false positives after #5128 (#5133)
- Fixed intersection types with unknown classes: Proper method resolution when intersection contains both known and unknown classes (#5190)
- Fixed template type compatibility: No more false positive
PhanTypeMismatchDeclaredReturnwith template types (#5181) - Fixed readonly property access: No more false positives with
isset()on readonly properties in@phan-side-effect-freeclasses (#5180) - Fixed switch fall-through variable tracking: Variable definitions now flow correctly through fall-through cases (#5155)
- Fixed try/catch variable scope: Variable definitions in try blocks with finally clauses now track correctly (#5152, #5131)
- Fixed global variable type pollution in daemon mode: Incremental re-analysis no longer pollutes global variable types (#5183)
- Fixed ternary in function arguments: Proper type narrowing for conditional expressions in arguments (#5166)
- Fixed empty array vs non-empty union: Re-checks union arguments mixing empty and non-empty arrays (#5115)
- Fixed array map callback analysis: No more false positive
PhanParamTooFewUnpackwith array_map (#5114) - Fixed static call on trait properties: No more false positives after instanceof checks (#5111)
- Fixed never return type inheritance: Proper detection of inherited never methods via
parent::/static::(#5109) - Fixed nested array shape field refinement: Suppressed property mismatch warnings when refining nested fields (#5106)
- Fixed enums as class constants: Allow enum types as class constant values (#5103)
- Fixed SID constant handling: Special-case handling for dynamically defined SID constant (#5150)
- Fixed reference assignment literal type erasure: Variables involved in reference assignments now correctly erase literal types to prevent false positives (#5197)
PHPDoc & Attributes:
- Fixed @deprecated detection for callable parameters: Emit PhanDeprecatedFunction when deprecated functions are passed as callable string arguments (#4858)
- Fixed @phan-suppress on class constants: Suppression annotations are no longer ignored (#5188)
- Fixed @phan-mandatory-param inheritance: Inherit annotation from interface methods (#5116)
- Fixed @phan-pure inheritance: Exclude
__calland__callStaticfrom automatic inheritance (#5124) - Fixed internal method purity: ArrayObject->count() now inherits pure flag from Countable (#5122)
- Fixed PHPDoc type inheritance: Proper inheritance of parameter types from interfaces (#5117)
Plugin Fixes:
- Fixed MoreSpecificElementTypePlugin: No more false positives with
array{}|non-empty-array<K,V>(#5187) - Fixed UnknownElementTypePlugin: No more false positives on inherited methods (#5177)
- Fixed RedundantConditionVisitor: No more false positives with static property empty() checks (#5148)
- Fixed CompactPlugin: Added check for possibly undefined variables (#5167)
- Fixed PHPDocRedundantPlugin: Added auto-fixer for redundant property comments (#5178)
Loop & Control Flow:
- Fixed possibly infinite loop detection: No more false positives when loop condition uses array count (#5153)
- Fixed
!isset()variable tracking: Possibly-undefined flag preservation now works correctly (#5172) - Fixed redundant condition after
empty(): Proper static property handling (#5148)
AST Compatibility:
- Fixed AST version 110/120 compatibility for PHP 8.4
- Updated TolerantASTConverter to support AST version 120
- Fixed AST structural changes: closure 'name' field removal, parameter 'hooks' field addition, property 'hooks' field addition
- Fixed
clonebeing incorrectly treated as a function call in AST version 110+ (it's now AST_CALL instead of AST_CLONE)