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

Remember narrowed types from the constructor when analysing other methods #3930

Merged
merged 23 commits into from
Apr 14, 2025

Conversation

staabm
Copy link
Contributor

@staabm staabm commented Apr 11, 2025

return 0;
}

return [!$a->isStatic(), $a->name->toLowerString() !== '__construct'] <=> [!$b->isStatic(), $b->name->toLowerString() !== '__construct'];
Copy link
Member

Choose a reason for hiding this comment

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

We have a better way to ask for constructor, that works for same-named constructors as class on PHP 7.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ExtendedMethodReflection has no isConstructor method.

I took inspiration from

$isConstructor = $isFromTrait || $stmt->name->toLowerString() === '__construct';
which seems to be also wrong then?

Copy link
Contributor Author

@staabm staabm Apr 14, 2025

Choose a reason for hiding this comment

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

I looked into adding ExtendedMethodReflection->isConstructor(): TrinaryLogic, but this is incompatible with
PhpMethodFromParserNodeReflection->isConstructor(): bool

any preferences how you want this to be resolved?

Copy link
Member

Choose a reason for hiding this comment

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

I think we can leave as it is. This is mostly useful for readonly properties anyway, which means __construct() constructors...

@staabm
Copy link
Contributor Author

staabm commented Apr 11, 2025

re-ordering the order in which methods get analyzed affected the ordering of errors in tests... I am not yet sure where/how to compensate that

@ondrejmirtes
Copy link
Member

Here's this line:

$finalizer->finalize($analyserResult, false, true)->getAnalyserResult()->getUnorderedErrors(),

You could call getErrors instead of getUnorderedErrors. Not sure what the side effects might be, hopefully it's not brutal.

@staabm
Copy link
Contributor Author

staabm commented Apr 11, 2025

I have put it behind bleeding edge and added corresponding opt-in methods for the test-base-classes so we can move forward without the need to adjust all tests

@staabm staabm marked this pull request as ready for review April 11, 2025 14:50
@phpstan-bot
Copy link
Collaborator

This pull request has been marked as ready for review.

@staabm
Copy link
Contributor Author

staabm commented Apr 12, 2025

sorry for push bombing :-) - I am finished now

@ondrejmirtes
Copy link
Member

I just realized there's one more thing we could use this for: with natively typed properties (non-nullable), expressions like isset or ?? do not report errors. Because these properties might be uninitialized and isset or ?? should report an error.

We should preserve PropertyInitializationExpr and then check for that in IssetCheck.

This is for non-readonly properties too.

@staabm
Copy link
Contributor Author

staabm commented Apr 13, 2025

We should preserve PropertyInitializationExpr and then check for that in IssetCheck.

great idea. done

Copy link
Member

@ondrejmirtes ondrejmirtes left a comment

Choose a reason for hiding this comment

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

This is getting really close to merging 👍

&& $expr->name instanceof Node\Identifier
&& $expr->var instanceof Expr\Variable
&& $expr->var->name === 'this'
&& !TypeCombinator::containsNull($propertyReflection->getNativeType())
Copy link
Member

Choose a reason for hiding this comment

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

  1. Doing Type::isNull() would make more sense. This example doesn't cover mixed (which can include null).
  2. But I think we should pay more attention to $typeMessageCallback passed to IssetCheck. For both the type check and the error message. Something a little bit different happens in EmptyRule which should be covered by a set of tests. (Because empty($x) is !isset($x) || $x == false).

$this->propertyDescriptor->describeProperty($propertyReflection, $scope, $expr),
$operatorDescription,
),
)->identifier(sprintf('%s.neverNullOrUninitialized', $identifier))->build();
Copy link
Member

Choose a reason for hiding this comment

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

This identifier should include property

@staabm staabm force-pushed the constr-scope branch 2 times, most recently from 75ca7d5 to e9343d7 Compare April 13, 2025 20:03
@ondrejmirtes ondrejmirtes merged commit da74773 into phpstan:2.1.x Apr 14, 2025
415 of 417 checks passed
@ondrejmirtes
Copy link
Member

Perfect, thank you!

@staabm staabm deleted the constr-scope branch April 14, 2025 08:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment