Skip to content
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

Added type assertions for multiple methods not covered by existing @psalm-assert functionality #160

Merged
merged 59 commits into from
Feb 19, 2020

Conversation

@Ocramius
Copy link
Contributor Author

Ocramius commented Dec 9, 2019

Note: I do not know how to test @psalm-assert empty or !empty in combination with @param string or @param array. If anyone has suggestions on how to write an expression that infers as constant when one of the above applies, please let me know.

src/Assert.php Outdated Show resolved Hide resolved
src/Assert.php Outdated Show resolved Hide resolved
src/Assert.php Outdated Show resolved Hide resolved
@Ocramius
Copy link
Contributor Author

Ocramius commented Dec 10, 2019

Note to self:

  1. add "conflict": {"vimeo/psalm": "<3.7.3"} before calling this patch "done" (needs upstream release)
  2. add type-checker tests for the above discussions

Ocramius added a commit to Ocramius/assert-1 that referenced this pull request Dec 10, 2019
…e `string|object`

An object implementing `#__toString()` suffices for `filter_var()` to
work, so we can't assume that after the assertion, the value being
asserted upon is a `string`. Instead, `string|object` is our closest
bet.

Ref: webmozarts#160 (comment)

Note: will fail until vimeo/psalm#2452 has some
sort of resolution.
@Ocramius
Copy link
Contributor Author

Ocramius commented Dec 10, 2019

@Ocramius Ocramius force-pushed the feature/verify-map-invariants branch from 32d2ad6 to 4e28a31 Compare December 10, 2019 13:11
@Ocramius
Copy link
Contributor Author

Ocramius commented Dec 11, 2019

ERROR: TypeDoesNotContainType - tests/static-analysis/assert-email.php:9:13 - Found a contradiction when evaluating $value and trying to reconcile type 'null|string' to object
    Assert::email($value);


ERROR: TypeDoesNotContainType - tests/static-analysis/assert-email.php:16:13 - Found a contradiction when evaluating $value and trying to reconcile type 'null|object' to string
    Assert::email($value);


ERROR: RedundantCondition - tests/static-analysis/assert-email.php:16:13 - Found a redundant condition when evaluating $value and trying to reconcile type 'object' to !empty
    Assert::email($value);


ERROR: TypeDoesNotContainType - tests/static-analysis/assert-ip.php:9:13 - Found a contradiction when evaluating $value and trying to reconcile type 'null|string' to object
    Assert::ip($value);


ERROR: TypeDoesNotContainType - tests/static-analysis/assert-ip.php:16:13 - Found a contradiction when evaluating $value and trying to reconcile type 'null|object' to string
    Assert::ip($value);


ERROR: RedundantCondition - tests/static-analysis/assert-ip.php:16:13 - Found a redundant condition when evaluating $value and trying to reconcile type 'object' to !empty
    Assert::ip($value);


ERROR: TypeDoesNotContainType - tests/static-analysis/assert-ipv4.php:9:13 - Found a contradiction when evaluating $value and trying to reconcile type 'null|string' to object
    Assert::ipv4($value);


ERROR: TypeDoesNotContainType - tests/static-analysis/assert-ipv4.php:16:13 - Found a contradiction when evaluating $value and trying to reconcile type 'null|object' to string
    Assert::ipv4($value);


ERROR: RedundantCondition - tests/static-analysis/assert-ipv4.php:16:13 - Found a redundant condition when evaluating $value and trying to reconcile type 'object' to !empty
    Assert::ipv4($value);


ERROR: TypeDoesNotContainType - tests/static-analysis/assert-ipv6.php:9:13 - Found a contradiction when evaluating $value and trying to reconcile type 'null|string' to object
    Assert::ipv6($value);


ERROR: TypeDoesNotContainType - tests/static-analysis/assert-ipv6.php:16:13 - Found a contradiction when evaluating $value and trying to reconcile type 'null|object' to string
    Assert::ipv6($value);


ERROR: RedundantCondition - tests/static-analysis/assert-ipv6.php:16:13 - Found a redundant condition when evaluating $value and trying to reconcile type 'object' to !empty
    Assert::ipv6($value);


------------------------------
12 errors found
------------------------------

