Skip to content

Conversation

rvanvelzen
Copy link
Contributor

Implementation for phpstan/phpstan#7110. I already had a big chunk of this quite soon, so I finished most of it.

Just putting this out there, hopefully you didn't already start on this 🥲

@herndlm
Copy link
Contributor

herndlm commented May 16, 2022

Oh, nice! Looks like we could get rid then of almost all of the custom expression building in the phpstan-assert* extensions :)

@ondrejmirtes
Copy link
Member

Awesome, I haven't, I'm too deep in InitializerExprTypeResolver now (and also in my Verona phpDay talk) so great, I appreciate this :) I'm gonna go through the code soon, I just gonna list of some of my extra ideas for rules around this (you might have already done some of them, but I need to get them off my chest first :):

  • Similarly to how subject/target is checked in conditional types, @phpstan-assert should also be checked for sanity.
    • So for example - @param int + @phpstan-assert int - doesn't narrow down the type
    • @param int + @phpstan-assert int|string - doesn't narrow down the type
    • @param string + @phpstan-assert int - can never happen
    • Also needs to work with generics @template T of int + @param T + @phpstan-assert positive-int is fine
    • But @template T of int + @param T + @phpstan-assert int doesn't narrow down the type
  • The same rule should check if the referenced parameter in @phpstan-assert exists
  • Check that the feature works with generics - e.g. @phpstan-assert T after we know what T is inferred as.
  • Check that the @phpstan-assert can be put in stub files and that it works too.
  • Check that ImpossibleCheckTypeFunctionCallRule automatically picks up that function is narrowing down something that's already narrowed. (Nothing special should be needed, everything should already work based on the code in TypeSpecifier and ImpossibleCheckTypeHelper).

Before I dive into the review, can you please check this list? :) Thanks.

@ondrejmirtes
Copy link
Member

Just to be sure - if you're gonna work on these, please add them as new commits, it makes the review easier :)

To see how stubs-related functionality is tested, look at https://github.com/phpstan/phpstan-src/blob/1.7.x/tests/PHPStan/Analyser/ClassConstantStubFileTest.php.

@rvanvelzen
Copy link
Contributor Author

None of that list is tested right now, just the very basic type narrowing. I'll look into that soon, thanks :)

@ondrejmirtes
Copy link
Member

I'm glad that I hit the nail on the head :)

Also I'll probably question some of your reflection choices (where to put the new information), but I need to think about it and I'll shuffle things around myself later :)

$assert->isNegated() ? TypeSpecifierContext::createFalse() : TypeSpecifierContext::createTrue(),
false,
$scope,
$containsClassTemplate ? $call : null,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is really tricky, but I couldn't think of something else.

@ondrejmirtes ondrejmirtes mentioned this pull request May 19, 2022
@ondrejmirtes
Copy link
Member

I really like how it's coming together :) After this one is merged, I also look forward to support for properties and methods besides parameter names :)

@rvanvelzen
Copy link
Contributor Author

There's some work left to be done here, but it should be ready for review.

I'm not sure if the always true/false errors should pop up from these asserts - a function could very well narrow a type down without that being the only thing the function does. This could actually be handled by specifying types from conditional return types. I have a PR almost ready for that.

All that aside: I'll be on holiday for the next 2 weeks so I won't be able to spend much time on this. If you point out any issues I'll gladly work on them but I just don't know how quickly that will be. Thanks for understanding :)

@ondrejmirtes
Copy link
Member

I'm not sure if the always true/false errors should pop up from these asserts - a function could very well narrow a type down without that being the only thing the function does.

That's my worry as well but I'd like to see in practice if it's a real concern or not. In theory someone could write @phpstan-assert above functions that do other things as side effects as well, but maybe it doesn't happen in practice and it's still useful to report things like "this int is already an int".

It'd be nice to have these "impossible or always-true type checks" reported only in bleeding edge, but I'm not sure we can easily isolate these cases from the already existing ones in rules like ImpossibleCheckTypeFunctionCallRule etc.

This could actually be handled by specifying types from conditional return types. I have a PR almost ready for that.

I have no idea what you mean by that, so I'm intrigued to see it :)

All that aside: I'll be on holiday for the next 2 weeks so I won't be able to spend much time on this.

I hope you won't mind if we're going to postpone this feature for PHPStan 1.8. There's already a lot of stuff ready in PHPStan 1.7 (and it doesn't mean we can't release 1.8 immediately after the PHPDoc-based type narrowing is merged too) and I'd like to gather real-world feedback on it soon. Thank you for understanding.

