Replace inline docblock declarations with assertions#47
Conversation
| /** @var Type1|Type2 $unionType */ | ||
| $unionType = expression(); | ||
|
|
||
| /** @var Type1&Type2 $intersectionType */ |
There was a problem hiding this comment.
Unsure if we should deal with this now. I can see it growing in usage since it was first introduced a couple months ago, so it may be worth exploring after poking the @phpstorm folks
There was a problem hiding this comment.
Issue for intersection types is pending and being ignored: https://youtrack.jetbrains.com/issue/WI-39419 for three months already. :(
Unfortunately until then, it's a no-go, it completely breaks assumptions in PhpStorm.
There was a problem hiding this comment.
Is that one actually a problem?
assert($intersectionType instanceof Type1 && $intersectionType instacenof Type2);
There was a problem hiding this comment.
The problem here is that contemporary tooling does not support this except for phpstan.
There was a problem hiding this comment.
@morozov the CS tool simply needs to learn to parse them in order to replace them. These annotations are still valid in docblocks.
There was a problem hiding this comment.
@muglug awesome! Now we just need to convince JetBrains 😂
There was a problem hiding this comment.
These annotations are still valid in docblocks.
Do you also want to replicate complex argument types with assertions? Otherwise, unlike local variables, they remain unenforced. For instance:
/**
* @param string|int $a
*/
function ($a) {
assert(is_string($a) || is_int($a));
}There was a problem hiding this comment.
Might be interesting, but unlikely for this PR
There was a problem hiding this comment.
I'm not too sure about how I feel about assertions for public API parameters. Personally of course I always avoid complex types in public APIs and only use them in private APIs when I want to keep things simple.
The thing with assertions is, that they are not ran by default (heck, PHP throws away the assertion op-codes when assertions are disabled). Thus most users wouldn't get those checks. IMHO a public API should do parameter checks the proper way (and throw exceptions itself) for such stuff.
Assertions come in handy when you have methods which are supposed to return something specific, but the API cannot specify the return type (like PSR containers). Thus you assert on the return type.
| /** @var int|float|bool|string|null|array $multipleScalarTypes */ | ||
| $multipleScalarTypes = expression(); | ||
|
|
||
| /** @var Potato $variableThatIsNowhereToBeFound */ |
There was a problem hiding this comment.
Is it for the cases like extract() or eval()? If the standard doesn't allow them (and why would it?), then this case doesn't need to be handled.
There was a problem hiding this comment.
Replacing an inline definition with an assertion still challenges the assumption, which is good
| assert($nullableType instanceof Type || $nullableType === null); | ||
|
|
||
| $multipleScalarTypes = expression(); | ||
| assert(is_int($multipleScalarTypes) || is_float($multipleScalarTypes) || is_bool($multipleScalarTypes) || is_string($multipleScalarTypes) || $multipleScalarTypes === null || is_array($multipleScalarTypes)); |
There was a problem hiding this comment.
What about assert(in_array(gettype($multipleScalarTypes), ['int', 'float', 'bool', 'string', 'null', 'array'], true)), isn't a more readable/cheaper way?
There was a problem hiding this comment.
I think it's harder to parse for the IDE to infer the type.
There was a problem hiding this comment.
Indeed: let's keep it to very well known constructs
|
@Ocramius I like the idea but I don't what's better, holding off the part that are not supported by phpstorm or going all in and see how it goes. |
Yeah, I think this will be the initial approach |
$ git grep -E '/\*\*\s+@var\s+[^\|]+\s+\$' lib tests | wc -lThere is currently 174 affected non-compound occurrences in ORM. git grep -E '/\*\*\s+@var\s+\S+\s+\$' lib tests | wc -lThere is currently 249 afftected compound or non-compound occurrences in ORM. This may (or may not) have serious impact.
This assumption is not valid, since you can't turn it off per-library or so. There are projects out there that build business asserts using |
|
Having business logic depend on assertions is a seriously bad practice, and they should feel bad. |
|
@Majkl578 we'd obviously have to turn assertions on in the test suite. |
|
@DASPRiD Not really. Please consult your opinion with beberlei/assert and webmozart/assert. |
|
Those two libs are not at all matching |
|
Strictly related: https://www.reddit.com/r/PHP/comments/7yiaib/comment/duhbahh |
|
I can easily use assert() for the same purpose (and avoid method call overheads) with simple pre-condition: asserts are always enabled. |
|
Indeed, but (assuming th docblocks are correct) whether the assertions are enabled or disabled makes no functional difference with the proposed patch approach |
|
Not functional, but could have performance impact, depending on the amount & # of uses of course. |
|
In other words, simply adding 250 |
Putting `/** @var foo $foo */` after `$foo = bar();` is now a fixable violation as per slevomat/coding-standard@63a8186 Thanks @kukulich! Meanwhile, also pinning down `slevomat/coding-standard` to the specific commit that includes all required changes for this test to run. Ref: f797ff6#commitcomment-33331234
40cae4d to
c8e32d3
Compare
| "php": "^7.1", | ||
| "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0", | ||
| "slevomat/coding-standard": "^5.0", | ||
| "slevomat/coding-standard": "dev-master#63a8186b129ee96d1277e68c80cf87c8cdb356d1", |
There was a problem hiding this comment.
IIRC pinning a commit could lead to weird side effects where we can end up with vendor requirements from current dev-master while code in pinned commit could require something else.
There was a problem hiding this comment.
Yes: this is not for a stable release yet. I'd need a SemVer selector for the release to be tagged.
|
@doctrine/coding-standard-approvers please shoot a 👍 or 👎 my way. As mentioned in #47 (comment), I'll pin to a stable release after this (chore/release work). |
|
👍 on the rule itself. Not sure if we want to merge with commit pinning in composer.json, I'll defer to others for that decision. |
| <rule ref="SlevomatCodingStandard.PHP.UselessParentheses"/> | ||
| <!-- Forbid useless semicolon `;` --> | ||
| <rule ref="SlevomatCodingStandard.PHP.UselessSemicolon"/> | ||
| <!-- Require /* @var type $foo */ and similar simple inline annotations to be replaced by assert() --> |
There was a problem hiding this comment.
Does this sniff support /* @var ArrayIterator $iterator */ with one * too like in this comment? IDEs like PHPStorm support those too, even though it doesn't autocomplete on "/*". In case it supports the single-*, some tests for those would be nice.
There was a problem hiding this comment.
It does not. However it is reported (and fixed) by
coding-standard/lib/Doctrine/ruleset.xml
Line 200 in e008cac
There was a problem hiding this comment.
Is it possible that both can be used? The first one fixing a /* and the other one handles it for the assert()?
There was a problem hiding this comment.
A CS rule enforcing /** style would be preferable: please note that current static analysis tools fail to parse /* AFAIK
There was a problem hiding this comment.
InvalidDocCommentDeclaration fixes /* to /** in first round and in second round RequireExplictionAssertion changes doccomment to assertion :)
There was a problem hiding this comment.
A CS rule enforcing
/**style would be preferable: please note that current static analysis tools fail to parse/*AFAIK
Last time I used NetBeans (granted, a while ago), it only supported vardoc syntax (/*) but not /**.
There was a problem hiding this comment.
That changed a long time ago: at the moment, the ecosystem is pushing for /**, and enabling that sniff could put a tombstone on this
There was a problem hiding this comment.
@Ocramius Not sure I undrestand you well but the InvalidInlineDocCommentDeclaration is already enabled ;)
ostrolucky
left a comment
There was a problem hiding this comment.
Why was review re-requested from me when nothing changed?
SenseException
left a comment
There was a problem hiding this comment.
A usage of SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration can be discussed another time.
Dismissing current feedback due to approvals quota reached
@ostrolucky my bad (or maybe GH UI issues), sorry. I was asking re-reviews from people that requested changes but didn't look at it again, interface moved and I miss clicked 😓 |
|
No worries I just wanted to know. Explanation makes sense :) |
This RFC proposes replacing the typical
/** @var Something $something */withassert($something instanceof Something)wherever possible.This has a few nice side-effects that @DASPRiD exposed:
assert()can report when these assumptions fail (once the CS rule is applied to a codebase)assert()can be turned off, making this an informational additionI didn't dig into PHPStan's new type declarations, which support
foo[],(foo|bar)[]and union types (foo&bar) too much, so this is just a first sketch of the idea I stole from @DASPRiD.