The entire object|string scenario will always raise these if the type of $value is already known to be a string (see vimeo/psalm#2452 (comment)).

I don't have an immediate solution: will likely resort to dropping all @psalm-assert from ip* and email functions.

@Ocramius Ocramius force-pushed the feature/verify-map-invariants branch 2 times, most recently from 59fcf57 to 500f5d2 Compare December 11, 2019 10:56
@Ocramius
Copy link
Contributor Author

@BackEndTea I've added @psalm-pure where applicable. Don't think there are other issues with the patch, besides waiting for upstream psalm release.

@BackEndTea
Copy link
Collaborator

BackEndTea commented Dec 11, 2019

We still have the issue of type assertions being considered redundant by psalm: https://psalm.dev/r/33a97818c1

Another thing i'm not quite sure of is the effect of the psalm-pure annotations: https://psalm.dev/r/23518408b0 Because now we suddenly get a UnusedFunctionCall error in this example.

@Ocramius
Copy link
Contributor Author

Ocramius commented Dec 11, 2019

We still have the issue of type assertions being considered redundant by psalm: https://psalm.dev/r/33a97818c1

This one is indeed incorrect/to be removed, since having the types being constrained seemed more useful in the typical PHPUnit scenario:

/** @var T $a */
$a = someExpr();
$b = someOtherExpr();

Assert::same($a, $b);

$b->canNowCallSomethingOnThis();

I'll drop it though, since the same() will most certainly not be used that way (relying on $a is sufficient).

Would you consider sameType() as an alternate implementation that introduces the constraint?

Another thing i'm not quite sure of is the effect of the psalm-pure annotations: https://psalm.dev/r/23518408b0 Because now we suddenly get a UnusedFunctionCall error in this example.

Considering same($a, $b): void as "unused function call" seems weird upstream, since there is no function result by design (void signature). Will report there.

@BackEndTea
Copy link
Collaborator

I guess it is somewhat unusual for a pure method to have a void return type, because that usually would imply side effects, since you aren't getting a result from that.

@Ocramius
Copy link
Contributor Author

Yeah, I'm raising an upstream issue: exceptions would be pure if it wasn't for the fact that they contain a stack trace.

In practice, any method that @throws in PHP is an Either ExceptionType ReturnType which affects all callers.

@Ocramius
Copy link
Contributor Author

@BackEndTea I reported vimeo/psalm#2456 about :void unused return value

@BackEndTea
Copy link
Collaborator

I'd be okay with a sameType, but, that should probably be another PR.

The same example was more an indicator of a general problem, namely the 'side effect' assertions. Where the @psalm-assert assertion is a side effect of the actual assertion. An example would be the file assertion atm. it has an @psalm-assert string. Which means we get a redundant condition error when a string is passed in there.

To solve that we need to either remove them here, or we need a way to give psalm that information, without triggering the redundant condition error

@Ocramius
Copy link
Contributor Author

@BackEndTea ok, will likely need to go through the assertions one-by-one and check them for redundancy.

An example would be the file assertion atm. it has an @psalm-assert string. Which means we get a redundant condition error when a string is passed in there.

We should probably remove the assert::string() bit from these then, and enforce the parameter to already be a string instead (BC break). Besides that, the issue is clear, and needs more work.

@Ocramius
Copy link
Contributor Author

Just an update here: vimeo/psalm@4b715cd fixes @psalm-assert + @psalm-pure + void being considered a RedundantCondition

Ocramius added a commit to Ocramius/assert-1 that referenced this pull request Dec 11, 2019
@BackEndTea
Copy link
Collaborator

I do have a concern about using @psalm-pure.

We don't analyse the Assert class with psalm yet, otherwise it would error on the reportInvalidArgument not being marked as pure.

But marking it as pure prevents our users from overriding that method and doing impure things, e.g. any kind of logging they may do from that class. But not marking it and leaving it as is, allows our users to do that, but it also means that we are lying about the methods being pure.

In all honesty i'm not sure what the best solution would be.

@Ocramius
Copy link
Contributor Author

But marking it as pure prevents our users from overriding that method and doing impure things, e.g. any kind of logging they may do from that class.

Few things to consider:

  1. If users have a subtype of their own, and they don't inspect it, they'll be fine for now, even if effects are introduced (because they didn't care about purity).
  2. users that care about the functional purity of the API and relying on Assert::someMethod() (static reference) will have an execution path reaching Assert::reportInvalidArgument() (rather than Subclass::reportInvalidArgument()) so we're fine here.

Yes, this means that the library won't pass any sort of internal SA unless either:

  1. static:: calls are replaced with self::calls
  2. the Assert class is made final (not sure why one would extend it)

Consumers will be able to ignore this though, unless they subclass (which probably already has broken SA).

The static::reportInvalidArgument() (just noticed it as well, sorry) seems like a really bad hack to me: it jeopardizes stability of this class to allow DI via inheritance for very rare scenarios (logging is usually best done in the application layer).

@Ocramius
Copy link
Contributor Author

Ocramius commented Feb 3, 2020

@BackEndTea would you be OK with deprecating reportInvalidArgument()?

@BackEndTea
Copy link
Collaborator

I have to think about that. I'm not sure how many people make use of it.

@Ocramius
Copy link
Contributor Author

Note: I'll also add this library to upstream psalm test suite, once through 👍

@tux-rampage
Copy link

I've added extensive tests (sorry @tux-rampage if we're duplicating work :S ) for all of this, and restricted @psalm-pure to API that is really pure.

No worries. I compared both branches and you already addressed a lot more here. When this one gets merged #167 can be considered obsolete. I don't mind how pure/immutable supports makes it into this lib as long as it does ;-)

Copy link

@tux-rampage tux-rampage left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You didn't mark Assert::ip*() as pure. Is there a reason why not?

tests/static-analysis/assert-ip.php Show resolved Hide resolved
tests/static-analysis/assert-ipv4.php Show resolved Hide resolved
tests/static-analysis/assert-ipv6.php Show resolved Hide resolved
src/Assert.php Outdated Show resolved Hide resolved
src/Assert.php Outdated Show resolved Hide resolved
src/Assert.php Outdated Show resolved Hide resolved
While these functions support integers, we do not necessarily intend
downstream consumers to rely on the widened type.

Ref: webmozarts#160 (comment)
@BackEndTea
Copy link
Collaborator

I got a few comments/questions. When those are resolved this should be good to merge.
I'm not sure if i can release this as a patch version as it changes the version constraints on psalm.

@Ocramius
Copy link
Contributor Author

@BackEndTea all done: let's see if CI passes, then we should be good to go 👍

src/Assert.php Outdated Show resolved Hide resolved
src/Assert.php Outdated Show resolved Hide resolved
src/Assert.php Outdated Show resolved Hide resolved
@BackEndTea
Copy link
Collaborator

Found a few more things, didn't think of these edge cases being impure, but the filter_var based assertions are impure, so these should be as well

…o `__toString`

In fact, the following explains it:

```php
'foo' == new class { public function __toString() : string {echo 'hi'; return 'bar'; }};
```

This is a fucking mess of a language, but it's what we have to deal with :-(

Ref: webmozarts#160 (comment)
Ref: webmozarts#160 (comment)
Ref: webmozarts#160 (comment)
@Ocramius
Copy link
Contributor Author

@BackEndTea adjusted according to comments above about purity: good looking out!

@BackEndTea
Copy link
Collaborator

Thanks for the work on this

@BackEndTea BackEndTea merged commit efafa74 into webmozarts:master Feb 19, 2020
@Ocramius
Copy link
Contributor Author

W00P W00P! Thanks for the very careful review, @BackEndTea!

@Ocramius Ocramius deleted the feature/verify-map-invariants branch February 19, 2020 21:58
@tux-rampage
Copy link

🎉 Thanks for all the psalm improvements @Ocramius

@Ocramius
Copy link
Contributor Author

Ocramius commented Mar 5, 2020

@BackEndTea could you please push a 1.8.0 tag with this?

@Ocramius
Copy link
Contributor Author

Ocramius commented Mar 5, 2020

Or better: revert the merge on `master, then merge without squash? :S

@Ocramius Ocramius restored the feature/verify-map-invariants branch March 5, 2020 14:59
TomasVotruba pushed a commit to rectorphp/rector that referenced this pull request Sep 14, 2020
…4220)

* Github Actions: add a job which tests the lowest supported versions

* Update tests.yaml

* run lowest only on php 7.2

to reduce the number of generated jobs

* faise min webmozart/assert version

we need at least webmozarts/assert#160

* use quotes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants