PHPStan Bug Report: __construct() Broader Types Override Property Types
Introduction
This article addresses a bug report concerning PHPStan, a static analysis tool for PHP, specifically regarding how it handles type inference for readonly
properties initialized in the __construct()
method. The issue arises when a broader type is inferred within the constructor, potentially overriding a more precise type declared for the property. This can lead to unexpected errors and hinder the accuracy of static analysis.
Background on PHPStan and Type Inference
PHPStan is a powerful static analysis tool that helps developers identify errors in their PHP code without actually running it. It analyzes the code's structure and types to detect potential issues such as undefined variables, incorrect function calls, and type mismatches. Type inference is a crucial aspect of static analysis, where the tool attempts to automatically determine the data type of a variable or expression based on its usage.
When dealing with classes and properties, PHPStan relies on type declarations to understand the expected data types. However, it also considers assignments and operations performed within the class methods, including the constructor (__construct()
), to refine its understanding of the property types. In most cases, this dynamic type inference enhances the accuracy of the analysis. However, there are scenarios where it can lead to unexpected behavior, as highlighted in this bug report.
The Bug Report: Broader Types in __construct()
The core of the issue lies in how PHPStan remembers types from the __construct()
method, particularly after a specific change introduced in this pull request. The intention behind this change was to narrow the type of readonly
properties for more precise analysis. While this approach is generally beneficial, it can lead to problems when the type inferred within the constructor is broader than the type explicitly declared for the property. In such cases, PHPStan might incorrectly remember the broader type, leading to false positives or missed errors.
Code Snippet Illustrating the Problem
To better understand the issue, let's analyze the code snippet provided in the bug report:
<?php
class Foo
{
public readonly string $test;
public function __construct(int|string $input)
{
$this->test = $input;
}
public function doFoo(): void
{
echo $this->test;
}
public function doBar(): void
{
$test = $this->test;
if (is_int($test)) {
echo $test;
}
}
}
In this example, the Foo
class has a readonly
property $test
declared as type string
. The constructor accepts an argument $input
which can be either an int
or a string
. Inside the constructor, $this->test
is assigned the value of $input
. Due to this assignment, PHPStan infers that $this->test
can be either an int
or a string
within the constructor's scope.
Expected vs. Actual Output
The bug report highlights the discrepancy between the expected and actual behavior. While the error on line 15 (likely related to the potential for a non-string value being assigned to a string property) is considered acceptable, the error on line 19 is deemed problematic. The reasoning is that PHPStan should not remember the broader type (int|string
) inferred in the constructor and apply it throughout the class, especially when a more specific type (string
) is already declared for the property.
Detailed Explanation of the Issue
The core problem is that PHPStan, after encountering the $this->test = $input;
assignment in the constructor, remembers the broader type int|string
for the $test
property. This is because the $input
parameter is declared as int|string
, and PHPStan accurately infers that the assigned value could be either type. However, this inference overrides the explicit string
type declaration for the $test
property.
As a result, when PHPStan analyzes the doBar()
method, it still considers $this->test
to be of type int|string
. This leads to the error on line 19, even though the code within the if (is_int($test))
block clearly handles the case where $test
might be an integer. The issue is that PHPStan's type analysis is being influenced by the broader type inferred in the constructor, even within a context where the type is explicitly checked.
Analysis of the Problem and Potential Solutions
The bug report raises a valid concern about the interaction between constructor type inference and property type declarations. While remembering types from the constructor can be beneficial for narrowing types in some cases, it can also lead to unintended consequences when a broader type is inferred. This highlights a trade-off between the precision of type inference and the potential for overriding explicit type declarations.
Potential Solutions
Several approaches could be considered to address this issue:
- Prioritize Property Type Declarations: PHPStan could be modified to prioritize the explicitly declared type of a property over any broader types inferred in the constructor. This would ensure that the declared type serves as the primary source of truth for the property's type.
- Context-Specific Type Narrowing: PHPStan could implement more sophisticated context-specific type narrowing. In the example code, even though the broader type
int|string
is inferred in the constructor, PHPStan should be able to recognize that within theif (is_int($test))
block,$test
is known to be an integer. This would prevent the error on line 19. - Configuration Options: PHPStan could provide configuration options to control the behavior of constructor type inference. This would allow developers to choose whether they want PHPStan to remember broader types from the constructor or to prioritize property type declarations.
- Improved Type Variance Handling: The underlying issue is related to the concept of type variance, specifically covariance and contravariance. PHPStan's type system could be enhanced to better handle type variance in the context of property assignments and method calls.
Preferred Solution
Among these options, prioritizing property type declarations seems to be the most straightforward and intuitive solution. It aligns with the principle of explicit type declarations serving as the primary source of type information. This approach would minimize the risk of unintended type overrides and improve the predictability of PHPStan's analysis.
Implications and Impact
This bug report underscores the importance of careful consideration when implementing type inference in static analysis tools. While dynamic type inference can enhance accuracy, it must be balanced against the need to respect explicit type declarations and avoid unintended type overrides. The issue described in the bug report can lead to false positives, which can be frustrating for developers and reduce the effectiveness of static analysis.
Impact on Developers
- False Positives: Developers might encounter errors flagged by PHPStan that are not actually errors, leading to wasted time and effort in debugging.
- Missed Errors: In some cases, the broader type inferred in the constructor might mask genuine type errors, preventing PHPStan from identifying potential issues in the code.
- Reduced Confidence in Static Analysis: If developers encounter frequent false positives or missed errors, they might lose confidence in the accuracy of static analysis and be less likely to use it effectively.
Broader Implications for Static Analysis
This issue highlights a general challenge in static analysis: balancing the desire for precise type inference with the need to respect explicit type information. Static analysis tools must carefully consider the trade-offs involved in different type inference strategies and strive to provide accurate and reliable results.
Conclusion
The bug report regarding broader types from __construct()
overriding declared property types in PHPStan is a significant issue that needs to be addressed. The current behavior can lead to false positives and reduce the effectiveness of static analysis. Prioritizing property type declarations or implementing context-specific type narrowing are potential solutions that could mitigate this problem. Addressing this issue will improve the accuracy and reliability of PHPStan, making it a more valuable tool for PHP developers.
Repair Input Keyword
Should PHPStan remember broader types inferred in the constructor when a more specific type is declared for the property?
SEO Title
PHPStan Bug Report __construct() Broader Types Override Property Types