-
Notifications
You must be signed in to change notification settings - Fork 56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc @JanTvrdik
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that one actually a problem?
assert($intersectionType instanceof Type1 && $intersectionType instacenof Type2);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem here is that contemporary tooling does not support this except for phpstan.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@muglug awesome! Now we just need to convince JetBrains 😂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be interesting, but unlikely for this PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's harder to parse for the IDE to infer the type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 -l There is currently 174 affected non-compound occurrences in ORM. git grep -E '/\*\*\s+@var\s+\S+\s+\$' lib tests | wc -l There 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
@@ -28,7 +28,7 @@ | |||
"require": { | |||
"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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
@@ -311,6 +311,8 @@ | |||
<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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does not. However it is reported (and fixed) by
coding-standard/lib/Doctrine/ruleset.xml
Line 200 in e008cac
<rule ref="SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
InvalidDocCommentDeclaration
fixes /*
to /**
in first round and in second round RequireExplictionAssertion
changes doccomment to assertion :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ocramius Not sure I undrestand you well but the InvalidInlineDocCommentDeclaration
is already enabled ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't mind the commit pinning on composer.json
, we just have to remember that we shouldn't release anything before removing it.
Thanks @kukulich!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was review re-requested from me when nothing changed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.