55use PhpParser \Node ;
66use PHPStan \Analyser \Scope ;
77use PHPStan \Node \ClassPropertyNode ;
8+ use PHPStan \Php \PhpVersion ;
89use PHPStan \Reflection \ClassReflection ;
910use PHPStan \Reflection \Php \PhpPropertyReflection ;
1011use PHPStan \Rules \Rule ;
@@ -21,6 +22,7 @@ final class OverridingPropertyRule implements Rule
2122{
2223
2324 public function __construct (
25+ private PhpVersion $ phpVersion ,
2426 private bool $ checkPhpDocMethodSignatures ,
2527 private bool $ reportMaybes ,
2628 )
@@ -129,15 +131,45 @@ public function processNode(Node $node, Scope $scope): array
129131 ))->identifier ('property.missingNativeType ' )->nonIgnorable ()->build ();
130132 } else {
131133 if (!$ prototype ->getNativeType ()->equals ($ nativeType )) {
132- $ typeErrors [] = RuleErrorBuilder::message (sprintf (
133- 'Type %s of property %s::$%s is not the same as type %s of overridden property %s::$%s. ' ,
134- $ nativeType ->describe (VerbosityLevel::typeOnly ()),
135- $ classReflection ->getDisplayName (),
136- $ node ->getName (),
137- $ prototype ->getNativeType ()->describe (VerbosityLevel::typeOnly ()),
138- $ prototype ->getDeclaringClass ()->getDisplayName (),
139- $ node ->getName (),
140- ))->identifier ('property.nativeType ' )->nonIgnorable ()->build ();
134+ if (
135+ $ this ->phpVersion ->supportsPropertyHooks ()
136+ && ($ prototype ->isVirtual ()->yes () || $ prototype ->isAbstract ()->yes ())
137+ && (!$ prototype ->isReadable () || !$ prototype ->isWritable ())
138+ ) {
139+ if (!$ prototype ->isReadable ()) {
140+ if (!$ nativeType ->isSuperTypeOf ($ prototype ->getNativeType ())->yes ()) {
141+ $ typeErrors [] = RuleErrorBuilder::message (sprintf (
142+ 'Type %s of property %s::$%s is not contravariant with type %s of overridden property %s::$%s. ' ,
143+ $ nativeType ->describe (VerbosityLevel::typeOnly ()),
144+ $ classReflection ->getDisplayName (),
145+ $ node ->getName (),
146+ $ prototype ->getNativeType ()->describe (VerbosityLevel::typeOnly ()),
147+ $ prototype ->getDeclaringClass ()->getDisplayName (),
148+ $ node ->getName (),
149+ ))->identifier ('property.nativeType ' )->nonIgnorable ()->build ();
150+ }
151+ } elseif (!$ prototype ->getNativeType ()->isSuperTypeOf ($ nativeType )->yes ()) {
152+ $ typeErrors [] = RuleErrorBuilder::message (sprintf (
153+ 'Type %s of property %s::$%s is not covariant with type %s of overridden property %s::$%s. ' ,
154+ $ nativeType ->describe (VerbosityLevel::typeOnly ()),
155+ $ classReflection ->getDisplayName (),
156+ $ node ->getName (),
157+ $ prototype ->getNativeType ()->describe (VerbosityLevel::typeOnly ()),
158+ $ prototype ->getDeclaringClass ()->getDisplayName (),
159+ $ node ->getName (),
160+ ))->identifier ('property.nativeType ' )->nonIgnorable ()->build ();
161+ }
162+ } else {
163+ $ typeErrors [] = RuleErrorBuilder::message (sprintf (
164+ 'Type %s of property %s::$%s is not the same as type %s of overridden property %s::$%s. ' ,
165+ $ nativeType ->describe (VerbosityLevel::typeOnly ()),
166+ $ classReflection ->getDisplayName (),
167+ $ node ->getName (),
168+ $ prototype ->getNativeType ()->describe (VerbosityLevel::typeOnly ()),
169+ $ prototype ->getDeclaringClass ()->getDisplayName (),
170+ $ node ->getName (),
171+ ))->identifier ('property.nativeType ' )->nonIgnorable ()->build ();
172+ }
141173 }
142174 }
143175 } elseif ($ nativeType !== null ) {
@@ -167,6 +199,45 @@ public function processNode(Node $node, Scope $scope): array
167199 }
168200
169201 $ verbosity = VerbosityLevel::getRecommendedLevelByType ($ prototype ->getReadableType (), $ propertyReflection ->getReadableType ());
202+
203+ if (
204+ $ this ->phpVersion ->supportsPropertyHooks ()
205+ && ($ prototype ->isVirtual ()->yes () || $ prototype ->isAbstract ()->yes ())
206+ && (!$ prototype ->isReadable () || !$ prototype ->isWritable ())
207+ ) {
208+ if (!$ prototype ->isReadable ()) {
209+ if (!$ propertyReflection ->getReadableType ()->isSuperTypeOf ($ prototype ->getReadableType ())->yes ()) {
210+ $ errors [] = RuleErrorBuilder::message (sprintf (
211+ 'PHPDoc type %s of property %s::$%s is not contravariant with PHPDoc type %s of overridden property %s::$%s. ' ,
212+ $ propertyReflection ->getReadableType ()->describe ($ verbosity ),
213+ $ classReflection ->getDisplayName (),
214+ $ node ->getName (),
215+ $ prototype ->getReadableType ()->describe ($ verbosity ),
216+ $ prototype ->getDeclaringClass ()->getDisplayName (),
217+ $ node ->getName (),
218+ ))->identifier ('property.phpDocType ' )->tip (sprintf (
219+ "You can fix 3rd party PHPDoc types with stub files: \n %s " ,
220+ '<fg=cyan>https://phpstan.org/user-guide/stub-files</> ' ,
221+ ))->build ();
222+ }
223+ } elseif (!$ prototype ->getReadableType ()->isSuperTypeOf ($ propertyReflection ->getReadableType ())->yes ()) {
224+ $ errors [] = RuleErrorBuilder::message (sprintf (
225+ 'PHPDoc type %s of property %s::$%s is not covariant with PHPDoc type %s of overridden property %s::$%s. ' ,
226+ $ propertyReflection ->getReadableType ()->describe ($ verbosity ),
227+ $ classReflection ->getDisplayName (),
228+ $ node ->getName (),
229+ $ prototype ->getReadableType ()->describe ($ verbosity ),
230+ $ prototype ->getDeclaringClass ()->getDisplayName (),
231+ $ node ->getName (),
232+ ))->identifier ('property.phpDocType ' )->tip (sprintf (
233+ "You can fix 3rd party PHPDoc types with stub files: \n %s " ,
234+ '<fg=cyan>https://phpstan.org/user-guide/stub-files</> ' ,
235+ ))->build ();
236+ }
237+
238+ return $ errors ;
239+ }
240+
170241 $ isSuperType = $ prototype ->getReadableType ()->isSuperTypeOf ($ propertyReflection ->getReadableType ());
171242 $ canBeTurnedOffError = RuleErrorBuilder::message (sprintf (
172243 'PHPDoc type %s of property %s::$%s is not the same as PHPDoc type %s of overridden property %s::$%s. ' ,
0 commit comments