@rvanvelzen
Copy link
Contributor Author

Sure, it's absolutely fine to let this sit until after 1.7 is released :)

@BackEndTea
Copy link
Contributor

BackEndTea commented May 24, 2022

In theory someone could write @phpstan-assert above functions that do other things as side effects as well, but maybe it doesn't happen in practice and it's still useful to report things like "this int is already an int".

As far as i know this is the current behavior in psalm, which is why not all methods in webmozarts/assert have an @psalm-assert annotation.
So it would probably be best to mimic this behavior in PHPStan. And maybe it helps promote people to write smaller functions that only do 1 thing, either a side effect or a validation.

@ondrejmirtes
Copy link
Member

BTW Do you have plans to support this syntax? Thanks! @psalm-assert =ExpectedType $actual

@BackEndTea
Copy link
Contributor

What would that do? Does that mean $actual is optional/nullable?

@rvanvelzen
Copy link
Contributor Author

Not necessarily for the initial implementation, but I do plan on adding that as well.

@ondrejmirtes
Copy link
Member

What would that do?

I have no idea, doesn't seem documented: https://psalm.dev/docs/annotating_code/adding_assertions/

/cc @orklah What does @psalm-assert =ExpectedType $actual do? And is there any other syntax to be aware of?

@rvanvelzen
Copy link
Contributor Author

Those are equality assertions, though the documentation for it is hard to find: https://psalm.dev/annotating_code/assertion_syntax/

@rvanvelzen
Copy link
Contributor Author

I believe this has become a bit too large and complex. I'd like to break it up into a few pieces to make it easier to reason about.

@ondrejmirtes
Copy link
Member

Feel free to do what you think is best 😊 My idea for reflection is that I'm not sure if I want it on ParametersAcceptor vs. MethodReflection/FunctionReflection. I think it fits the latter better.

@mvorisek
Copy link
Contributor

mvorisek commented Jun 3, 2022

this PR seems to implement phpstan/phpstan#7385, please include the demo https://phpstan.org/r/53ba9b04-685e-404c-ba77-a9616ca75ace code in the tests

@ondrejmirtes
Copy link
Member

BTW @rvanvelzen This one would be a great thing to finish :) My idea is that instead of ParametersAcceptorWithAsserts / FunctionVariant etc., the getAsserts() could be on ExtendedMethodReflection + FunctionReflection.

@rvanvelzen
Copy link
Contributor Author

@ondrejmirtes it's on my list to work on, but I need/want to finish phpstan/phpdoc-parser#139 first but I'm not really happy with the current version :/

@orklah
Copy link
Contributor

orklah commented Aug 9, 2022

/cc @orklah What does @psalm-assert =ExpectedType $actual do? And is there any other syntax to be aware of?

Wow, just saw this in my notifications, sorry I seem to have missed it completely before.

The = in assertions is used to express the IsIdentical assertion. You also have the ~ to express the IsLooselyEqual assertion
And both of those can be negated (i.e. !~ is IsNotLooselyEqual). It allows to express every !=, !==, == or === we can encounter. I'm still unsure in which case we use our IsType/IsNotType assertion compared to those.

@ondrejmirtes
Copy link
Member

I'm quite happy with the CI results, I'll make Rector freak out less about it (it's one of the problems I already suspected and mentioned) and then we can improve it on top of 1.9.x branch :)

@ondrejmirtes
Copy link
Member

I want to merge this because we're already stepping on each other's toes (I have some local commits but you force-pushed meanwhile :))

@rvanvelzen rvanvelzen marked this pull request as ready for review October 3, 2022 12:14
@ondrejmirtes
Copy link
Member

We can do so much more stuff once this is merged!

I can't wait :)

@ondrejmirtes ondrejmirtes merged commit 737d6bf into phpstan:1.9.x Oct 3, 2022
@ondrejmirtes
Copy link
Member

Thank you very much!

@ondrejmirtes
Copy link
Member

OMG, I love this so much! ace76ce

@mvorisek
Copy link
Contributor

mvorisek commented Oct 3, 2022

Hi @rvanvelzen, can this PR be used to solve phpstan/phpstan#7385?

@rvanvelzen
Copy link
Contributor Author

I don't think so, because $this on its own isn't assertable right now.

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.

7 participants