gh-2706: Switch to PHPStan docblock parser#2759
gh-2706: Switch to PHPStan docblock parser#2759bart-jaskulski wants to merge 29 commits intophpactor:masterfrom
Conversation
lib/WorseReflection/Bridge/Phpactor/DocblockParser/PHPStanDocblockParserFactory.php
Outdated
Show resolved
Hide resolved
| { | ||
| $properties = []; | ||
| // merge read/write virtual properties | ||
| foreach ($this->node->getPropertyTagValues() as $propertyTag) { |
There was a problem hiding this comment.
Here only @property tag is listed, without @property-read and @property-write, but I'm not sure if Phpactor has support for those
There was a problem hiding this comment.
Phpactor supports them AFAIK but only in the sense that it interprets them as @property
There was a problem hiding this comment.
Added @property-read and @property-write to parsed property types
lib/WorseReflection/Bridge/Phpactor/DocblockParser/PHPStanTypeConverter.php
Outdated
Show resolved
Hide resolved
|
|
||
| $type = $this->convert($callableNode->returnType); | ||
|
|
||
| if (((string) $callableNode->identifier) === 'Closure') { |
There was a problem hiding this comment.
This may be unnecessary, would require a test case
| use Prophecy\Argument; | ||
| use Prophecy\PhpUnit\ProphecyTrait; | ||
|
|
||
| class PHPStanDocblockParserFactoryTest extends IntegrationTestCase |
There was a problem hiding this comment.
This is nearly a raw copy of DocblockParserFactoryTest
...rseReflection/Tests/Unit/Bridge/Phpactor/DocblockParser/PHPStanDocblockParserFactoryTest.php
Show resolved
Hide resolved
...rseReflection/Tests/Unit/Bridge/Phpactor/DocblockParser/PHPStanDocblockParserFactoryTest.php
Outdated
Show resolved
Hide resolved
...rseReflection/Tests/Unit/Bridge/Phpactor/DocblockParser/PHPStanDocblockParserFactoryTest.php
Show resolved
Hide resolved
|
|
||
| yield 'conditional type' => [ | ||
| '/** @return ($foo is true ? string : int) */', | ||
| TypeFactory::parenthesized( |
There was a problem hiding this comment.
Again, PHPStan return it correctly, but without parenthesis
There was a problem hiding this comment.
ConditionalTypes are now wrapped in ParenthesizedType by default with PHPStan parser, but this seems fake to me
|
some before/after benchmarks:
|
|
Besides What would be your take on that? And additionally, what are the best ways to test the Phpactor after those changes, specifically, where this phpdoc parsing is used? Apart from phpunit tests, ofc |
we can leave that as is for now and focus on worse-reflection which is where most of the value comes in (analysis). We can then look at if/how we can replace DocblcokParser elsewhere (one of it's design principles was that it be "lossless" so you can reconsitute the docblock from the AST - but PHPStan may also fulfil that requirement now - but not sure). the best way to test phpactor is to run the tests? or? but let's run some benchmarks and see how it compares... |
| use PHPStan\PhpDocParser\Parser\TokenIterator; | ||
| use PHPStan\PhpDocParser\Parser\TypeParser; | ||
|
|
||
| class PHPStanDocblockParserFactory implements DocBlockFactory |
There was a problem hiding this comment.
this would now be a bridge to PHPStan, so the namespace for this code should be ...\Bridge\PHPStan\
There was a problem hiding this comment.
moved to separate namespace, yet for now I left the (redundant) PHPStan prefix in class names (easier during testing and switching with original parser)
| if (trim($docblock) === '') { | ||
| return new PlainDocblock(); | ||
| } | ||
|
|
There was a problem hiding this comment.
note that you will need to filter for "supported tags" by regex before proceeding to parse as with the previous factory (maybe it can be done with a decorator). Otherwise we parse every single docblock whether we extract info from it or not which will have a pretty massive performance impact.
There was a problem hiding this comment.
By supported you mean those, which has according Type classes in worse reflection? I've ditched this, because I thought those are tags supported by doc parser
There was a problem hiding this comment.
i mean:
const SUPPORTED_TAGS = [
'property',
'var',
'param',
'return',
'method',
'type',
'deprecated',
'extends',
'implements',
'template',
'template-covariant',
'template-extends',
'mixin',
'throws',
'assert',
];
are the tags that are analysed, if there is a docblock that has none of those then we don't need to parse it.
|
I've been looking at benchmarks from CI: commit from master: https://github.com/phpactor/phpactor/actions/runs/11244200976/job/31261739177#step:5:84 There's a slight improvement, but negligible, may be only a variance between tests |
|
yeah, i'd run them locally in anycase, you can get wildly different results in GL runners. FWIW this was my original attempt to use phpstan from > 6 years ago ... it was much simpler then 😄 : (at the time it was replacing a reg-based solution). |
lib/WorseReflection/Bridge/Phpactor/DocblockParser/PHPStanParsedDocblock.php
Outdated
Show resolved
Hide resolved
|
I did some benchmarking on my (low-end) laptop (intel core i7-1065G7, integrated graphics, 8GB ram, PHP 8.3). Below is the median for both branches from 3 runs:
EDIT: updated values for gh-2706 branch after fixing the measurement |
|
hm ok, well it's not the end of the world necessarily if we can make up the performance elsewhere (phpactor can be so much faster than it is), we can also make this a strategy and merge with it being disabled by default for now, I think there is a test for the factory/type converter which could maybe be abstracted to run on both implementations? In anycase it would be very useful to have this as a second strategy. |
3d13aca to
98c5eb0
Compare
|
Please, review the benchmark table again, as I have mistakenly benchmarked the branches without actual changes 🤦 I've edited the table to avoid confusion, and now it includes the values where tested code is actually using PHPStanDocblockParserFactory (benchmarks before still used original parser, what makes it even more bizzare, that results were slower). Anyway, this again shows more potential. Please, note that at current stage I've hardcoded PHPStan parser, yet closer to the actual merge, I will introduce strategy you mentioned (after solving failing test cases) |
98c5eb0 to
f75d54e
Compare
|
Yes, it's tricky :) in any case adding the filtering would have helped, but looks encouraging! |
| $docblock, | ||
| $matches | ||
| )) { | ||
| return new PlainDocblock($docblock); |
There was a problem hiding this comment.
we should extract this "filter" to e.g. Phpactor\WorseReflection\Core\DocBloc so that it's used by both implementations (assuming we will run them side-by-side via. a strategy initially)... or add a decorator (tbh though I think I prefer a simple static method).
There was a problem hiding this comment.
Good 👍 . I was thinking about the same, but didn't want to introduce that prematurely
There was a problem hiding this comment.
I've settled with decorator because I did not fully understand how could I handle this with just static method, but if you have any suggestion, please do.
|
I'd like to ask for help about specific cases and guidance in solving the issues.
|
yes
fine to ignore invalid ones i guess
it's fine if PHPStan reduces the type
diff --git a/lib/WorseReflection/Tests/Inference/generics/class_implements_multiple1.test b/lib/WorseReflection/Tests/Inference/generics/class_implements_multiple1.test
index e07e6e36..c8238f5d 100644
--- a/lib/WorseReflection/Tests/Inference/generics/class_implements_multiple1.test
+++ b/lib/WorseReflection/Tests/Inference/generics/class_implements_multiple1.test
@@ -23,7 +23,8 @@ interface Second {
}
/**
- * @implements Second<Baz>, First<Boo>
+ * @implements Second<Baz>
+ * @implements First<Boo>
*/
class Foo implements Second, First
{for the second (narrowing) with |
|
FYI, I'll get back to this PR next week |
f76cd90 to
00bbe1e
Compare
Signed-off-by: Bart Jaskulski <[email protected]>
Signed-off-by: Bart Jaskulski <[email protected]>
For now, withdraw from attempts to separate with newline changes between different tags. The most naive approach to check for previous and current tag, and render empty line if change is detected fails short, when there are tags, we don't want to render – those will result in excessive empty lines. Signed-off-by: Bart Jaskulski <[email protected]>
Signed-off-by: Bart Jaskulski <[email protected]>
Signed-off-by: Bart Jaskulski <[email protected]>
Signed-off-by: Bart Jaskulski <[email protected]>
Signed-off-by: Bart Jaskulski <[email protected]>
Signed-off-by: Bart Jaskulski <[email protected]>
1caf667 to
951bf7b
Compare
Signed-off-by: Bart Jaskulski <[email protected]>
Signed-off-by: Bart Jaskulski <[email protected]>
Signed-off-by: Bart Jaskulski <[email protected]>
Signed-off-by: Bart Jaskulski <[email protected]>
Signed-off-by: Bart Jaskulski <[email protected]>
Signed-off-by: Bart Jaskulski <[email protected]>
| return; | ||
| } | ||
|
|
||
| if ($docblockReturn->isDefined()) { |
There was a problem hiding this comment.
There are MissingReturnTypeProvider and DocblockMissingReturnTypeProvider. It seems, even on master branch declaring return type only in docblock leaves with false-positive diagnostic about missing return type. I've added this check, but it may be better to merge both classes -- despite the change I think it is out of the scope of this PR, but without this new test case is failing. Anyway, up to you if this should be split into own PR.
Signed-off-by: Bart Jaskulski <[email protected]>
|
As Besides, when using this branch on daily basis I've noticed two issues, which are now covered with test case and appropriate fix (missing return and unused import for array types). |
Signed-off-by: Bart Jaskulski <[email protected]>
|
Nice, so what is left from your perspective? I guess we still need to run the tests with both implementations (something I could look at). I had a quick look into this and noticed that PHPStan has an option to record the |
Yeah, running tests for both implementation would be crucial -- any help appreciated. Besides coming up with some sort of consensus about discrepancies in tests, like rendered doc formatting or changes which are not reflected in native parser. I've annotated all such cases in this PR comments; it would also be obvious when running the test suite against the original parser. ATM there are 4 test cases which fail on original parser.
API for creating phpdoc tags in phpstan is indeed more object-oriented (:boom:) and burdensome to actually craft anything, but it would be possible to make such replacement, following the example from their readme with |
Signed-off-by: Bart Jaskulski <[email protected]>
Signed-off-by: Bart Jaskulski <[email protected]>
Signed-off-by: Bart Jaskulski <[email protected]>
Signed-off-by: Bart Jaskulski <[email protected]>
This is a draft for incorporating PHPStan docblock parser with the premise of better stability and performance.